mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-09 18:28:15 +01:00
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>
This commit is contained in:
1 parent
807dd806d7
commit
836ab20575
4 files changed
+43
-3
No files matched your search
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
### 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 <file>` 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 now owns 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 private zero-byte bunfig. 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.
|
||||
- **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 private zero-byte bunfig. 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.
|
||||
|
||||
@@ -1,2 +1,35 @@
|
||||
/** The empty `--config` for every Bun child browse spawns (#3085); lib/bun-isolation.ts owns it. */
|
||||
export { emptyBunConfig } from '../../lib/bun-isolation';
|
||||
/**
|
||||
* The `--config` file for every Bun child browse spawns (#3085). The child
|
||||
* runs in the user's project, where Bun would otherwise load that project's
|
||||
* bunfig.toml, whose preload runs code. POSIX passes /dev/null. Windows has no
|
||||
* /dev/null and `--config=NUL` panicked Bun 1.3.10 while parsing the flag, so
|
||||
* Windows gets an empty bunfig created exclusively in a private directory and
|
||||
* re-checked to be a zero-byte regular file before each spawn; anything else
|
||||
* is replaced, so the file cannot become a preload vector.
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
let emptyConfig: string | null = null;
|
||||
|
||||
function isEmptyRegularFile(file: string): boolean {
|
||||
try {
|
||||
const stat = fs.lstatSync(file);
|
||||
return stat.isFile() && stat.size === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function emptyBunConfig(platform: NodeJS.Platform = process.platform): string {
|
||||
if (platform !== 'win32') return '/dev/null';
|
||||
if (emptyConfig && isEmptyRegularFile(emptyConfig)) return emptyConfig;
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-bunfig-'));
|
||||
const file = path.join(dir, 'bunfig.toml');
|
||||
fs.closeSync(fs.openSync(file, 'wx', 0o600));
|
||||
if (!isEmptyRegularFile(file)) throw new Error(`empty Bun config ${file} is not a zero-byte file`);
|
||||
process.once('exit', () => fs.rmSync(dir, { recursive: true, force: true }));
|
||||
emptyConfig = file;
|
||||
return file;
|
||||
}
|
||||
@@ -14,6 +14,9 @@
|
||||
* Windows gets an empty bunfig created exclusively in a private directory and
|
||||
* re-checked to be a zero-byte regular file before each spawn; anything else
|
||||
* is replaced, so the file cannot become a preload vector.
|
||||
* browse/src/bun-child-config.ts keeps its own copy of emptyBunConfig(): the
|
||||
* Windows native cookie qualification binds that file's bytes and bundles it
|
||||
* without lib/.
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
|
||||
@@ -22,6 +22,7 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { bunIsolationArgs, emptyBunConfig } from '../lib/bun-isolation';
|
||||
import { scriptInvocation } from '../lib/script-invocation';
|
||||
import { emptyBunConfig as browseEmptyBunConfig } from '../browse/src/bun-child-config';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const FLAGS = '--no-env-file --config=/dev/null';
|
||||
@@ -154,6 +155,9 @@ describe('GSTA-56: gstack never takes configuration from the audited repository'
|
||||
expect(inv).toEqual({ ok: true, cmd: 'bun', argv: [...bunIsolationArgs(), path.join(ROOT, 'bin', 'gstack-decision-log'), '--help'] });
|
||||
expect(bunIsolationArgs('linux')).toEqual(['--no-env-file', '--config=/dev/null']);
|
||||
expect(bunIsolationArgs('win32')).toEqual(['--no-env-file', `--config=${emptyBunConfig('win32')}`]);
|
||||
expect(browseEmptyBunConfig('linux')).toBe(emptyBunConfig('linux'));
|
||||
expect(fs.statSync(browseEmptyBunConfig('win32')).size).toBe(0);
|
||||
expect(fs.statSync(emptyBunConfig('win32')).size).toBe(0);
|
||||
});
|
||||
|
||||
test('every bun build --compile disables .env and bunfig autoload', () => {
|
||||
|
||||
Reference in new issue
Block a user