fix: seal P1 acceptance process boundaries

This commit is contained in:
2026-08-09 22:51:56 +02:00
parent a3129b8b81
commit 7d8fb065b5
4 changed files with 480 additions and 142 deletions
+139 -20
View File
@@ -280,15 +280,13 @@ test("command helper accepts only executable plus separate argv", async () => {
await assert.rejects(runCommand({ executable: "git status; rm -rf /", argv: [] }));
await assert.rejects(runCommand({ executable: "/tmp/git", argv: ["--version"] }), /command executable is not allowlisted/);
await assert.rejects(runCommand({ executable: "tht", argv: ["config", "check"] }), /command executable is invalid/);
const repositoryRoot = await fakeRepository();
const executable = join(repositoryRoot, "executable with spaces");
const scratchRoot = await fakeRepository();
const executable = join(scratchRoot, "executable with spaces");
await writeFile(executable, "#!/bin/sh\nprintf '%s' \"$1\"\n", { mode: 0o700 });
await chmod(executable, 0o700);
await assert.rejects(runCommand({ executable, argv: ["literal;not-a-shell"] }), /command executable is not allowlisted/);
const tht = join(repositoryRoot, "harness", ".venv", "bin", "tht");
await mkdir(dirname(tht), { recursive: true });
await writeFile(tht, "#!/bin/sh\nexit 0\n", { mode: 0o700 });
const { gitPath } = await resolveProductionExecutables({ repositoryRoot, thtBin: tht, ambientPath: process.env.PATH });
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
const { gitPath } = await resolveProductionExecutables({ repositoryRoot });
const result = await runCommand({ executable: gitPath, argv: ["--version"] });
assert.match(result.stdout, /^git version /);
assert.equal(result.code, 0);
@@ -350,7 +348,7 @@ test("announce callback observes PASS and manual pending before non-keep cleanup
test("public wrapper replaces ambient environment before invoking the runner", async () => {
const wrapper = await readFile(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "scripts", "p1-acceptance.sh"), "utf8");
assert.match(wrapper, /safe_env=\(env -i/);
assert.match(wrapper, /safe_env=\(\/usr\/bin\/env -i/);
assert.match(wrapper, /P1_ACCEPTANCE_FAIL_AT/);
assert.doesNotMatch(wrapper, /export THT_BIN/);
});
@@ -473,14 +471,11 @@ test("runIntegration fails closed when a later duplicate overwrites stale artifa
});
test("production surface guard rejects and records UDP, Worker, git ls-remote, and unexpected python", async () => {
const repositoryRoot = await fakeRepository();
const thtPath = join(repositoryRoot, "harness", ".venv", "bin", "tht");
await mkdir(dirname(thtPath), { recursive: true });
await writeFile(thtPath, "#!/bin/sh\nexit 0\n", { mode: 0o700 });
await chmod(thtPath, 0o700);
const executables = await resolveProductionExecutables({ repositoryRoot, thtBin: thtPath, ambientPath: process.env.PATH });
const runRoot = await fakeRepository();
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
const executables = await resolveProductionExecutables({ repositoryRoot });
const guard = installProductionSurfaceGuard({
...executables, runRoot: repositoryRoot, environment: { ...process.env }, originalFetch: globalThis.fetch,
...executables, runRoot, environment: { ...process.env }, originalFetch: globalThis.fetch,
});
try {
assert.throws(() => dgram.createSocket("udp4"), /prohibited production surface/);
@@ -576,16 +571,140 @@ test("environment tampering fails the audit and restores the caller environment"
});
test("production guard detects global tampering and restores without stranding patches", async () => {
const repositoryRoot = await fakeRepository();
const thtPath = join(repositoryRoot, "harness", ".venv", "bin", "tht");
await mkdir(dirname(thtPath), { recursive: true });
await writeFile(thtPath, "#!/bin/sh\nexit 0\n", { mode: 0o700 });
const executables = await resolveProductionExecutables({ repositoryRoot, thtBin: thtPath });
const runRoot = await fakeRepository();
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
const executables = await resolveProductionExecutables({ repositoryRoot });
const originalFetch = globalThis.fetch;
const guard = installProductionSurfaceGuard({ ...executables, runRoot: repositoryRoot, environment: { ...process.env }, originalFetch });
const guard = installProductionSurfaceGuard({ ...executables, runRoot, environment: { ...process.env }, originalFetch });
globalThis.fetch = originalFetch;
assert.throws(() => guard.restore(), /ownership restoration failed/);
assert.equal(globalThis.fetch, originalFetch);
const childProcess = await import("node:child_process");
assert.doesNotThrow(() => childProcess.spawn);
});
test("Git grammar rejects helper, config, alias, and network-capable spellings with one event each", async () => {
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
const runRoot = await fakeRepository();
const executables = await resolveProductionExecutables({ repositoryRoot, thtBin: "/tmp/hostile-tht" });
const guard = installProductionSurfaceGuard({ ...executables, runRoot, environment: { ...process.env } });
const source = join(runRoot, "source.git");
const destination = join(runRoot, "destination");
const marker = join(runRoot, "helper-ran");
await execFileAsync(executables.gitPath, ["init", "--bare", source]);
const helper = join(runRoot, "upload-helper");
await writeFile(helper, `#!/bin/sh\nprintf ran > "${marker}"\nexit 99\n`, { mode: 0o700 });
const prohibited = [
["clone", `--upload-pack=${helper}`, source, destination],
["clone", "--receive-pack=/tmp/helper", source, destination],
["--exec-path=/tmp", "status"],
["-c", "alias.status=!touch /tmp/pwn", "status"],
["-c", "core.hooksPath=/tmp/hooks", "status"],
["-c", "diff.external=/tmp/helper", "status"],
["config", "filter.bad.clean", "/tmp/helper"],
["ls-remote", "https://example.com/repo.git"],
];
try {
for (const argv of prohibited) {
const before = guard.events.length;
await assert.rejects(runCommand({ executable: executables.gitPath, argv }), /Git command is prohibited/);
assert.equal(guard.events.length - before, 1);
assert.equal(guard.events.at(-1).outcome, "REJECTED");
}
await assert.rejects(lstat(marker));
} finally { guard.restore(); }
});
test("production executables ignore ambient THT and bind the generated tht entrypoint to reviewed source", async () => {
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
const hostile = join(await fakeRepository(), "tht");
await writeFile(hostile, "#!/bin/sh\nexit 0\n", { mode: 0o700 });
const executables = await resolveProductionExecutables({ repositoryRoot, thtBin: hostile });
assert.equal(executables.thtPath, join(repositoryRoot, "harness", ".venv", "bin", "tht"));
assert.equal(executables.thtIdentity.sourceRoot, join(repositoryRoot, "harness", "tht"));
assert.equal(executables.thtIdentity.sourceStatus, "tracked-clean");
assert.equal(executables.thtIdentity.entrypoint, "generated-console-script");
assert.match(executables.thtIdentity.pythonPath, /python3(?:\.\d+)?$/);
});
test("tht accepts only config check for one owned rendered yaml", async () => {
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
const runRoot = await fakeRepository();
const rendered = join(runRoot, "rendered", "workspace.yaml");
await mkdir(dirname(rendered), { recursive: true });
await writeFile(rendered, "profile: acceptance\n");
const executables = await resolveProductionExecutables({ repositoryRoot });
const guard = installProductionSurfaceGuard({ ...executables, runRoot, environment: { ...process.env } });
const childProcess = await import("node:child_process");
try {
for (const argv of [
["config", "check"], ["config", "check", "-c", "/tmp/unowned.yaml"],
["doctor"], ["config", "check", "-c", rendered, "--extra"],
]) {
const before = guard.events.length;
assert.throws(() => childProcess.execFile(executables.thtPath, argv), /THT command is prohibited/);
assert.equal(guard.events.length - before, 1);
}
} finally { guard.restore(); }
});
test("production guard installation rolls back every patch and owner on every injected patch failure", async () => {
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
const runRoot = await fakeRepository();
const executables = await resolveProductionExecutables({ repositoryRoot });
const childProcess = await import("node:child_process");
const originalSpawn = childProcess.spawn;
const originalDgram = dgram.createSocket;
const originalFetch = globalThis.fetch;
for (let failPatchAt = 1; failPatchAt <= 12; failPatchAt += 1) {
assert.throws(() => installProductionSurfaceGuard({
...executables, runRoot, environment: { ...process.env }, failPatchAt,
}), /injected production patch failure/);
assert.equal(childProcess.spawn, originalSpawn);
assert.equal(dgram.createSocket, originalDgram);
assert.equal(globalThis.fetch, originalFetch);
const reacquired = installProductionSurfaceGuard({ ...executables, runRoot, environment: { ...process.env } });
reacquired.restore();
}
});
test("command bounds reject zero, negative, fractional, and nonnumeric timeouts with one sanitized event", async () => {
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
const runRoot = await fakeRepository();
const executables = await resolveProductionExecutables({ repositoryRoot });
const guard = installProductionSurfaceGuard({ ...executables, runRoot, environment: { ...process.env } });
try {
for (const timeoutMs of [0, -1, 1.5, NaN]) {
const before = guard.events.length;
await assert.rejects(runCommand({ executable: executables.gitPath, argv: ["--version"], timeoutMs }), /command bounds are invalid/);
assert.equal(guard.events.length - before, 1);
}
} finally { guard.restore(); }
});
test("public wrapper has no ambient command resolution and isolates the build and runner", async () => {
const wrapper = await readFile(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "scripts", "p1-acceptance.sh"), "utf8");
assert.doesNotMatch(wrapper, /command\s+-v/);
assert.doesNotMatch(wrapper, /\b(?:node|npm)\s+--prefix/);
assert.match(wrapper, /env -i/);
assert.match(wrapper, /npm-cli\.js/);
assert.match(wrapper, /"\$node_path" "\$npm_path"/);
});
test("hostile PATH Node npm and THT substitutes never execute before a real wrapper integration", async () => {
const hostileRoot = await fakeRepository();
const marker = join(hostileRoot, "ambient-tool-ran");
for (const name of ["node", "npm", "tht"]) {
const path = join(hostileRoot, name);
await writeFile(path, `#!/bin/sh\nprintf '%s' '${name}' >> '${marker}'\nexit 97\n`, { mode: 0o700 });
await chmod(path, 0o700);
}
const wrapper = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "scripts", "p1-acceptance.sh");
const { stdout } = await execFileAsync(wrapper, ["integration"], {
env: { ...process.env, PATH: hostileRoot, THT_BIN: join(hostileRoot, "tht") }, timeout: 120_000, maxBuffer: 4 * 1024 * 1024,
});
assert.match(stdout, /automated integration: PASS/);
await assert.rejects(lstat(marker));
});