Files
gstack/scripts/retired-command.ts
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

50 lines
2.9 KiB
TypeScript

#!/usr/bin/env -S bun --no-env-file --config=/dev/null
/**
* One-release stubs for retired package scripts. Each retired name in
* package.json runs `bun run scripts/retired-command.ts <name>`, which prints
* the replacement and exits 1, so an old command in an agent's memory or a
* stale doc fails loudly instead of running zero cases or the wrong lane.
*
* CONTRIBUTING.md "Retired commands" carries the same table. TODOS.md
* "Remove one-release command stubs" deletes this file and the stub entries
* in the next release.
*/
export interface RetiredCommand {
replacement: string;
reason: string;
}
export const RETIRED_COMMANDS: Record<string, RetiredCommand> = {
'test:evals': { replacement: 'bun run eval:bg:pr', reason: 'tierless run skipped every tier-gated paid file' },
'test:evals:all': { replacement: 'bun run eval:bg:release', reason: 'tierless run skipped every tier-gated paid file' },
'test:e2e': { replacement: 'bun run eval:bg:pr', reason: 'tierless run skipped every tier-gated paid file' },
'test:e2e:all': { replacement: 'bun run eval:bg:release', reason: 'tierless run skipped every tier-gated paid file' },
'test:gate': { replacement: 'bun run test:gate:sharded', reason: 'the single-process fan-out never completed a run' },
'test:periodic': { replacement: 'bun run test:periodic:sharded', reason: 'the single-process fan-out never completed a run' },
'test:codex': { replacement: 'bun run test:periodic:sharded', reason: 'it set no EVALS_TIER, so both periodic-tier Codex files ran zero cases' },
'test:codex:all': { replacement: 'bun run test:periodic:sharded', reason: 'it set no EVALS_TIER, so both periodic-tier Codex files ran zero cases' },
'eval:bg': { replacement: 'bun run eval:bg:pr', reason: 'it detached the retired tierless test:evals' },
'eval:bg:all': { replacement: 'bun run eval:bg:release', reason: 'it detached the retired tierless test:evals:all' },
'eval:flake-rank': { replacement: 'bun run eval:pass-rates', reason: 'it was a second name for the same script' },
'eval:watch': { replacement: 'tail the gstack-detach log under ~/.gstack-dev/eval-runs/, or `gh run watch <run-id>` for a CI run', reason: 'it read a file only the unsharded runner wrote, so it showed nothing for sharded runs' },
'test:audit': { replacement: 'bun run test', reason: 'test/audit-compliance.test.ts already runs in the free suite' },
};
export function retiredMessage(name: string): string | null {
const entry = RETIRED_COMMANDS[name];
if (!entry) return null;
return `\`bun run ${name}\` is retired (${entry.reason}).\nUse instead: ${entry.replacement}\nSee CONTRIBUTING.md "Retired commands" for the full table.`;
}
if (import.meta.main) {
const name = process.argv[2] ?? '';
const message = retiredMessage(name);
if (!message) {
console.error(`retired-command: unknown name "${name}". Known: ${Object.keys(RETIRED_COMMANDS).join(', ')}`);
process.exit(2);
}
console.error(message);
process.exit(1);
}