fix: close P1 acceptance audit gaps

This commit is contained in:
2026-08-09 22:16:37 +02:00
parent 6b2fd71018
commit c1ca814440
4 changed files with 676 additions and 150 deletions
+159 -4
View File
@@ -8,6 +8,8 @@ import { dirname, join } from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
import { createServer, connect } from "node:net";
import dgram from "node:dgram";
import { Worker } from "node:worker_threads";
import test from "node:test";
import {
@@ -16,6 +18,8 @@ import {
buildSafeEnvironment,
installExternalFetchGuard,
installNetworkGuard,
installProductionSurfaceGuard,
resolveProductionExecutables,
negativeRequestEvidence,
cleanupOwnedRun,
createOwnedRun,
@@ -275,13 +279,17 @@ test("command helper accepts only executable plus separate argv", async () => {
await assert.rejects(runCommand({ executable: "/bin/echo", argv: [], shell: true }));
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 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");
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 result = await runCommand({ executable: "git", argv: ["--version"] });
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 result = await runCommand({ executable: gitPath, argv: ["--version"] });
assert.match(result.stdout, /^git version /);
assert.equal(result.code, 0);
});
@@ -293,8 +301,7 @@ test("safe environment rejects ambient THT and keeps only strict process allowli
fixture: { THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets" },
});
assert.deepEqual(safe, {
PATH: "/safe/bin", HOME: "/home/test", LANG: "C",
THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets",
LANG: "C", THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets",
});
});
@@ -434,3 +441,151 @@ test("secret scan fails closed when either expected Git repository is missing",
await assert.rejects(scanSecrets({ runRoot: run.root, forbiddenValues: ["CANARY-value-123"] }), new RegExp(`missing expected Git repository: ${missing.replace(".", "\\.")}`));
}
});
test("runIntegration fails closed when a later duplicate overwrites stale artifact evidence", async () => {
const repositoryRoot = await fakeRepository();
const checks = exactScenarios(async (id) => {
if (id === CHECK_IDS[0]) {
await mkdir(join(repositoryRoot, ".artifacts", "p1-integration", "scratch"), { recursive: true });
}
return { commands: [], artifacts: [] };
});
const result = await runIntegration({
repositoryRoot, keep: true,
setup: async (run, _repositoryRoot, _env, ctx) => {
const path = join(run.root, "logs", "overwritten.json");
await mkdir(dirname(path), { recursive: true });
await writeFile(path, "first");
const stale = { path: "logs/overwritten.json", sha256: "a7937b64b8caa58f03721bb6bacf9e92a2c78987f5d1692a065a4698e006c4ca" };
checks[0].run = async () => ({ commands: [], artifacts: [stale] });
checks[1].run = async () => {
await writeFile(path, "second");
return { commands: [], artifacts: [{ path: stale.path, sha256: "16367aacb67a4a017c8da8ab95682ccb389c61bb315f3425e2f2666f2476d1ce" }] };
};
return ctx;
},
checks,
});
assert.equal(result.exitCode, 1);
assert.equal(result.report.checks.length, 15);
assert(result.report.checks.every(({ status, artifacts }) => status === "FAIL" && artifacts.length === 0));
});
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 guard = installProductionSurfaceGuard({
...executables, runRoot: repositoryRoot, environment: { ...process.env }, originalFetch: globalThis.fetch,
});
try {
assert.throws(() => dgram.createSocket("udp4"), /prohibited production surface/);
assert.throws(() => new Worker("", { eval: true }), /prohibited production surface/);
await assert.rejects(runCommand({ executable: executables.gitPath, argv: ["ls-remote", "https://example.com/repo.git"] }), /Git command is prohibited/);
const childProcess = await import("node:child_process");
assert.throws(() => childProcess.spawn(executables.pythonPath, ["-c", "print('unexpected')"]), /child command is prohibited/);
assert.deepEqual(new Set(guard.events.filter(({ outcome }) => outcome === "REJECTED").map(({ surface }) => surface)),
new Set(["dgram", "worker_threads", "child_process"]));
} finally {
guard.restore();
}
});
test("listener close rejection retains listening truth and forces exact-15 FAIL", async () => {
const repositoryRoot = await fakeRepository();
const server = createServer();
await new Promise((resolvePromise, reject) => server.listen(0, "127.0.0.1", (error) => error ? reject(error) : resolvePromise()));
const address = server.address();
assert(address && typeof address === "object");
const result = await runIntegration({
repositoryRoot, keep: false, checks: exactScenarios(),
setup: async (run, _repositoryRoot, _env, ctx) => {
ctx.services = [{ name: "primary", baseUrl: `http://127.0.0.1:${address.port}`, app: { close: async () => { throw new Error("close rejected"); } } }];
const value = JSON.parse(await readFile(join(run.root, "ownership.json"), "utf8"));
value.listeners[0] = { name: "primary", kind: "fastify", host: "127.0.0.1", requestedPort: 0, actualPort: address.port, pid: process.pid, state: "listening" };
await writeFile(join(run.root, "ownership.json"), `${JSON.stringify(value, null, 2)}\n`);
return ctx;
},
});
assert.equal(result.exitCode, 1);
assert.equal(result.retained, true);
const owner = JSON.parse(await readFile(join(result.runRoot, "ownership.json"), "utf8"));
assert.notEqual(owner.listeners[0].state, "closed");
assert(result.report.checks.every(({ status }) => status === "FAIL"));
await new Promise((resolvePromise) => server.close(resolvePromise));
});
test("ownership close write failure forces retained exact-15 FAIL", async () => {
const repositoryRoot = await fakeRepository();
const server = createServer();
await new Promise((resolvePromise, reject) => server.listen(0, "127.0.0.1", (error) => error ? reject(error) : resolvePromise()));
const address = server.address();
assert(address && typeof address === "object");
const result = await runIntegration({
repositoryRoot, keep: false, checks: exactScenarios(),
ownershipWriter: async (_run, update) => { if (update?.state === "closed") throw new Error("owned write rejected"); },
setup: async (_run, _repositoryRoot, _env, ctx) => {
ctx.services = [{ name: "primary", baseUrl: `http://127.0.0.1:${address.port}`, app: { close: async () => await new Promise((resolvePromise) => server.close(resolvePromise)) } }];
return ctx;
},
});
assert.equal(result.exitCode, 1);
assert.equal(result.retained, true);
assert(result.report.checks.every(({ status }) => status === "FAIL"));
});
test("nested runIntegration is rejected before process-global mutation and outer restoration remains owned", async () => {
const repositoryRoot = await fakeRepository();
const originalFetch = globalThis.fetch;
const originalPath = process.env.PATH;
let nestedError;
const result = await runIntegration({
repositoryRoot, keep: true, checks: exactScenarios(),
setup: async (_run, _repositoryRoot, _env, ctx) => {
try { await runIntegration({ repositoryRoot, keep: true, checks: exactScenarios() }); } catch (error) { nestedError = error; }
assert.equal(globalThis.fetch, originalFetch);
assert.equal(process.env.PATH, originalPath);
return ctx;
},
});
assert.match(nestedError?.message ?? "", /already active/);
assert.equal(result.exitCode, 0);
assert.equal(globalThis.fetch, originalFetch);
assert.equal(process.env.PATH, originalPath);
});
test("environment tampering fails the audit and restores the caller environment", async () => {
const repositoryRoot = await fakeRepository();
const before = { ...process.env };
const checks = exactScenarios(async (id) => {
if (id === CHECK_IDS[0]) process.env.P1_ACCEPTANCE_UNOWNED = "tampered";
return { commands: [], artifacts: [] };
});
const result = await runIntegration({
repositoryRoot, keep: true, checks,
setup: async (_run, _repositoryRoot, _env, ctx) => { ctx.env = { P1_ACCEPTANCE_OWNED: "yes" }; return ctx; },
});
assert.equal(result.exitCode, 1);
assert(result.report.checks.every(({ status }) => status === "FAIL"));
assert.deepEqual({ ...process.env }, before);
});
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 originalFetch = globalThis.fetch;
const guard = installProductionSurfaceGuard({ ...executables, runRoot: repositoryRoot, 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);
});