fix(#3156): sandbox HOME on raw installer spawns — the one leak no scrub reaches
The strict live-config guard this PR ships went red on CI: the suite creates $HOME/.gsd/defaults.json. Diagnosed rather than suppressed, because the guard is right — this is #2665's class arriving through the one door the scrub set is structurally unable to close. bin/install.js writeNonClaudeDefaults() (#2834) writes path.join(os.homedir(), '.gsd', 'defaults.json') for every non-Claude runtime. os.homedir() consults NO GSD variable, so: - no entry in CONFIG_LOCATION_ENV_KEYS can reach it, however the set is derived; and - blanking GSD_HOME does not reach it either -- a blank GSD_HOME falls back to exactly that homedir(). Only a sandboxed HOME contains it, and HOME is deliberately excluded from TEST_ENV_BASE because blanking it would break far more than it fixed. So the containment belongs per-spawn, which is the discipline the suite already applies by hand -- install-minimal-hooks.test.cjs:576 carries a comment naming this exact hazard for the --codex spawn, while the parameterized --${runtime} spawn 130 lines below it does not. Instance fixed, class open. Rather than add a fifth hand-synced env shape to a PR whose subject is that hand-synced copies drift, this adds ONE export -- installSpawnEnv() in tests/helpers.cjs -- and routes every raw installer spawn through it, including the shared tests/helpers/install-shared.cjs installerEnv(), which every install suite already consumes. Callers passing an explicit { HOME, USERPROFILE } are unaffected: overrides spread last. Census (measured, not reasoned): of the 119 test files that spawn bin/install.js, exactly four wrote into a sandboxed $HOME before this commit -- install, copilot-install, install-minimal-hooks, opencode-plugin-adapter -- and zero do after. Only install.test.cjs sits in CI's targeted lane, which is why ubuntu went red on one file while the macOS full lanes went red on four. Attribution: the leak reproduces unchanged at upstream/next itself, so the defect is base-owned and pre-existing; only the detector is new. The guard found a real leak on next within one run. No new failures: the surviving names under a sandboxed HOME (folded:enh-2380-sync-skills, getGlobalConfigDir (Copilot)) fail at base too, and base additionally fails folded:bug-3288-model-catalog-install-path, which this tree does not.
This commit is contained in:
@@ -1362,7 +1362,10 @@ const EXPECTED_AGENTS = listAgentFiles().length;
|
||||
const { PROBE_TIMEOUT_MS, INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
|
||||
function runCopilotInstall(cwd) {
|
||||
const env = { ...process.env };
|
||||
// #3156: sandbox HOME — the installer writes <home>/.gsd/defaults.json via
|
||||
// os.homedir() directly, which no env scrub can reach. See installSpawnEnv.
|
||||
const { installSpawnEnv } = require('./helpers.cjs');
|
||||
const env = installSpawnEnv();
|
||||
delete env.GSD_TEST_MODE;
|
||||
const r = runNode([INSTALL_PATH, '--copilot', '--local', '--no-sdk'], {
|
||||
cwd,
|
||||
@@ -1374,7 +1377,10 @@ function runCopilotInstall(cwd) {
|
||||
}
|
||||
|
||||
function runCopilotUninstall(cwd) {
|
||||
const env = { ...process.env };
|
||||
// #3156: sandbox HOME — the installer writes <home>/.gsd/defaults.json via
|
||||
// os.homedir() directly, which no env scrub can reach. See installSpawnEnv.
|
||||
const { installSpawnEnv } = require('./helpers.cjs');
|
||||
const env = installSpawnEnv();
|
||||
delete env.GSD_TEST_MODE;
|
||||
const r = runNode([INSTALL_PATH, '--copilot', '--local', '--uninstall', '--no-sdk'], {
|
||||
cwd,
|
||||
@@ -1673,7 +1679,10 @@ describe('E2E: Copilot uninstall verification', () => {
|
||||
// ─── E2E: Copilot global scope (#786) ──────────────────────────────────────────
|
||||
|
||||
function runCopilotInstallGlobal(cwd, configDir) {
|
||||
const env = { ...process.env };
|
||||
// #3156: sandbox HOME — the installer writes <home>/.gsd/defaults.json via
|
||||
// os.homedir() directly, which no env scrub can reach. See installSpawnEnv.
|
||||
const { installSpawnEnv } = require('./helpers.cjs');
|
||||
const env = installSpawnEnv();
|
||||
delete env.GSD_TEST_MODE;
|
||||
const r = runNode(
|
||||
[INSTALL_PATH, '--copilot', '--global', '--config-dir', configDir, '--no-sdk'],
|
||||
@@ -1684,7 +1693,10 @@ function runCopilotInstallGlobal(cwd, configDir) {
|
||||
}
|
||||
|
||||
function runCopilotUninstallGlobal(cwd, configDir) {
|
||||
const env = { ...process.env };
|
||||
// #3156: sandbox HOME — the installer writes <home>/.gsd/defaults.json via
|
||||
// os.homedir() directly, which no env scrub can reach. See installSpawnEnv.
|
||||
const { installSpawnEnv } = require('./helpers.cjs');
|
||||
const env = installSpawnEnv();
|
||||
delete env.GSD_TEST_MODE;
|
||||
const r = runNode(
|
||||
[INSTALL_PATH, '--copilot', '--global', '--config-dir', configDir, '--uninstall', '--no-sdk'],
|
||||
@@ -1734,7 +1746,10 @@ describe('E2E: Copilot global install (#786)', () => {
|
||||
// ─── Claude uninstall: user file preservation (#1423) ─────────────────────────
|
||||
|
||||
function runClaudeInstall(cwd) {
|
||||
const env = { ...process.env };
|
||||
// #3156: sandbox HOME — the installer writes <home>/.gsd/defaults.json via
|
||||
// os.homedir() directly, which no env scrub can reach. See installSpawnEnv.
|
||||
const { installSpawnEnv } = require('./helpers.cjs');
|
||||
const env = installSpawnEnv();
|
||||
delete env.GSD_TEST_MODE;
|
||||
const r = runNode([INSTALL_PATH, '--claude', '--local', '--no-sdk'], {
|
||||
cwd,
|
||||
@@ -1746,7 +1761,10 @@ function runClaudeInstall(cwd) {
|
||||
}
|
||||
|
||||
function runClaudeUninstall(cwd) {
|
||||
const env = { ...process.env };
|
||||
// #3156: sandbox HOME — the installer writes <home>/.gsd/defaults.json via
|
||||
// os.homedir() directly, which no env scrub can reach. See installSpawnEnv.
|
||||
const { installSpawnEnv } = require('./helpers.cjs');
|
||||
const env = installSpawnEnv();
|
||||
delete env.GSD_TEST_MODE;
|
||||
const r = runNode([INSTALL_PATH, '--claude', '--local', '--uninstall', '--no-sdk'], {
|
||||
cwd,
|
||||
|
||||
Reference in New Issue
Block a user