From 4c6fa89cdce663f7ee027e2b47dad60ae7b3265f Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 19 Oct 2026 17:24:28 -0700 Subject: [PATCH] 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. 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> --- AGENTS.md | 1 + CHANGELOG.md | 22 ++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- autoplan/SKILL.md | 2 +- autoplan/bin/phase-publication-hook | 2 +- autoplan/bin/phase-publication-hook.ts | 2 +- autoplan/sections/ceo-phase.md | 2 +- autoplan/sections/design-phase.md | 2 +- autoplan/sections/dx-phase.md | 2 +- autoplan/sections/eng-phase.md | 2 +- bin/gstack-approval-classes | 2 +- bin/gstack-artifact | 2 +- bin/gstack-autoplan | 2 +- bin/gstack-autoplan-snapshot.ts | 2 +- bin/gstack-autoplan-timing | 2 +- bin/gstack-banned-terms | 2 +- bin/gstack-brain-cache | 2 +- bin/gstack-brain-context-load.ts | 2 +- bin/gstack-brain-sync | 2 +- bin/gstack-browser-ensure | 2 +- bin/gstack-ci-gate | 2 +- bin/gstack-claude-code | 2 +- bin/gstack-code-intelligence | 2 +- bin/gstack-codex-probe | 2 +- bin/gstack-codex-session-import | 2 +- bin/gstack-context-bill | 2 +- bin/gstack-contributor-mode | 2 +- bin/gstack-decision-log | 2 +- bin/gstack-decision-search | 2 +- bin/gstack-design-approved | 2 +- bin/gstack-design-detect.ts | 2 +- bin/gstack-design-md.ts | 2 +- bin/gstack-developer-profile | 22 +- bin/gstack-distill-apply | 4 +- bin/gstack-distill-free-text | 10 +- bin/gstack-docs-candidate | 2 +- bin/gstack-doctor | 2 +- bin/gstack-doctor-check.sh | 6 +- bin/gstack-egress | 2 +- bin/gstack-egress-receipt | 2 +- bin/gstack-error-capture | 2 +- bin/gstack-eval-plan | 2 +- bin/gstack-evidence | 101 +-------- bin/gstack-gate | 2 +- bin/gstack-gate-diff | 2 +- bin/gstack-gbrain-detect | 2 +- bin/gstack-gbrain-install | 4 +- bin/gstack-gbrain-read-capability.ts | 2 +- bin/gstack-gbrain-source-wireup | 2 +- bin/gstack-gbrain-supabase-provision | 2 +- bin/gstack-gbrain-sync.ts | 5 +- bin/gstack-global-discover.ts | 2 +- bin/gstack-greptile-early | 2 +- bin/gstack-hook-check | 2 +- bin/gstack-install-verdict.sh | 2 +- bin/gstack-ios-qa-daemon | 2 +- bin/gstack-ios-qa-mint | 2 +- bin/gstack-ios-qa-regen | 2 +- bin/gstack-issue-guard | 2 +- bin/gstack-issue-links | 2 +- bin/gstack-lane-check | 2 +- bin/gstack-learnings-log | 2 +- bin/gstack-learnings-search | 2 +- bin/gstack-memorable | 2 +- bin/gstack-memory-ingest.ts | 2 +- bin/gstack-migrate-claude-code | 2 +- bin/gstack-model-benchmark | 2 +- bin/gstack-models | 2 +- bin/gstack-next-version | 2 +- bin/gstack-office-hours-review | 2 +- bin/gstack-outside-voice | 2 +- bin/gstack-owner-brief | 2 +- bin/gstack-plan-reality | 2 +- bin/gstack-post | 2 +- bin/gstack-pregate | 2 +- bin/gstack-qa-deadline | 2 +- bin/gstack-qa-evidence | 2 +- bin/gstack-question-log | 6 +- bin/gstack-question-preference | 8 +- bin/gstack-redact | 2 +- bin/gstack-redact-prepush | 2 +- bin/gstack-regen | 2 +- bin/gstack-relink | 2 +- bin/gstack-render.ts | 2 +- bin/gstack-restamp | 2 +- bin/gstack-review-log | 6 +- bin/gstack-review-read | 2 +- bin/gstack-runtime-pins.ts | 2 +- bin/gstack-settings-hook | 18 +- bin/gstack-ship-policy | 2 +- bin/gstack-ship-queue | 2 +- bin/gstack-ship-receipt | 2 +- bin/gstack-specialist-stats | 2 +- bin/gstack-state-matrix | 2 +- bin/gstack-taste-update | 2 +- bin/gstack-team-init | 2 +- bin/gstack-telemetry-log | 2 +- bin/gstack-timeline-log | 6 +- bin/gstack-timeline-read | 2 +- bin/gstack-tree-receipt | 2 +- bin/gstack-version-bump | 5 +- bin/gstack-windows-bash.ts | 2 +- browse/scripts/extension-id.ts | 2 +- browse/test/learnings-injection.test.ts | 4 +- codex/sections/challenge-mode.md | 2 +- codex/sections/challenge-mode.md.tmpl | 2 +- codex/sections/consult-mode.md | 2 +- codex/sections/consult-mode.md.tmpl | 2 +- codex/sections/review-mode.md | 4 +- codex/sections/review-mode.md.tmpl | 4 +- design-consultation/SKILL.md | 8 +- .../sections/proposal-and-preview.md | 4 +- .../sections/proposal-and-preview.md.tmpl | 2 +- design-html/SKILL.md | 6 +- design-html/SKILL.md.tmpl | 2 +- .../sections/detector-install-offer.md | 2 +- design-review/SKILL.md | 14 +- diagram/SKILL.md | 4 +- diagram/SKILL.md.tmpl | 4 +- document-release/sections/release-body.md | 6 +- .../sections/release-body.md.tmpl | 4 +- gstack-upgrade/migrations/v1.86.0.0.sh | 2 +- health/SKILL.md | 2 +- health/SKILL.md.tmpl | 2 +- hosts/claude/hooks/auq-error-fallback-hook | 2 +- hosts/claude/hooks/auq-error-fallback-hook.ts | 2 +- hosts/claude/hooks/memorable-user-prompt-hook | 2 +- .../hooks/memorable-user-prompt-hook.ts | 2 +- hosts/claude/hooks/question-log-hook | 2 +- hosts/claude/hooks/question-log-hook.ts | 2 +- hosts/claude/hooks/question-preference-hook | 2 +- .../claude/hooks/question-preference-hook.ts | 2 +- hosts/claude/hooks/timeline-stop-hook | 2 +- hosts/claude/hooks/timeline-stop-hook.ts | 5 +- investigate/sections/flake.md | 2 +- investigate/sections/flake.md.tmpl | 2 +- investigate/sections/polluter.md | 6 +- investigate/sections/polluter.md.tmpl | 6 +- ios-qa/scripts/gen-accessors.ts | 2 +- land-and-deploy/SKILL.md | 2 +- land-and-deploy/SKILL.md.tmpl | 2 +- landing-report/SKILL.md | 2 +- landing-report/SKILL.md.tmpl | 2 +- learn/SKILL.md | 2 +- learn/SKILL.md.tmpl | 2 +- lib/autoplan-prompts.ts | 3 +- lib/bun-isolation.ts | 56 +++++ lib/claude-code-migration.ts | 5 +- lib/cso/cli.ts | 2 +- lib/cso/preparation-container.ts | 2 +- lib/cso/verifier.ts | 2 +- lib/qa-deadline.ts | 3 +- lib/restamp.ts | 5 +- lib/script-invocation.ts | 3 +- make-pdf/src/cli.ts | 2 +- office-hours/SKILL.md | 6 +- package.json | 4 +- plan-ceo-review/bin/mode-handoff-hook | 2 +- plan-ceo-review/bin/mode-handoff-hook.ts | 2 +- plan-ceo-review/sections/review-sections.md | 2 +- plan-design-review/SKILL.md | 2 +- plan-devex-review/sections/review-sections.md | 2 +- plan-eng-review/sections/review-sections.md | 2 +- plan-tune/SKILL.md | 14 +- plan-tune/SKILL.md.tmpl | 14 +- retro/SKILL.md | 2 +- retro/SKILL.md.tmpl | 2 +- review/SKILL.md | 4 +- review/SKILL.md.tmpl | 4 +- review/design-checklist.md | 6 +- review/sections/adversarial.md | 4 +- scripts/analytics.ts | 2 +- scripts/build-app.sh | 2 +- scripts/build-cso.sh | 2 +- scripts/bump-harness-version.ts | 2 +- scripts/capture-baseline.ts | 2 +- scripts/census-arms.ts | 2 +- scripts/compare-pr-version.ts | 2 +- scripts/cso-attestation-evidence.ts | 2 +- scripts/cso-eval-producer.ts | 2 +- scripts/cso-eval.ts | 2 +- scripts/cso-image-matrix.ts | 2 +- scripts/cso-native-evidence.ts | 2 +- scripts/cso-public-ghcr.ts | 2 +- scripts/cso-requalification.ts | 2 +- scripts/cso-runtime-promotion.ts | 2 +- scripts/cso-scanner-catalog.ts | 2 +- scripts/cso-scanner-matrix.ts | 2 +- scripts/cso-staging-run.ts | 2 +- scripts/cso-verify-runtime-base.ts | 2 +- scripts/detect-bump.ts | 2 +- scripts/dev-skill.ts | 2 +- scripts/eval-bg.ts | 2 +- scripts/eval-compare.ts | 2 +- scripts/eval-flake-rank.ts | 2 +- scripts/eval-list.ts | 2 +- scripts/eval-select.ts | 2 +- scripts/eval-summary.ts | 2 +- scripts/eval-trial-series.ts | 2 +- scripts/exercised-map.ts | 2 +- scripts/garry-output-comparison.ts | 2 +- scripts/gate-cost-replay.ts | 2 +- scripts/gen-agents-digest.ts | 2 +- scripts/gen-doctor-components.ts | 2 +- scripts/gen-llms-txt.ts | 2 +- scripts/gen-skill-docs.ts | 2 +- scripts/harness-version.json | 2 +- scripts/host-config-export.ts | 2 +- scripts/judge-calibration.ts | 2 +- scripts/judge-controls.ts | 2 +- scripts/measure-journal-read.ts | 2 +- scripts/preflight-codex-overlap.ts | 2 +- scripts/prompt-audit-manifest.ts | 2 +- scripts/resolve-codex-generation-model.ts | 2 +- scripts/resolvers/composition.ts | 2 +- scripts/resolvers/design-checklist.ts | 6 +- scripts/resolvers/design.ts | 14 +- scripts/resolvers/outside-voice.ts | 6 +- scripts/retired-command.ts | 2 +- scripts/ship-measure.ts | 2 +- scripts/skill-check.ts | 2 +- scripts/slop-diff.ts | 2 +- scripts/test-free-shards.ts | 2 +- scripts/test-health-report.ts | 2 +- scripts/test-paid-shards.ts | 2 +- scripts/typecheck-test.ts | 2 +- scripts/ubicloud/pregate-remote.sh | 6 +- scripts/ubicloud/setup-free-suite.sh | 2 +- scripts/ubicloud/test-free.sh | 2 +- setup | 6 +- setup-gbrain/SKILL.md | 2 +- setup-gbrain/SKILL.md.tmpl | 2 +- setup-gbrain/sections/transcript-gate.md | 8 +- setup-gbrain/sections/transcript-gate.md.tmpl | 8 +- ship/SKILL.md | 8 +- ship/SKILL.md.tmpl | 8 +- ship/sections/adversarial.md | 4 +- ship/sections/measure.md | 2 +- ship/sections/measure.md.tmpl | 2 +- ship/sections/review-army.md | 8 +- spec/sections/gate-and-file.md | 4 +- spec/sections/gate-and-file.md.tmpl | 2 +- sync-gbrain/SKILL.md | 12 +- sync-gbrain/SKILL.md.tmpl | 12 +- test/bun-dotenv-isolation.test.ts | 161 +++++++++++++++ test/bun-entry-isolation.test.ts | 191 ++++++++++++++++++ test/bun-isolation-launch.test.ts | 71 +++++++ test/document-skills-redaction.test.ts | 2 +- test/evidence.test.ts | 93 ++------- test/exercised-map.test.ts | 1 + test/fixtures/golden/agy-ship-SKILL.md | 8 +- test/fixtures/golden/claude-ship-SKILL.md | 8 +- test/fixtures/golden/codex-ship-SKILL.md | 8 +- test/fixtures/golden/factory-ship-SKILL.md | 8 +- test/fixtures/skill-md-size-allowlist.json | 160 +++++++-------- test/gen-skill-docs.test.ts | 4 +- test/gstack-doctor.test.ts | 2 +- test/helpers/qa-evidence-producer.ts | 2 +- test/helpers/touchfiles-data.ts | 24 +-- test/investigate-recipes.test.ts | 2 +- test/learnings-injection.test.ts | 6 +- test/plan-mode-preamble-render.test.ts | 2 +- test/plan-tune-cathedral.test.ts | 2 +- test/plan-tune.test.ts | 2 +- test/script-invocation.test.ts | 3 +- .../setup-gbrain-bin-invocation-paths.test.ts | 4 +- test/setup-hook-canonical-paths.test.ts | 4 +- test/ship-measure.test.ts | 2 +- test/ship-version-sync.test.ts | 2 +- test/ship-workflow-clarity.test.ts | 2 +- test/team-mode.test.ts | 3 +- test/timeline-stop-hook-slug.test.ts | 1 + 273 files changed, 1029 insertions(+), 669 deletions(-) create mode 100644 lib/bun-isolation.ts create mode 100644 test/bun-dotenv-isolation.test.ts create mode 100644 test/bun-entry-isolation.test.ts create mode 100644 test/bun-isolation-launch.test.ts diff --git a/AGENTS.md b/AGENTS.md index 0b6fc7e70..ee8777385 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -298,3 +298,4 @@ Agents poll `eval:bg:*` logs for the `### gstack-detach EXIT= ###` sentine - Every Codex run's verdict goes through `lib/outside-review-result.ts`; its reason codes live in `lib/gate-outcomes.ts`, each with a `docs/troubleshooting.md` anchor. - `test/module-size-ratchet.test.ts` keeps refactored owner modules at or under 800 lines (150 per function) and residual files from growing. - The `claude` CLI binary resolves via `lib/claude-bin.ts` (re-exported from `browse/src/claude-bin.ts` for browse internals; `Bun.which()` + `GSTACK_CLAUDE_BIN` override). Set `GSTACK_CLAUDE_BIN=wsl` plus `GSTACK_CLAUDE_BIN_ARGS='["claude"]'` to run Claude through WSL on Windows. +- Every Bun gstack starts ignores the working directory's `.env` files and `bunfig.toml`: bun shebangs are `#!/usr/bin/env -S bun --no-env-file --config=/dev/null`, shell and skill callers pass `bun --no-env-file --config=/dev/null`, TypeScript spawns use `bunIsolationArgs()` from `lib/bun-isolation.ts`, and `bun build --compile` adds `--no-compile-autoload-dotenv --no-compile-autoload-bunfig`. `test/bun-entry-isolation.test.ts` enforces all four. diff --git a/CHANGELOG.md b/CHANGELOG.md index ec881da50..65c57f156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## [1.91.159.0] - 2026-10-19 + +**Security: the repository gstack runs in can no longer configure gstack's own programs.** Bun loads `.env`, `.env.local` and `.env.` from the directory it starts in. It also runs any `preload` that a `bunfig.toml` there names, before the program's first line. gstack runs inside the repository it audits, so a committed or branch `.env` could set `GSTACK_STATE_ROOT`, `GSTACK_PROJECT_SLUG`, `GSTACK_CLAUDE_BIN` (an attacker-chosen binary then ran as "claude"), `GSTACK_SPEND_CAP_USD` or a provider base URL for every gstack bin, hook and skill step. A `bunfig.toml` preload ran arbitrary code in all of them. Every Bun that gstack starts now passes `--no-env-file` and an empty `--config`. Your shell environment works exactly as before. Found in review by GSTA-54. + +### Security +- **Every bun entry point starts isolated:** all 123 bun shebangs are now `#!/usr/bin/env -S bun --no-env-file --config=/dev/null`. `bun run ` ignores the shebang, so the callers carry the flags too: skill templates, generated skill docs, resolvers, hook wrappers, `setup` and the bash bins pass `bun --no-env-file --config=/dev/null` for every `run`, `-e`, `-p` and script path. +- **TypeScript spawns use `bunIsolationArgs()`** from `lib/bun-isolation.ts`, which follows browse's `emptyBunConfig()`. This covers `scriptInvocation` (how TypeScript launches gstack's own bins), restamp, qa-deadline, the timeline hook's repair child, gbrain sync's ingest child, the agents-digest regen and the Claude Code migration. Native Windows has no `/dev/null`, so there the helper passes a zero-byte bunfig in a fixed directory under the per-user temp root. It is never removed, because the timeline hook's detached repair child reads it after the hook exits. Under Git Bash, MSYS rewrites the shebang's `/dev/null` to `nul`, which Bun 1.4.2 reads as an empty config. A windows-latest run verified both. +- **Compiled builds:** `build:gates` and `scripts/build-app.sh` now pass `--no-compile-autoload-dotenv --no-compile-autoload-bunfig`, as `scripts/build.sh` already did. Without them, compiled binaries load both files. +- **A ratchet and regressions:** + - `test/bun-entry-isolation.test.ts` fails on any bun shebang, shell or skill caller, TypeScript spawn or `build --compile` that drops the flags. + - `test/bun-dotenv-isolation.test.ts` runs the real bins in a fixture repo with a hostile `.env`, `.env.local` and `bunfig.toml`. The state root, slug, claude binary and pregate spend cap still come from the shell. + - `test/bun-isolation-launch.test.ts` runs the shebang, bash and TypeScript launch forms on every free lane, Windows included. + +### Changed +- **`gstack-evidence run` no longer scrubs dotenv values from the child environment,** and `GSTACK_EVIDENCE_KEEP_DOTENV` is gone. Bun no longer loads those values into the wrapper, so the child sees your shell's environment, as it would if you ran the command by hand. The old value-equality scrub (#2652, @namtrok) also dropped shell-exported variables whose value matched the file. +- **Raised SKILL.md size pins:** 80 pins in `test/fixtures/skill-md-size-allowlist.json` grew by 33 to 330 characters each, from the isolation flags alone (8,253 characters in total). +- **Directly executed `./scripts/*.ts` files no longer read gstack's own `.env`.** `bun run