fix(#2665): watch the fallback root a scrubbing child actually resolves to
resolveLiveConfigRoots resolves what THIS process sees, and getGlobalConfigDir is env-first -- so with an ambient CLAUDE_CONFIG_DIR the guard watched that path. A spawned child does not see it: TEST_ENV_BASE blanks the config-location vars precisely so the child cannot follow them, and a blanked var is falsy, so the child resolves its HOME-derived root instead. A child that blanks the var and does NOT also sandbox HOME therefore writes into the developer's real ~/.claude, which the guard was not watching. That is this PR's own escape route, taken one process deeper -- and the guard is the artifact that is supposed to make it loud. Both resolutions are now unioned: the ambient one, and the fallback one obtained by handing the REAL descriptor resolver an EMPTY env. Deriving it that way is deliberate -- a hand-listed copy of the scrub set inside the guard is a second list to drift, which is the defect this PR spent three rounds closing one layer up. grok resolves through a hardcoded branch rather than a descriptor, so its fallback is stated explicitly for the same reason it is named in the ambient loop. Addresses review finding: Blocker 3.
This commit is contained in:
@@ -43,6 +43,34 @@ describe('#2665: live-config hermeticity guard', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('watches the HOME-derived fallback root, not only the ambient one', () => {
|
||||
// #2665 round 5: the guard resolves env-first, so it sees the AMBIENT root.
|
||||
// A child that blanks CLAUDE_CONFIG_DIR (which is exactly what TEST_ENV_BASE
|
||||
// does) falls back to <HOME>/.claude instead. Watching only the ambient path
|
||||
// leaves that fallback unwatched -- the escape route this PR closes, one
|
||||
// process deeper.
|
||||
const ambient = tmpRoot();
|
||||
const fakeHome = tmpRoot();
|
||||
const saved = process.env.CLAUDE_CONFIG_DIR;
|
||||
try {
|
||||
process.env.CLAUDE_CONFIG_DIR = ambient;
|
||||
const roots = resolveLiveConfigRoots({ os: { homedir: () => fakeHome } });
|
||||
assert.ok(
|
||||
roots.includes(path.resolve(ambient)),
|
||||
`ambient root missing from ${JSON.stringify(roots)}`,
|
||||
);
|
||||
assert.ok(
|
||||
roots.includes(path.resolve(path.join(fakeHome, '.claude'))),
|
||||
`HOME-derived fallback root missing from ${JSON.stringify(roots)}`,
|
||||
);
|
||||
} finally {
|
||||
if (saved === undefined) delete process.env.CLAUDE_CONFIG_DIR;
|
||||
else process.env.CLAUDE_CONFIG_DIR = saved;
|
||||
cleanup(ambient);
|
||||
cleanup(fakeHome);
|
||||
}
|
||||
});
|
||||
|
||||
test('a clean run produces no violations', () => {
|
||||
const root = tmpRoot();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user