fix: bind P1 acceptance evidence to reviewed runtime
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import {
|
||||
chmod, lstat, mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile,
|
||||
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";
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
canonicalIntegrationBase,
|
||||
CHECK_IDS,
|
||||
buildSafeEnvironment,
|
||||
collectRepositoryProvenance,
|
||||
installExternalFetchGuard,
|
||||
installNetworkGuard,
|
||||
installProductionSurfaceGuard,
|
||||
@@ -623,7 +624,7 @@ test("production executables ignore ambient THT and bind the generated tht entry
|
||||
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.sourceStatus, "git-index-byte-identical");
|
||||
assert.equal(executables.thtIdentity.entrypoint, "generated-console-script");
|
||||
assert.match(executables.thtIdentity.pythonPath, /python3(?:\.\d+)?$/);
|
||||
});
|
||||
@@ -690,10 +691,11 @@ test("public wrapper has no ambient command resolution and isolates the build an
|
||||
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 before a real wrapper integration", async () => {
|
||||
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"]) {
|
||||
@@ -702,9 +704,189 @@ test("hostile PATH Node npm and THT substitutes never execute before a real wrap
|
||||
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(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 execution cannot source ambient BASH_ENV or ENV", async () => {
|
||||
const root = await fakeRepository();
|
||||
const startup = join(root, "startup");
|
||||
const marker = join(root, "ambient-shell-ran");
|
||||
await writeFile(startup, `printf sourced > '${marker}'\n`);
|
||||
const wrapper = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "scripts", "p1-acceptance.sh");
|
||||
await assert.rejects(execFileAsync(wrapper, ["invalid"], { env: { ...process.env, BASH_ENV: startup, ENV: startup } }));
|
||||
await assert.rejects(lstat(marker));
|
||||
assert.match(await readFile(wrapper, "utf8"), /^#!\/usr\/bin\/env -S -u BASH_ENV -u ENV \/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/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user