fix: seal P1 acceptance process boundaries
This commit is contained in:
@@ -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));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user