959 lines
51 KiB
JavaScript
959 lines
51 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import {
|
|
chmod, cp, lstat, mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile,
|
|
} from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
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 {
|
|
canonicalIntegrationBase,
|
|
CHECK_IDS,
|
|
buildSafeEnvironment,
|
|
collectRepositoryProvenance,
|
|
installExternalFetchGuard,
|
|
installNetworkGuard,
|
|
installProductionSurfaceGuard,
|
|
resolveProductionExecutables,
|
|
negativeRequestEvidence,
|
|
cleanupOwnedRun,
|
|
createOwnedRun,
|
|
deriveOverall,
|
|
executeChecks,
|
|
exportArchiveEvidencePath,
|
|
readAndValidateOwnership,
|
|
runCommand,
|
|
runIntegration,
|
|
scalarSecretBytes,
|
|
scanSecrets,
|
|
validateReport,
|
|
validateRunRoot,
|
|
} from "./p1-acceptance.mjs";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const roots = [];
|
|
async function fakeRepository() {
|
|
const root = await mkdtemp(join(tmpdir(), "p1 acceptance repository with spaces-"));
|
|
roots.push(root);
|
|
await mkdir(join(root, ".artifacts", "p1-integration"), { recursive: true });
|
|
return await realpath(root);
|
|
}
|
|
|
|
test.afterEach(async () => {
|
|
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
});
|
|
|
|
test("run roots are only canonical direct integration children", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const base = canonicalIntegrationBase(repositoryRoot);
|
|
const id = `p1-${"a".repeat(32)}`;
|
|
assert.equal(validateRunRoot(repositoryRoot, join(base, id), id), join(base, id));
|
|
for (const candidate of [
|
|
base,
|
|
join(repositoryRoot, ".artifacts", "manual-acceptance", id),
|
|
join(base, id, "nested"),
|
|
join(base, "foreign"),
|
|
join(dirname(base), id),
|
|
]) assert.throws(() => validateRunRoot(repositoryRoot, candidate, id));
|
|
assert.throws(() => validateRunRoot(repositoryRoot, join(base, `p1-${"A".repeat(32)}`), `p1-${"A".repeat(32)}`));
|
|
});
|
|
|
|
test("cleanup refuses every unowned or ambiguous root", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const base = canonicalIntegrationBase(repositoryRoot);
|
|
const cases = [
|
|
["missing ownership", async (run) => rm(join(run.root, "ownership.json"))],
|
|
["malformed ownership", async (run) => writeFile(join(run.root, "ownership.json"), "{")],
|
|
["mismatched root", async (run) => {
|
|
const value = JSON.parse(await readFile(join(run.root, "ownership.json"), "utf8"));
|
|
value.root = join(base, `p1-${"b".repeat(32)}`);
|
|
await writeFile(join(run.root, "ownership.json"), JSON.stringify(value));
|
|
}],
|
|
["mismatched pid", async (run) => {
|
|
const value = JSON.parse(await readFile(join(run.root, "ownership.json"), "utf8"));
|
|
value.pid += 1;
|
|
await writeFile(join(run.root, "ownership.json"), JSON.stringify(value));
|
|
}],
|
|
["wrong resource list", async (run) => {
|
|
const value = JSON.parse(await readFile(join(run.root, "ownership.json"), "utf8"));
|
|
value.resources.push(join(repositoryRoot, "foreign"));
|
|
await writeFile(join(run.root, "ownership.json"), JSON.stringify(value));
|
|
}],
|
|
];
|
|
for (const [, mutate] of cases) {
|
|
const run = await createOwnedRun({ repositoryRoot });
|
|
await mutate(run);
|
|
await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce }));
|
|
assert.equal((await lstat(run.root)).isDirectory(), true);
|
|
}
|
|
const wrongNonce = await createOwnedRun({ repositoryRoot });
|
|
await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: wrongNonce.root, expectedNonce: "0".repeat(64) }));
|
|
const symlinkRun = await createOwnedRun({ repositoryRoot });
|
|
const target = `${symlinkRun.root}-target`;
|
|
await rm(symlinkRun.root, { recursive: true });
|
|
await mkdir(target);
|
|
await symlink(target, symlinkRun.root);
|
|
await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: symlinkRun.root, expectedNonce: symlinkRun.nonce }));
|
|
for (const bad of [base, join(repositoryRoot, ".artifacts", "manual-acceptance"), join(base, "foreign")]) {
|
|
await assert.rejects(cleanupOwnedRun({ repositoryRoot, runRoot: bad, expectedNonce: "0".repeat(64) }));
|
|
}
|
|
});
|
|
|
|
test("cleanup atomically removes one owned root and preserves siblings", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const run = await createOwnedRun({ repositoryRoot });
|
|
const sibling = join(canonicalIntegrationBase(repositoryRoot), `p1-${"c".repeat(32)}`);
|
|
await mkdir(sibling);
|
|
await writeFile(join(sibling, "sentinel"), "foreign");
|
|
await readAndValidateOwnership({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce });
|
|
await cleanupOwnedRun({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce });
|
|
await assert.rejects(lstat(run.root));
|
|
assert.equal(await readFile(join(sibling, "sentinel"), "utf8"), "foreign");
|
|
});
|
|
|
|
function resultFor(id) {
|
|
return {
|
|
id, status: "PASS", startedAt: "2026-08-09T00:00:00.000Z",
|
|
finishedAt: "2026-08-09T00:00:01.000Z", commands: ["git"],
|
|
artifacts: [{ path: `logs/${id}.json`, sha256: "a".repeat(64) }],
|
|
};
|
|
}
|
|
function validReport(checks = CHECK_IDS.map(resultFor)) {
|
|
return {
|
|
schemaVersion: 1, runId: `p1-${"d".repeat(32)}`, startedAt: "2026-08-09T00:00:00.000Z",
|
|
finishedAt: "2026-08-09T00:00:02.000Z", command: "p1-acceptance integration --keep",
|
|
overall: deriveOverall(checks), checks,
|
|
};
|
|
}
|
|
|
|
test("report validation enforces uniqueness, derivation, safe evidence, hashes, times, and commands", () => {
|
|
assert.doesNotThrow(() => validateReport(validReport()));
|
|
const mutations = [
|
|
(r) => r.checks.push(structuredClone(r.checks[0])),
|
|
(r) => { r.checks[0].attempt = 1; },
|
|
(r) => { r.checks[0].artifacts[0].path = "../secret"; },
|
|
(r) => { r.checks[0].artifacts[0].sha256 = "bad"; },
|
|
(r) => { r.checks[0].startedAt = "today"; },
|
|
(r) => { r.checks[0].commands = ["git status"]; },
|
|
(r) => { r.overall = "PASS"; r.checks[0].status = "FAIL"; },
|
|
(r) => { r.nested = { retries: 2 }; },
|
|
];
|
|
for (const mutate of mutations) {
|
|
const report = validReport(); mutate(report); assert.throws(() => validateReport(report));
|
|
}
|
|
});
|
|
|
|
function exactScenarios(run = async () => ({ commands: [], artifacts: [] })) {
|
|
return CHECK_IDS.map((id) => ({ id, run: () => run(id) }));
|
|
}
|
|
|
|
test("injected failure executes once, retains a complete ordered diagnostic report, and returns nonzero", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const calls = [];
|
|
const failAt = CHECK_IDS[3];
|
|
const result = await runIntegration({
|
|
repositoryRoot, keep: false, failAt,
|
|
checks: exactScenarios(async (id) => { calls.push(id); return { commands: [], artifacts: [] }; }),
|
|
});
|
|
assert.equal(result.exitCode, 1);
|
|
assert.deepEqual(calls, CHECK_IDS.slice(0, 4));
|
|
assert.equal((await lstat(result.runRoot)).isDirectory(), true);
|
|
const report = JSON.parse(await readFile(join(result.runRoot, "report.json"), "utf8"));
|
|
assert.deepEqual(report.checks.map(({ id }) => id), CHECK_IDS);
|
|
assert.equal(report.checks.filter((check) => check.status === "FAIL").length, CHECK_IDS.length - 3);
|
|
assert.equal(report.checks[3].error, "Acceptance scenario failed safely.");
|
|
assert.equal(report.checks[4].error, "Not executed after earlier failure.");
|
|
});
|
|
|
|
test("failed scenario retains partial request and response evidence with observed commands", async () => {
|
|
const partial = {
|
|
commands: ["git"],
|
|
artifacts: [
|
|
{ path: "requests/partial.json", sha256: "a".repeat(64) },
|
|
{ path: "responses/partial.json", sha256: "b".repeat(64) },
|
|
],
|
|
};
|
|
const checks = exactScenarios(async (id) => {
|
|
if (id === CHECK_IDS[4]) {
|
|
const error = new Error("HTTP scenario failed after response persistence");
|
|
error.acceptancePartial = partial;
|
|
throw error;
|
|
}
|
|
return {};
|
|
});
|
|
const results = await executeChecks({ checks });
|
|
assert.deepEqual(results[4].commands, partial.commands);
|
|
assert.deepEqual(results[4].artifacts, partial.artifacts);
|
|
assert.equal(results[4].error, "Acceptance scenario failed safely.");
|
|
});
|
|
|
|
test("executeChecks never repeats or executes after first failure but emits the exact check set", async () => {
|
|
const calls = new Map();
|
|
const result = await executeChecks({
|
|
checks: exactScenarios(async (id) => { calls.set(id, (calls.get(id) ?? 0) + 1); return {}; }),
|
|
failAt: CHECK_IDS[1],
|
|
});
|
|
assert.deepEqual(result.map(({ id }) => id), CHECK_IDS);
|
|
assert.deepEqual(Object.fromEntries(calls), Object.fromEntries(CHECK_IDS.slice(0, 2).map((id) => [id, 1])));
|
|
assert.equal(result[1].status, "FAIL");
|
|
assert(result.slice(2).every(({ status, error }) => status === "FAIL" && error === "Not executed after earlier failure."));
|
|
assert.throws(() => validateReport(validReport(CHECK_IDS.slice(0, -1).map(resultFor))));
|
|
await assert.rejects(executeChecks({ checks: exactScenarios().reverse() }));
|
|
});
|
|
|
|
test("owned setup failure still writes one safe result for every exact check", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const result = await runIntegration({
|
|
repositoryRoot, keep: false,
|
|
setup: async () => { throw new Error("fixture setup raw failure"); },
|
|
});
|
|
assert.equal(result.exitCode, 1);
|
|
const report = JSON.parse(await readFile(join(result.runRoot, "report.json"), "utf8"));
|
|
assert.deepEqual(report.checks.map(({ id }) => id), CHECK_IDS);
|
|
assert.equal(report.checks[0].error, "Acceptance setup failed safely.");
|
|
assert(report.checks.slice(1).every(({ error }) => error === "Not executed after earlier failure."));
|
|
});
|
|
|
|
test("scalar fixture secret files contain no harness-invalid whitespace", () => {
|
|
const bytes = scalarSecretBytes("CANARY-secret-value-123456");
|
|
assert.equal(bytes.toString("utf8"), "CANARY-secret-value-123456");
|
|
assert.equal([...bytes].some((byte) => /\s/.test(String.fromCharCode(byte))), false);
|
|
assert.throws(() => scalarSecretBytes("bad secret"));
|
|
});
|
|
|
|
test("secret scanner excludes only the direct fixture-secrets subtree", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const run = await createOwnedRun({ repositoryRoot });
|
|
const canary = "CANARY-secret-value-123456";
|
|
await mkdir(join(run.root, "fixture-secrets"));
|
|
await writeFile(join(run.root, "fixture-secrets", "allowed"), canary);
|
|
const paths = [
|
|
"logs/a.log", "responses/a.json", "rendered/a.yaml", "exports/raw/a.zip",
|
|
"exports/extracted/a.md", "requests/a.json", "report-preview.md", "nested/fixture-secrets/not-excluded",
|
|
];
|
|
for (const path of paths) {
|
|
await mkdir(dirname(join(run.root, path)), { recursive: true });
|
|
await writeFile(join(run.root, path), `prefix ${canary} suffix`);
|
|
}
|
|
const findings = await scanSecrets({ runRoot: run.root, forbiddenValues: [canary], expectedGitRepositories: [] });
|
|
assert.deepEqual(new Set(findings.map((finding) => finding.path)), new Set(paths));
|
|
});
|
|
|
|
test("secret scanner examines reachable Git blobs, not just loose file bytes", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const run = await createOwnedRun({ repositoryRoot });
|
|
const canary = "GIT-CANARY-secret-value-987654";
|
|
const gitRoot = join(run.root, "author");
|
|
await mkdir(gitRoot);
|
|
await execFileAsync("git", ["init", "--initial-branch=main"], { cwd: gitRoot });
|
|
await execFileAsync("git", ["config", "user.name", "Scanner Test"], { cwd: gitRoot });
|
|
await execFileAsync("git", ["config", "user.email", "scanner@example.invalid"], { cwd: gitRoot });
|
|
await writeFile(join(gitRoot, "secret.txt"), canary);
|
|
await execFileAsync("git", ["add", "secret.txt"], { cwd: gitRoot });
|
|
await execFileAsync("git", ["commit", "-m", "secret blob"], { cwd: gitRoot });
|
|
await execFileAsync("git", ["rm", "secret.txt"], { cwd: gitRoot });
|
|
await execFileAsync("git", ["commit", "-m", "remove worktree copy"], { cwd: gitRoot });
|
|
const findings = await scanSecrets({ runRoot: run.root, forbiddenValues: [canary], expectedGitRepositories: ["author"] });
|
|
assert.equal(findings.some((finding) => finding.path.startsWith("git-object:")), true);
|
|
});
|
|
|
|
test("successful lifecycle honors keep and cleanup", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const checks = exactScenarios();
|
|
const kept = await runIntegration({ repositoryRoot, keep: true, checks });
|
|
assert.equal(kept.exitCode, 0);
|
|
assert.equal((await lstat(kept.runRoot)).isDirectory(), true);
|
|
const cleaned = await runIntegration({ repositoryRoot, keep: false, checks });
|
|
assert.equal(cleaned.exitCode, 0);
|
|
await assert.rejects(lstat(cleaned.runRoot));
|
|
});
|
|
|
|
test("command helper accepts only executable plus separate argv", async () => {
|
|
await assert.rejects(runCommand("git status"));
|
|
await assert.rejects(runCommand({ executable: "/bin/echo", argv: "hello" }));
|
|
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 invalid/);
|
|
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 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);
|
|
});
|
|
|
|
test("raw runCommand rejects a configured clean filter before exact Git add", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const content = join(repositoryRoot, "workspace-content");
|
|
const helper = join(repositoryRoot, "clean-helper");
|
|
const marker = join(repositoryRoot, "clean-helper-ran");
|
|
await execFileAsync("/usr/bin/git", ["init", "--initial-branch=main"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.name", "P1 Fixture Curator"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.email", "p1-curator@example.invalid"], { cwd: repositoryRoot });
|
|
await mkdir(content);
|
|
await writeFile(join(content, "guide.md"), "content\n");
|
|
await writeFile(join(repositoryRoot, ".gitattributes"), "workspace-content/** filter=bad\n");
|
|
await writeFile(helper, `#!/bin/sh\nprintf ran > '${marker}'\ncat\n`, { mode: 0o700 });
|
|
await execFileAsync("/usr/bin/git", ["config", "filter.bad.clean", `'${helper}'`], { cwd: repositoryRoot });
|
|
|
|
const { gitPath } = await resolveProductionExecutables({
|
|
repositoryRoot: await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", "..")),
|
|
});
|
|
await assert.rejects(
|
|
runCommand({ executable: gitPath, argv: ["add", "workspace-content"], cwd: repositoryRoot, env: process.env }),
|
|
/unsafe Git repository state/,
|
|
);
|
|
await assert.rejects(lstat(marker));
|
|
});
|
|
|
|
test("raw runCommand rejects a diff driver textconv before exact Git show", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const marker = join(repositoryRoot, "textconv-helper-ran");
|
|
const helper = join(repositoryRoot, "textconv-helper");
|
|
await execFileAsync("/usr/bin/git", ["init", "--initial-branch=main"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.name", "P1 Fixture Curator"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.email", "p1-curator@example.invalid"], { cwd: repositoryRoot });
|
|
await writeFile(join(repositoryRoot, ".gitattributes"), "file diff=evil\n");
|
|
await execFileAsync("/usr/bin/git", ["add", ".gitattributes"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["commit", "-m", "attributes"], { cwd: repositoryRoot });
|
|
await writeFile(join(repositoryRoot, "file"), "v1\n");
|
|
await execFileAsync("/usr/bin/git", ["add", "file"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["commit", "-m", "v1"], { cwd: repositoryRoot });
|
|
await writeFile(join(repositoryRoot, "file"), "v2\n");
|
|
await execFileAsync("/usr/bin/git", ["add", "file"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["commit", "-m", "v2"], { cwd: repositoryRoot });
|
|
await writeFile(helper, `#!/bin/sh\nprintf ran > '${marker}'\nexit 0\n`, { mode: 0o700 });
|
|
await execFileAsync("/usr/bin/git", ["config", "diff.evil.textconv", `'${helper}'`], { cwd: repositoryRoot });
|
|
const emptyHooks = join(repositoryRoot, "registry", "locks", "empty-hooks");
|
|
await mkdir(emptyHooks, { recursive: true });
|
|
|
|
const { gitPath } = await resolveProductionExecutables({
|
|
repositoryRoot: await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", "..")),
|
|
});
|
|
await assert.rejects(
|
|
runCommand({ executable: gitPath, argv: ["-c", `core.hooksPath=${emptyHooks}`, "show", "HEAD"], cwd: repositoryRoot, env: process.env }),
|
|
/unsafe Git repository state/,
|
|
);
|
|
await assert.rejects(lstat(marker));
|
|
});
|
|
|
|
|
|
test("safe environment rejects ambient THT and keeps only strict process allowlist plus fixture values", () => {
|
|
const safe = buildSafeEnvironment({
|
|
ambient: { PATH: "/safe/bin", HOME: "/home/test", LANG: "C", THT_SECRETS_FILE: "/real/secrets", AWS_SECRET_ACCESS_KEY: "real" },
|
|
fixture: { THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets" },
|
|
});
|
|
assert.deepEqual(safe, {
|
|
LANG: "C", THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets",
|
|
});
|
|
});
|
|
|
|
test("secret scan fails closed when Git enumeration fails", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const run = await createOwnedRun({ repositoryRoot });
|
|
await mkdir(join(run.root, "remote.git"));
|
|
await assert.rejects(scanSecrets({ runRoot: run.root, forbiddenValues: ["CANARY-value-123"] }), /Git secret scan failed closed/);
|
|
});
|
|
|
|
|
|
test("negative request evidence persists only case label and expected input field", () => {
|
|
const value = negativeRequestEvidence("credential-field", "evidence.source.password");
|
|
assert.deepEqual(value, { case: "credential-field", expectedInputField: "evidence.source.password" });
|
|
assert.equal(JSON.stringify(value).includes("body"), false);
|
|
});
|
|
|
|
test("external fetch guard permits only the owned loopback API and records external attempts", async () => {
|
|
const called = [];
|
|
const guard = installExternalFetchGuard("http://127.0.0.1:12345", async (url) => { called.push(String(url)); return { ok: true }; });
|
|
await guard.fetch("http://127.0.0.1:12345/workspaces");
|
|
await assert.rejects(guard.fetch("https://evidence.example.test/guide.md"), /external fetch prohibited/);
|
|
await assert.rejects(guard.fetch("http://127.0.0.1:9999/health"), /external fetch prohibited/);
|
|
assert.deepEqual(called, ["http://127.0.0.1:12345/workspaces"]);
|
|
assert.equal(guard.externalAttempts.length, 2);
|
|
});
|
|
|
|
|
|
test("export archive evidence path matches the persisted binary request id", () => {
|
|
assert.equal(exportArchiveEvidencePath("export-p1-filesystem"), "exports/raw/export-p1-filesystem.zip");
|
|
});
|
|
|
|
|
|
test("announce callback observes PASS and manual pending before non-keep cleanup", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
let observed;
|
|
const result = await runIntegration({
|
|
repositoryRoot, keep: false, checks: exactScenarios(),
|
|
announce: async ({ report, runRoot }) => {
|
|
observed = { overall: report.overall, manual: "PENDING", rootExists: (await lstat(runRoot)).isDirectory() };
|
|
},
|
|
});
|
|
assert.deepEqual(observed, { overall: "PASS", manual: "PENDING", rootExists: true });
|
|
assert.equal(result.retained, false);
|
|
});
|
|
|
|
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=\(\/usr\/bin\/env -i/);
|
|
assert.doesNotMatch(wrapper, /P1_ACCEPTANCE_FAIL_AT|LANG|LC_ALL|TZ/);
|
|
assert.doesNotMatch(wrapper, /export THT_BIN/);
|
|
});
|
|
|
|
|
|
test("network guard is installed globally, rejects non-loopback sockets, and permits one owned listener", async () => {
|
|
const server = createServer((socket) => socket.end("ok"));
|
|
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 guard = installNetworkGuard();
|
|
try {
|
|
guard.addOwnedOrigin(`http://127.0.0.1:${address.port}`);
|
|
const contents = await new Promise((resolvePromise, reject) => {
|
|
const socket = connect({ host: "127.0.0.1", port: address.port });
|
|
let value = "";
|
|
socket.setEncoding("utf8");
|
|
socket.on("data", (chunk) => { value += chunk; });
|
|
socket.on("end", () => resolvePromise(value));
|
|
socket.on("error", reject);
|
|
});
|
|
assert.equal(contents, "ok");
|
|
assert.throws(() => connect({ host: "example.com", port: 80 }), /external network connection prohibited/);
|
|
await assert.rejects(globalThis.fetch("https://example.com/"), /external network connection prohibited/);
|
|
assert.equal(guard.externalAttempts.length, 2);
|
|
} finally {
|
|
guard.restore();
|
|
await new Promise((resolvePromise) => server.close(resolvePromise));
|
|
}
|
|
});
|
|
|
|
test("report validation rejects duplicate artifact paths across checks", () => {
|
|
const report = validReport();
|
|
report.checks[1].artifacts[0].path = report.checks[0].artifacts[0].path;
|
|
assert.throws(() => validateReport(report), /report artifact path is duplicated/);
|
|
});
|
|
|
|
test("virtual report leakage yields a minimal sanitized exact-15 FAIL report", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const canary = "VIRTUAL-CANARY-12345678";
|
|
const checks = exactScenarios(async (id) => ({
|
|
commands: [],
|
|
artifacts: id === CHECK_IDS[0] ? [{ path: `logs/${canary}.json`, sha256: "a".repeat(64) }] : [],
|
|
}));
|
|
const result = await runIntegration({
|
|
repositoryRoot,
|
|
checks,
|
|
setup: async (_run, _repositoryRoot, _env, ctx) => {
|
|
ctx.forbiddenValues = [canary];
|
|
return ctx;
|
|
},
|
|
});
|
|
assert.equal(result.exitCode, 1);
|
|
const bytes = await readFile(join(result.runRoot, "report.json"));
|
|
assert.equal(bytes.includes(Buffer.from(canary)), false);
|
|
const report = JSON.parse(bytes);
|
|
assert.deepEqual(report.checks.map(({ id }) => id), CHECK_IDS);
|
|
assert(report.checks.every(({ status, commands, artifacts }) => status === "FAIL" && commands.length === 0 && artifacts.length === 0));
|
|
});
|
|
|
|
test("partial setup preserves forbidden values and never writes secret-bearing report bytes", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const canary = "PARTIAL-SETUP-CANARY-12345678";
|
|
const result = await runIntegration({
|
|
repositoryRoot,
|
|
setup: async (run, _repositoryRoot, _env, ctx) => {
|
|
ctx.forbiddenValues = [canary];
|
|
await mkdir(join(run.root, "logs"), { recursive: true });
|
|
await writeFile(join(run.root, "logs", "partial-setup.log"), canary);
|
|
throw new Error(`unsafe ${canary}`);
|
|
},
|
|
});
|
|
assert.equal(result.exitCode, 1);
|
|
const bytes = await readFile(join(result.runRoot, "report.json"));
|
|
assert.equal(bytes.includes(Buffer.from(canary)), false);
|
|
const report = JSON.parse(bytes);
|
|
assert.equal(report.checks.length, 15);
|
|
assert(report.checks.every(({ status }) => status === "FAIL"));
|
|
});
|
|
|
|
test("secret scan fails closed when either expected Git repository is missing", async () => {
|
|
for (const missing of ["remote.git", "author"]) {
|
|
const repositoryRoot = await fakeRepository();
|
|
const run = await createOwnedRun({ repositoryRoot });
|
|
const present = missing === "remote.git" ? "author" : "remote.git";
|
|
await mkdir(join(run.root, present));
|
|
await execFileAsync("git", present === "remote.git" ? ["init", "--bare", join(run.root, present)] : ["init", join(run.root, present)]);
|
|
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 runRoot = await fakeRepository();
|
|
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
|
|
const executables = await resolveProductionExecutables({ repositoryRoot });
|
|
const guard = installProductionSurfaceGuard({
|
|
...executables, runRoot, 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 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, 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, "git-index-byte-identical");
|
|
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"/);
|
|
assert.match(wrapper, /\/bin\/rm -rf -- "\$repo_root\/backend\/dist"/);
|
|
});
|
|
|
|
|
|
test("hostile PATH Node npm and THT substitutes never execute at the public wrapper boundary", 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");
|
|
await assert.rejects(execFileAsync(wrapper, ["invalid"], {
|
|
env: { ...process.env, PATH: hostileRoot, THT_BIN: join(hostileRoot, "tht") }, timeout: 30_000,
|
|
}));
|
|
await assert.rejects(lstat(marker));
|
|
});
|
|
|
|
|
|
async function fakeTrustedThtRepository() {
|
|
const repositoryRoot = await fakeRepository();
|
|
const realRepository = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
|
|
const harness = join(repositoryRoot, "harness");
|
|
const sourceRoot = join(harness, "tht");
|
|
await mkdir(harness, { recursive: true });
|
|
await cp(join(realRepository, "harness", "tht"), sourceRoot, {
|
|
recursive: true, filter: (path) => !path.split("/").includes("__pycache__") && !path.endsWith(".pyc"),
|
|
});
|
|
await cp(join(realRepository, "harness", "pyproject.toml"), join(harness, "pyproject.toml"));
|
|
const realExecutables = await resolveProductionExecutables({ repositoryRoot: realRepository });
|
|
const pythonName = realExecutables.thtIdentity.pythonPath.split("/").at(-1);
|
|
const venvBin = join(harness, ".venv", "bin");
|
|
const sitePackages = join(harness, ".venv", "lib", pythonName, "site-packages");
|
|
await mkdir(venvBin, { recursive: true });
|
|
await mkdir(sitePackages, { recursive: true });
|
|
await symlink(realExecutables.thtIdentity.pythonCanonicalPath, join(venvBin, "python"));
|
|
await symlink(realExecutables.thtIdentity.pythonCanonicalPath, join(venvBin, pythonName));
|
|
const entrypoint = `#!${join(venvBin, pythonName)}\nimport sys\nfrom tht.cli import app\nif __name__ == '__main__':\n if sys.argv[0].endswith('.exe'):\n sys.argv[0] = sys.argv[0][:-4]\n sys.exit(app())\n`;
|
|
await writeFile(join(venvBin, "tht"), entrypoint, { mode: 0o700 });
|
|
const realSite = join(realRepository, "harness", ".venv", "lib", pythonName, "site-packages");
|
|
const realFinderName = (await import("node:fs/promises")).readdir(realSite).then((entries) => entries.find((name) => /^__editable___tht_.*_finder\.py$/.test(name)));
|
|
const finderName = await realFinderName;
|
|
const realFinder = await readFile(join(realSite, finderName), "utf8");
|
|
const finder = realFinder.replaceAll(join(realRepository, "harness", "tht"), sourceRoot);
|
|
await writeFile(join(sitePackages, finderName), finder);
|
|
const moduleName = finderName.slice(0, -3);
|
|
await writeFile(join(sitePackages, "__editable__.tht-0.1.0.pth"), `import ${moduleName}; ${moduleName}.install()`);
|
|
await execFileAsync("/usr/bin/git", ["init", "--initial-branch=main"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.name", "P1 Test"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.email", "p1-test@example.invalid"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["add", "harness/tht", "harness/pyproject.toml"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["commit", "-m", "trusted source"], { cwd: repositoryRoot });
|
|
return { repositoryRoot, sourceRoot, sitePackages, finderName };
|
|
}
|
|
|
|
test("Git rejects configured upload-pack, clean filter, and hook state before exact allowed operations", async () => {
|
|
const repositoryRoot = await realpath(join(dirname(fileURLToPath(import.meta.url)), "..", ".."));
|
|
const runRoot = await fakeRepository();
|
|
const remote = join(runRoot, "remote.git");
|
|
const author = join(runRoot, "author");
|
|
await execFileAsync("/usr/bin/git", ["init", "--bare", "--initial-branch=main", remote]);
|
|
await execFileAsync("/usr/bin/git", ["init", "--initial-branch=main", author]);
|
|
await execFileAsync("/usr/bin/git", ["config", "user.name", "P1 Fixture Curator"], { cwd: author });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.email", "p1-curator@example.invalid"], { cwd: author });
|
|
await writeFile(join(author, "seed"), "seed\n");
|
|
await execFileAsync("/usr/bin/git", ["add", "seed"], { cwd: author });
|
|
await execFileAsync("/usr/bin/git", ["commit", "-m", "seed"], { cwd: author });
|
|
await execFileAsync("/usr/bin/git", ["remote", "add", "origin", remote], { cwd: author });
|
|
await execFileAsync("/usr/bin/git", ["push", "origin", "main"], { cwd: author });
|
|
const executables = await resolveProductionExecutables({ repositoryRoot });
|
|
const guard = installProductionSurfaceGuard({ ...executables, runRoot, environment: { ...process.env } });
|
|
try {
|
|
for (const [kind, configure, argv] of [
|
|
["upload", async (helper) => execFileAsync("/usr/bin/git", ["config", "remote.origin.uploadpack", helper], { cwd: author }), ["fetch", "origin", "main"]],
|
|
["filter", async (helper) => {
|
|
await mkdir(join(author, "workspace-content"), { recursive: true });
|
|
await writeFile(join(author, ".gitattributes"), "workspace-content/** filter=bad\n");
|
|
await execFileAsync("/usr/bin/git", ["config", "filter.bad.clean", helper], { cwd: author });
|
|
}, ["add", "workspace-content"]],
|
|
["hook", async (helper) => { await cp(helper, join(author, ".git", "hooks", "pre-commit")); }, ["commit", "-m", "Bootstrap curated P1 content"]],
|
|
]) {
|
|
await execFileAsync("/usr/bin/git", ["config", "--unset-all", "remote.origin.uploadpack"], { cwd: author }).catch(() => {});
|
|
await execFileAsync("/usr/bin/git", ["config", "--remove-section", "filter.bad"], { cwd: author }).catch(() => {});
|
|
await rm(join(author, ".gitattributes"), { force: true });
|
|
await rm(join(author, ".git", "hooks", "pre-commit"), { force: true });
|
|
const marker = join(runRoot, `${kind}-marker`);
|
|
const helper = join(runRoot, `${kind}-helper`);
|
|
await writeFile(helper, `#!/bin/sh\nprintf ran > '${marker}'\nexec /usr/bin/git-upload-pack \"$@\"\n`, { mode: 0o700 });
|
|
await configure(helper);
|
|
const before = guard.events.length;
|
|
await assert.rejects(runCommand({ executable: executables.gitPath, argv, cwd: author, env: { ...process.env } }), /unsafe Git repository state/);
|
|
assert.equal(guard.events.length - before, 1);
|
|
assert.equal(guard.events.at(-1).outcome, "REJECTED");
|
|
await assert.rejects(lstat(marker));
|
|
}
|
|
} finally { guard.restore(); }
|
|
});
|
|
|
|
test("trusted tht rejects executable finder code and Git-hidden source changes", async () => {
|
|
const maliciousFinder = await fakeTrustedThtRepository();
|
|
const finderPath = join(maliciousFinder.sitePackages, maliciousFinder.finderName);
|
|
await writeFile(finderPath, `open('${join(maliciousFinder.repositoryRoot, "finder-marker")}', 'w').write('ran')\n${await readFile(finderPath, "utf8")}`);
|
|
await assert.rejects(resolveProductionExecutables({ repositoryRoot: maliciousFinder.repositoryRoot }), /editable binding is invalid/);
|
|
|
|
const ignoredPyc = await fakeTrustedThtRepository();
|
|
await mkdir(join(ignoredPyc.sitePackages, "__pycache__"));
|
|
await writeFile(join(ignoredPyc.sitePackages, "__pycache__", `${ignoredPyc.finderName.slice(0, -3)}.cpython-313.pyc`), "malicious bytecode");
|
|
await assert.rejects(resolveProductionExecutables({ repositoryRoot: ignoredPyc.repositoryRoot }), /import startup override/);
|
|
|
|
const hiddenSource = await fakeTrustedThtRepository();
|
|
const sourcePath = join(hiddenSource.sourceRoot, "cli", "__init__.py");
|
|
await execFileAsync("/usr/bin/git", ["update-index", "--assume-unchanged", "harness/tht/cli/__init__.py"], { cwd: hiddenSource.repositoryRoot });
|
|
await writeFile(sourcePath, `${await readFile(sourcePath, "utf8")}\n# malicious hidden swap\n`);
|
|
await assert.rejects(resolveProductionExecutables({ repositoryRoot: hiddenSource.repositoryRoot }), /source bytes differ from Git/);
|
|
});
|
|
|
|
test("trusted tht guard rejects and records post-resolution entrypoint finder and source swaps at spawn", async () => {
|
|
for (const target of ["entrypoint", "finder", "source"]) {
|
|
const fixture = await fakeTrustedThtRepository();
|
|
const executables = await resolveProductionExecutables({ repositoryRoot: fixture.repositoryRoot });
|
|
const runRoot = await fakeRepository();
|
|
const configPath = join(runRoot, "rendered", "workspace.yaml");
|
|
await mkdir(dirname(configPath), { recursive: true });
|
|
await writeFile(configPath, "profile: acceptance\n");
|
|
const guard = installProductionSurfaceGuard({ ...executables, runRoot, environment: { ...process.env } });
|
|
try {
|
|
const path = target === "entrypoint" ? executables.thtPath
|
|
: target === "finder" ? join(fixture.sitePackages, fixture.finderName)
|
|
: join(fixture.sourceRoot, "cli", "__init__.py");
|
|
await writeFile(path, `${await readFile(path, "utf8")}\n# post-resolution swap\n`, target === "entrypoint" ? { mode: 0o700 } : undefined);
|
|
const childProcess = await import("node:child_process");
|
|
assert.throws(() => childProcess.execFile(executables.thtPath, ["config", "check", "-c", configPath], {
|
|
cwd: join(fixture.repositoryRoot, "harness"), env: { ...process.env },
|
|
}), /trusted THT identity changed/);
|
|
assert.equal(guard.events.at(-1).outcome, "REJECTED");
|
|
} finally { guard.restore(); }
|
|
}
|
|
});
|
|
|
|
test("secret scan fails closed on a recoverable symlink outside fixture-secrets", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const run = await createOwnedRun({ repositoryRoot });
|
|
const canary = "CANARY-symlink-secret-123456";
|
|
await mkdir(join(run.root, "fixture-secrets"));
|
|
await writeFile(join(run.root, "fixture-secrets", "token"), canary);
|
|
await mkdir(join(run.root, "responses"));
|
|
await symlink(join(run.root, "fixture-secrets", "token"), join(run.root, "responses", "leak"));
|
|
await assert.rejects(scanSecrets({ runRoot: run.root, forbiddenValues: [canary], expectedGitRepositories: [] }), /symlink outside fixture-secrets/);
|
|
});
|
|
|
|
test("direct public wrapper clears startup files and exported functions before Bash starts", async () => {
|
|
const root = await fakeRepository();
|
|
const bashStartup = join(root, "bash-startup");
|
|
const envStartup = join(root, "env-startup");
|
|
const bashMarker = join(root, "bash-env-ran");
|
|
const envMarker = join(root, "env-ran");
|
|
const functionMarker = join(root, "exported-function-ran");
|
|
await writeFile(bashStartup, `printf sourced > '${bashMarker}'\n`);
|
|
await writeFile(envStartup, `printf sourced > '${envMarker}'\n`);
|
|
const wrapper = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "scripts", "p1-acceptance.sh");
|
|
await assert.rejects(execFileAsync(wrapper, ["invalid"], {
|
|
env: {
|
|
...process.env,
|
|
BASH_ENV: bashStartup,
|
|
ENV: envStartup,
|
|
"BASH_FUNC_cd%%": `() { printf function > '${functionMarker}'; builtin cd "$@"; }`,
|
|
},
|
|
}));
|
|
for (const marker of [bashMarker, envMarker, functionMarker]) await assert.rejects(lstat(marker));
|
|
assert.match(await readFile(wrapper, "utf8"), /^#!\/usr\/bin\/env -S -i PATH=\/usr\/bin:\/bin \/bin\/bash\n/);
|
|
});
|
|
|
|
test("final listener ownership state is a declared hash-bound report artifact", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
const result = await runIntegration({ repositoryRoot, keep: true, checks: exactScenarios() });
|
|
const artifact = result.report.checks.flatMap(({ artifacts }) => artifacts).find(({ path }) => path === "logs/final-ownership.json");
|
|
assert(artifact);
|
|
const bytes = await readFile(join(result.runRoot, artifact.path));
|
|
const { createHash } = await import("node:crypto");
|
|
assert.equal(createHash("sha256").update(bytes).digest("hex"), artifact.sha256);
|
|
const value = JSON.parse(bytes);
|
|
assert.deepEqual(value.listeners.map(({ state }) => state), ["not_started", "not_started"]);
|
|
});
|
|
|
|
test("repository provenance binds clean HEAD tree and backend source/dist manifests and rejects dirty state", async () => {
|
|
const repositoryRoot = await fakeRepository();
|
|
await mkdir(join(repositoryRoot, "backend", "src"), { recursive: true });
|
|
await mkdir(join(repositoryRoot, "backend", "scripts"), { recursive: true });
|
|
await mkdir(join(repositoryRoot, "backend", "dist"), { recursive: true });
|
|
await writeFile(join(repositoryRoot, "backend", "src", "app.ts"), "export const value = 1;\n");
|
|
await writeFile(join(repositoryRoot, "backend", "scripts", "p1-acceptance.mjs"), "export {};\n");
|
|
await writeFile(join(repositoryRoot, "backend", "dist", "app.js"), "export const value = 1;\n");
|
|
await writeFile(join(repositoryRoot, "backend", "package.json"), "{}\n");
|
|
await writeFile(join(repositoryRoot, "backend", "package-lock.json"), "{}\n");
|
|
await writeFile(join(repositoryRoot, "backend", "tsconfig.json"), "{}\n");
|
|
await execFileAsync("/usr/bin/git", ["init", "--initial-branch=main"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.name", "P1 Test"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["config", "user.email", "p1-test@example.invalid"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["add", "backend"], { cwd: repositoryRoot });
|
|
await execFileAsync("/usr/bin/git", ["commit", "-m", "clean tree"], { cwd: repositoryRoot });
|
|
const provenance = await collectRepositoryProvenance({ repositoryRoot, gitPath: "/usr/bin/git" });
|
|
assert.match(provenance.head, /^[0-9a-f]{40}$/);
|
|
assert.match(provenance.tree, /^[0-9a-f]{40}$/);
|
|
assert.equal(provenance.clean, true);
|
|
assert.equal(provenance.backendSource.files.some(({ path }) => path === "src/app.ts"), true);
|
|
assert.equal(provenance.backendDist.files.some(({ path }) => path === "dist/app.js"), true);
|
|
await writeFile(join(repositoryRoot, "backend", "src", "app.ts"), "dirty\n");
|
|
await assert.rejects(collectRepositoryProvenance({ repositoryRoot, gitPath: "/usr/bin/git" }), /repository is not clean/);
|
|
});
|