Files
Garry Tanandcapy-ai[bot] 4c6fa89cdc v1.91.159.0 security: gstack's Bun entry points ignore the audited repo's .env and bunfig.toml (S1) (#3312)
* security: isolate every gstack Bun entry point from the audited checkout's .env and bunfig.toml

Bun auto-loads .env, .env.local and .env.<NODE_ENV> from its cwd and runs a
cwd bunfig.toml preload before the program starts. gstack runs inside the repo
it audits, so a committed .env could steer state roots, the claude binary or
spend caps, and a bunfig preload ran arbitrary code in every bin and hook.

Every bun shebang, shell and skill caller, TypeScript spawn and compiled build
now disables both (--no-env-file --config=/dev/null, bunIsolationArgs(),
--no-compile-autoload-dotenv/bunfig), with a ratchet and hostile-fixture
regressions.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: name the directly executed scripts in the isolation ratchet

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: follow the isolation flags in touchfiles, copy lists, recipe assertions and the harness pin

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: keep browse/src/bun-child-config.ts unchanged so the native cookie qualification still binds

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(windows): the empty bunfig outlives its process so the timeline hook's detached repair child can read it

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.159.0 security: gstack's Bun entry points ignore the audited repo's .env and bunfig.toml (S1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: plan-mode render pattern accepts the isolation flags

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-20 00:24:28 +00:00

2.0 KiB

Polluter recipe

A polluter is an earlier test that leaks state the victim depends on: the victim passes alone and fails in the suite or in one shard.

  1. File order from the CI log. The shard runner prints its file list in execution order. Take the files that ran before the victim in the failing shard (gh run view <run-id> --log, or the local shard listing bun --no-env-file --config=/dev/null run scripts/ship-measure.ts free --shard <I> which reproduces the exact packing bun run test used).
  2. Confirm the dependency. The victim alone passes; the victim after the earlier files fails (<affected-files> is the earlier files then the victim, comma-joined, in shard order):
    bun --no-env-file --config=/dev/null run scripts/ship-measure.ts free --files <failing-test-file> --reruns 3
    bun --no-env-file --config=/dev/null run scripts/ship-measure.ts free --files <affected-files> --reruns 3
    
  3. Bisect the earlier files. Halve the earlier list, keep the half that still fails the victim, repeat until one file remains. Each step is one --files run; log the list at every step.
  4. Name what leaked. Read the polluter for state that outlives it: an env var, process.cwd(), process.exitCode, a module-level cache or singleton, a mock or spy left installed, a server or watcher left listening, a temp file under a shared path, a learnings or state file under the home directory (the free lane's home guard reports these), a global timer.
  5. Fix the polluter, not the victim. Restore or isolate the state where it is created (afterEach, mkdtemp, a private env); an order-dependent beforeEach added to the victim hides the leak for the next victim.
  6. Report: POLLUTER: <file> leaked <what> into <victim>; fixed by <change>, with the bisect log. If the polluter is on main, the victim's failure is outside the PR (route it with the flake recipe's evidence section).