Uh oh!
There was an error while loading. Please reload this page.
bin/dev: scrub foreign bundler activation before Ruby boots - #95
Merged
Conversation
A harness running under bundle exec leaks RUBYOPT/BUNDLE_* into every child, and the interpreter activates the caller's bundle before dev's Ruby half runs a single line — dev then can't load its own gems (observed live as ai-flow#44's LoadError). dev picks its own Ruby and gems; no caller's activation is ever wanted, so the sh shim unsets the activation keys itself, making every caller's scrub defense-in-depth instead of load-bearing. Closes#94 Co-authored-by: Cursor <cursoragent@cursor.com>
2 tasks
The dev.yml probe dragged shadowenv provisioning into the test, which fails on CI runners; a PATH-stubbed ruby printing its env pins the exact unset list hermetically. Co-authored-by: Cursor <cursoragent@cursor.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The suite's own bundle exec activation is the live truth: any key where ENV differs from Bundler.original_env is something the locked bundler exported, so a bundler bump that exports a new activation key turns the build red naming it. Subset direction only — config-dependent keys the launch didn't export are free no-ops, so exact equality would just add flake. The guard caught its first drift before shipping: bundler 4 exports BUNDLE_LOCKFILE, which the hand-pinned list missed. Co-authored-by: Cursor <cursoragent@cursor.com>
The sh shim is the single source of truth for the scrub list (the unset must run before any Ruby exists), so the tests alias it by parsing bin/dev instead of keeping a pinned copy that could drift; the stub-ruby and env-parsing mechanics move into named helpers and the file comment states what each of the three tests proves. Co-authored-by: Cursor <cursoragent@cursor.com>
Uh oh!
There was an error while loading. Please reload this page.
JPDuchesne added a commit
that referenced
this pull request
Aug 19, 2026
916fe89 Merge pull request #128 from d3mlabs/jpd/skill-installer-ephemeral-guard b9ea143 Move the ephemeral-source guard to SkillInstaller, the seam all links share 36803f7 Merge pull request #127 from d3mlabs/jpd/capture-learning-root-cause-gate d6e081e Name the wide-angle goal, not one command: an exact git-log depth invites checkbox compliance f876dbe capture-learning: gate workaround learnings on root cause, add wide angle 7249198 Merge pull request #123 from d3mlabs/ai/119-pr-b-typed-child-process-failure-taxonom e71063a Merge pull request #126 from d3mlabs/jpd/hermetic-scrub-guard f911e38 Make the scrub-list guard hermetic: construct the bundler launch it measures 6c398b8 ai-flow /build: let's resolve conflicts cb68d60 Merge pull request #122 from d3mlabs/ai/118-pr-d-split-commandexecutor-into-a-dispat 4477c6b Update the manifest-loader contract note for the eager toolchain pass 3ad03c3 Constructor-inject CommandRunner; two messages replace the wait flag 2ac941a Route help through the command path; group and eager-load usage a78ba14 Add the help builtin c7ae57a Add Category trait to the Command hierarchy 2e05625 ai-flow /build: let's fix the fake classes, put them within the test class a29b5e6 Merge main: sealed-module Command hierarchy, super() convention, and bin/test.rb runner 5d9c57b Merge pull request #121 from d3mlabs/ai/117-pr-a-close-the-sealed-command-hierarchy b8ed631 Call super() in every initializer that derives from the Command hierarchy 5bcc76f Rework the seal: Command becomes a sealed module, BuiltinCommand the abstract open-edge class 5c68c85 Merge pull request #120 from d3mlabs/ai/116-pr-c-bin-test-rb-tee-suite-output-to-a-s c62efc8 ai-flow /build: PR B: Typed child-process failure taxonomy in CommandRunner (CommandFailedError / CommandKilledError / CommandSpawnError) mapped to exit codes in Runner#exit_for f09f845 ai-flow /build: PR D: Split CommandExecutor into a dispatching composite with injectable BuiltinExecutor / ProjectExecutor / OverriddenExecutor strategies (exec_into vs run_waiting) 0775515 ai-flow /build: PR A: Close the sealed Command hierarchy honestly — BuiltinBody interface, final BuiltinCommand holding a body, delete the sorbet-runtime ivar pokes, un-private CommandRepository 5a5fb41 ai-flow /build: PR C: bin/test.rb — tee suite output to a stable log artifact and pass file args through to rake TEST 4c89408 Merge pull request #115 from d3mlabs/ai/37-layer-the-dev-runner-application-service 04d461c Add the simplecov-cobertura gem RBI 81677f6 Upload cobertura to codecov instead of SimpleCov JSON 0a741f0 Cover the default factories, image credential providers, and nocov the sealed absurd arm fe7c94e ai-flow /build: Layer the dev Runner (application service + boundary coercion) d782b1a Merge pull request #107 from d3mlabs/ai/101-dev-clone-host-global-builtin-cloning-vi f305ac9 ai-flow /build: codecov coverage missing fac96ee ai-flow /build: dev clone: host-global builtin cloning via gh auth to the canonical $DEV_CD_ROOT path d16b757 Merge pull request #100 from d3mlabs/jpd/99-pin-homebrew-installer 2ad614e Pin the Homebrew installer to a commit SHA (dev#99) 53e3616 Merge pull request #90 from d3mlabs/ai/89-gemskilllinker-links-minted-under-a-sand 95ee372 Merge pull request #97 from d3mlabs/ai/learn-promote-rbenv-libruby-rpath-hijack f7edc33 chore: nudge origin-firing after ai-flow#57 (removal diffs skip green) 646f189 Merge pull request #98 from d3mlabs/jpd/proposal-checks-edited 50e913a proposal-checks: re-verify on PR body edits (ai-flow#54) 59a3146 ai-flow /learn: drop rbenv-libruby-rpath-hijack (promoted to the org tier) f119987 Merge pull request #96 from d3mlabs/jpd/ai-flow-knowledge-repo b0e7131 ai-flow config: opt dev into org-tier learning promotion d17b2ff Merge pull request #95 from d3mlabs/jpd/94-self-defending-entrypoint 29b2e16 Test readability: one aliased scrub list, one property per test f7197ac Drift guard: the unset list must cover what the running bundler exports 049bbc8 Probe the shim scrub with a stub ruby instead of a full dev command run 88fa953 bin/dev: scrub foreign bundler activation before Ruby boots 9cf868a Merge pull request #92 from d3mlabs/ai/60-plan-pull-mangles-files-with-an-empty-fr 6783469 Merge pull request #93 from d3mlabs/ai/learn-issue-60 991d46d ai-flow /build: capture learnings from the build pass fe64507 ai-flow /build: Plan pull mangles files with an empty frontmatter block above the real one (double frontmatter) d8db57d ai-flow /build: GemSkillLinker: links minted under a sandboxed session point into ephemeral sandbox cache paths b4526ea Merge pull request #88 from d3mlabs/jpd/ast-transform-3.1.1 de9beaf Bump ast_transform to 3.1.1 and drop the heredoc-emission workaround
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
bin/devsh shim now unsets the bundler-activation keys (RUBYOPT,RUBYLIB,BUNDLE_GEMFILE,BUNDLE_PATH,BUNDLE_APP_CONFIG,BUNDLE_BIN,BUNDLE_BIN_PATH,BUNDLER_VERSION,BUNDLER_SETUP) before probing for or exec'ing Ruby — a leakedRUBYOPT=-r.../bundler/setupis processed by the interpreter ahead of the script's first line, so the sh layer is the only place this defense can live.GEM_HOME/GEM_PATHdeliberately stay: they're legitimate user config, and the one resolution they can redirect into an ephemeral cache is guarded at its call site (GemSkillLinker: links minted under a sandboxed session point into ephemeral sandbox cache paths #90).RUBYOPTnorBUNDLE_GEMFILE.Why
Third member of the harness-env-leak family (ai-flow#38, ai-flow#44, dev#89). Every caller patching every spawn site is a discipline that keeps failing — ai-flow#44 happened precisely because a later-added shell-out missed the existing scrub. dev defending itself at its entrypoint retires the class for every caller: ai-flow, sandboxed sessions, CI, anything future.
Test plan
Dev::BinDevTestred without the shim change, green with itsrb tcall greenCloses#94
Made with Cursor