Files
msd-core/tests/live-config-guard.test.cjs
0xdhx a02462e050 test(#2665): fail the suite when it writes into a live config dir
The recurrence guard, and #2665's own "Optional hardening". This class is silent
by construction: TEST_ENV_BASE cannot see an in-process caller, and CI cannot see
the class at all because CI never has these env vars set. It damages the
developer's machine and reports nothing -- which is how two prior authors each
diagnosed it and fixed only the instance in front of them.

run-tests.cjs snapshots GSD's install footprint in every live runtime config dir
before the suite and re-checks it after, failing the run on a create or a modify.
Roots come from the product's own getGlobalConfigDir, so the guard watches
wherever the product actually points, including through an ambient var.

Scope is ownership-based, not whole-root: the top-level install footprint plus
gsd-prefixed children of dirs GSD shares with the host agent. A config root like
~/.claude is shared, and watching it wholesale would false-positive on the host's
own history.jsonl or settings.json -- a guard that cries wolf gets disabled, and
then catches nothing. The prefix test is load-bearing: the first version watched
only the three top-level entries and MISSED a real leak into skills/gsd-*.

It earned its place immediately -- it is what found the fifth in-process leak in
runtime-artifact-layout.test.cjs, which no amount of reading the review would have
surfaced. Known gap documented in the module: a write to a file GSD does not own
is out of scope by construction.

Lives in scripts/, deliberately NOT scripts/lib/ -- the installer copies that dir
into every user's config dir wholesale while uninstall removes only an allowlist,
so a test-only module there would ship to users and survive uninstall.

Addresses review finding: Minor 8.
2026-08-08 05:50:17 -05:00

161 lines
6.0 KiB
JavaScript

'use strict';
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const {
GSD_OWNED_ENTRIES,
resolveLiveConfigRoots,
snapshotLiveConfig,
diffLiveConfig,
formatViolations,
} = require('../scripts/live-config-guard.cjs');
const { cleanup } = require('./helpers.cjs');
function tmpRoot() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'live-config-guard-'));
}
describe('#2665: live-config hermeticity guard', () => {
test('resolves real runtime config roots via the product resolver', () => {
const roots = resolveLiveConfigRoots();
// Guards the guard: an empty set would make every downstream assertion
// vacuous, and run-tests.cjs would silently skip the check.
assert.ok(roots.length > 5, `expected many runtime config roots, got ${roots.length}`);
for (const root of roots) {
assert.ok(path.isAbsolute(root), `root must be absolute: ${root}`);
}
});
test('a clean run produces no violations', () => {
const root = tmpRoot();
try {
fs.mkdirSync(path.join(root, 'gsd-core', 'bin'), { recursive: true });
fs.writeFileSync(path.join(root, 'gsd-core', 'bin', 'x.cjs'), 'x');
const before = snapshotLiveConfig([root]);
const after = snapshotLiveConfig([root]);
assert.deepStrictEqual(diffLiveConfig(before, after), []);
} finally {
cleanup(root);
}
});
test('detects a global install CREATED during the run', () => {
const root = tmpRoot();
try {
const before = snapshotLiveConfig([root]);
// Exactly the Blocker 1 shape: an in-process install(true, …) landing a
// full global install in a live config dir that was previously empty.
fs.mkdirSync(path.join(root, 'gsd-core'), { recursive: true });
fs.writeFileSync(path.join(root, 'gsd-file-manifest.json'), '{}');
const violations = diffLiveConfig(before, snapshotLiveConfig([root]));
const kinds = Object.fromEntries(violations.map((v) => [path.basename(v.path), v.kind]));
assert.strictEqual(kinds['gsd-core'], 'created');
assert.strictEqual(kinds['gsd-file-manifest.json'], 'created');
} finally {
cleanup(root);
}
});
test('detects an existing install MODIFIED during the run', () => {
const root = tmpRoot();
try {
const target = path.join(root, 'gsd-core', 'bin');
fs.mkdirSync(target, { recursive: true });
const file = path.join(target, 'gsd-tools.cjs');
fs.writeFileSync(file, 'original');
const before = snapshotLiveConfig([root]);
// mtime resolution is coarse on some filesystems; set it forward explicitly
// rather than racing the clock with a sleep.
const future = new Date(Date.now() + 10000);
fs.writeFileSync(file, 'clobbered');
fs.utimesSync(file, future, future);
const violations = diffLiveConfig(before, snapshotLiveConfig([root]));
assert.strictEqual(violations.length, 1);
assert.strictEqual(violations[0].kind, 'modified');
assert.strictEqual(path.basename(violations[0].path), 'gsd-core');
} finally {
cleanup(root);
}
});
test('ignores non-GSD writes in a shared config root', () => {
const root = tmpRoot();
try {
const before = snapshotLiveConfig([root]);
// A concurrent host-agent session writing its own state must NOT trip the
// guard — a guard that cries wolf gets disabled, and then catches nothing.
fs.writeFileSync(path.join(root, 'history.jsonl'), '{}');
fs.mkdirSync(path.join(root, 'todos'), { recursive: true });
fs.writeFileSync(path.join(root, 'settings.json'), '{}');
assert.deepStrictEqual(diffLiveConfig(before, snapshotLiveConfig([root])), []);
} finally {
cleanup(root);
}
});
test('watches exactly the GSD-owned entry set', () => {
const root = tmpRoot();
try {
const snap = snapshotLiveConfig([root]);
const watched = Object.keys(snap).map((p) => path.basename(p)).sort();
assert.deepStrictEqual(watched, [...GSD_OWNED_ENTRIES].sort());
} finally {
cleanup(root);
}
});
test('detects a gsd-prefixed artifact written into a SHARED dir', () => {
const root = tmpRoot();
try {
fs.mkdirSync(path.join(root, 'skills'), { recursive: true });
const before = snapshotLiveConfig([root]);
// The exact leak the first version of this guard MISSED: a writer that
// sandboxed HOME but inherited an ambient CLAUDE_CONFIG_DIR landed
// <live>/skills/gsd-dev-preferences/SKILL.md, outside the three
// top-level GSD entries.
fs.mkdirSync(path.join(root, 'skills', 'gsd-dev-preferences'), { recursive: true });
fs.writeFileSync(path.join(root, 'skills', 'gsd-dev-preferences', 'SKILL.md'), '# x');
const violations = diffLiveConfig(before, snapshotLiveConfig([root]));
assert.strictEqual(violations.length, 1);
assert.strictEqual(violations[0].kind, 'created');
assert.strictEqual(path.basename(violations[0].path), 'gsd-dev-preferences');
} finally {
cleanup(root);
}
});
test('ignores NON-gsd artifacts in a shared dir', () => {
const root = tmpRoot();
try {
fs.mkdirSync(path.join(root, 'skills'), { recursive: true });
const before = snapshotLiveConfig([root]);
// The host agent's own skills must not trip the guard.
fs.mkdirSync(path.join(root, 'skills', 'my-personal-skill'), { recursive: true });
fs.writeFileSync(path.join(root, 'skills', 'my-personal-skill', 'SKILL.md'), '# mine');
assert.deepStrictEqual(diffLiveConfig(before, snapshotLiveConfig([root])), []);
} finally {
cleanup(root);
}
});
test('the report names the path and the remedy', () => {
const out = formatViolations([{ path: '/live/.claude/gsd-core', kind: 'created' }]);
assert.match(out, /HERMETICITY FAILURE/);
assert.match(out, /\/live\/\.claude\/gsd-core/);
assert.match(out, /scrubConfigLocationEnv/);
assert.match(out, /GSD_SKIP_LIVE_CONFIG_GUARD/);
});
});