fix: bind P1 acceptance evidence to reviewed runtime

This commit is contained in:
2026-08-09 23:36:36 +02:00
parent 96362929d7
commit 0cfe5c7c9a
3 changed files with 562 additions and 61 deletions
+371 -53
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env node
import { createHash, randomBytes } from "node:crypto";
import {
accessSync, closeSync, constants as fsConstants, existsSync, fsyncSync, lstatSync, openSync, realpathSync, statSync,
accessSync, closeSync, constants as fsConstants, existsSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync,
openSync, readFileSync, readdirSync, realpathSync, statSync,
} from "node:fs";
import {
access, chmod, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, symlink, writeFile,
@@ -228,14 +229,135 @@ function resolveTrustedSystemExecutableSync(name) {
throw new Error(`cannot resolve trusted system executable: ${name}`);
}
const THT_EDITABLE_FINDER_NORMALIZED_SHA256 = "3489b09b63511e27e1ae4d3f858d2bc576fe77beb5ea97607673781a32d2723e";
function assertRegularNonSymlink(path, label) {
let entry;
try { entry = lstatSync(path); } catch { throw new Error(`${label} is unavailable`); }
if (!entry.isFile() || entry.isSymbolicLink() || realpathSync(path) !== path) throw new Error(`${label} identity is invalid`);
return entry;
}
function assertSafeSitePackages(identity) {
const entries = readdirSync(identity.sitePackages, { withFileTypes: true });
const pthNames = entries.filter(({ name }) => name.endsWith(".pth")).map(({ name }) => name);
const finderNames = entries.filter(({ name }) => /^__editable___tht_.*_finder\.py$/.test(name)).map(({ name }) => name);
if (pthNames.length !== 1 || pthNames[0] !== identity.pthName || finderNames.length !== 1
|| finderNames[0] !== identity.finderName || entries.some((entry) => entry.isSymbolicLink())) {
throw new Error("trusted THT editable binding is ambiguous");
}
const startup = /^(?:sitecustomize|usercustomize|tht)(?:\..*)?$/;
if (entries.some(({ name }) => startup.test(name))) throw new Error("trusted THT editable binding has an import startup override");
const cache = join(identity.sitePackages, "__pycache__");
if (existsSync(cache) && readdirSync(cache).some((name) => startup.test(name)
|| /^__editable___tht_.*_finder\..*\.pyc$/.test(name))) {
throw new Error("trusted THT editable binding has an import startup override");
}
assertRegularNonSymlink(identity.pthPath, "trusted THT editable pth");
assertRegularNonSymlink(identity.finderPath, "trusted THT editable finder");
const pth = readFileSync(identity.pthPath, "utf8");
const rawFinder = readFileSync(identity.finderPath, "utf8");
const finder = rawFinder.replaceAll("\r\n", "\n");
const occurrences = finder.split(identity.sourceRoot).length - 1;
const normalized = finder.replaceAll(identity.sourceRoot, "<SOURCE_ROOT>");
if (pth !== identity.expectedPth || occurrences !== 5 || sha256(normalized) !== THT_EDITABLE_FINDER_NORMALIZED_SHA256) {
throw new Error("trusted THT editable binding is invalid");
}
return { pth, finder: rawFinder };
}
function sourceTreeFilesSync(root, current = root, files = []) {
for (const entry of readdirSync(current, { withFileTypes: true })) {
const path = join(current, entry.name);
if (entry.isSymbolicLink()) throw new Error("trusted THT source contains a symlink");
if (entry.isDirectory()) sourceTreeFilesSync(root, path, files);
else if (entry.isFile()) files.push(relative(root, path).split(sep).join("/"));
else throw new Error("trusted THT source contains a special file");
}
return files;
}
function assertBoundThtFiles(identity) {
assertRegularNonSymlink(identity.entrypointPath, "trusted THT entrypoint");
assertRegularNonSymlink(identity.pythonCanonicalPath, "trusted THT interpreter");
if (realpathSync(identity.pythonPath) !== identity.pythonCanonicalPath) throw new Error("trusted THT identity changed: interpreter");
if (sha256(readFileSync(identity.entrypointPath)) !== identity.entrypointSha256
|| sha256(readFileSync(identity.pythonCanonicalPath)) !== identity.pythonSha256) {
throw new Error("trusted THT identity changed: entrypoint or interpreter");
}
for (const root of [dirname(identity.sourceRoot), dirname(identity.entrypointPath)]) {
for (const name of ["sitecustomize.py", "sitecustomize.pyc", "usercustomize.py", "usercustomize.pyc", "tht.py", "tht.pyc"]) {
if (existsSync(join(root, name))) throw new Error("trusted THT identity changed: import startup override");
}
}
const { pth, finder } = assertSafeSitePackages(identity);
if (sha256(pth) !== identity.pthSha256 || sha256(finder) !== identity.editableFinderSha256) {
throw new Error("trusted THT identity changed: editable binding");
}
const actualPaths = sourceTreeFilesSync(identity.sourceRoot).map((path) => `harness/tht/${path}`);
actualPaths.push("harness/pyproject.toml");
actualPaths.sort();
const expectedPaths = identity.sourceManifest.map(({ path }) => path).sort();
if (JSON.stringify(actualPaths) !== JSON.stringify(expectedPaths)) throw new Error("trusted THT identity changed: source manifest");
for (const file of identity.sourceManifest) {
const path = join(identity.repositoryRoot, ...file.path.split("/"));
assertRegularNonSymlink(path, "trusted THT source file");
if (sha256(readFileSync(path)) !== file.sha256) throw new Error("trusted THT identity changed: source bytes");
}
}
export function revalidateThtIdentity(identity) {
try { assertBoundThtFiles(identity); } catch (error) {
if (/^trusted THT identity changed/.test(error.message)) throw error;
throw new Error(`trusted THT identity changed: ${error.message}`);
}
return true;
}
async function gitTrackedSourceManifest(repo, gitPath) {
const listing = await runCommand({
executable: gitPath,
argv: ["-C", repo, "ls-files", "-s", "-z", "--", "harness/tht", "harness/pyproject.toml"],
env: baseSafeGitEnvironment(gitPath),
});
const treeListing = await runCommand({
executable: gitPath,
argv: ["-C", repo, "ls-tree", "-r", "-z", "HEAD", "--", "harness/tht", "harness/pyproject.toml"],
env: baseSafeGitEnvironment(gitPath),
});
const treeEntries = new Map(treeListing.stdout.split("\0").filter(Boolean).map((record) => {
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t(harness\/(?:tht\/[^\0]+|pyproject\.toml))$/.exec(record);
if (!match) throw new Error("trusted THT Git tree identity is invalid");
return [match[3], { mode: match[1], oid: match[2] }];
}));
const manifest = [];
for (const record of listing.stdout.split("\0").filter(Boolean)) {
const match = /^(100644|100755) ([0-9a-f]{40,64}) 0\t(harness\/(?:tht\/[^\0]+|pyproject\.toml))$/.exec(record);
if (!match) throw new Error("trusted THT Git index identity is invalid");
const [, mode, oid, path] = match;
const tree = treeEntries.get(path);
if (!tree || tree.mode !== mode || tree.oid !== oid) throw new Error(`trusted THT Git index differs from HEAD tree: ${path}`);
treeEntries.delete(path);
const blob = await runCommand({ executable: gitPath, argv: ["-C", repo, "cat-file", "blob", oid], env: baseSafeGitEnvironment(gitPath) });
const bytes = Buffer.from(blob.stdout);
const worktreePath = join(repo, ...path.split("/"));
assertRegularNonSymlink(worktreePath, "trusted THT source file");
if (!bytes.equals(readFileSync(worktreePath))) throw new Error(`trusted THT source bytes differ from Git: ${path}`);
manifest.push({ path, mode, gitBlob: oid, sha256: sha256(bytes) });
}
if (treeEntries.size !== 0) throw new Error("trusted THT Git index differs from HEAD tree");
manifest.sort((left, right) => left.path.localeCompare(right.path));
if (!manifest.some(({ path }) => path === "harness/pyproject.toml")
|| !manifest.some(({ path }) => path === "harness/tht/cli/__init__.py")) throw new Error("trusted THT tracked source manifest is incomplete");
return manifest;
}
export async function resolveProductionExecutables({ repositoryRoot } = {}) {
const repo = canonicalRoot(repositoryRoot);
const gitPath = resolveTrustedSystemExecutableSync("git");
const pythonPath = resolveTrustedSystemExecutableSync("python3");
const thtPath = join(repo, "harness", ".venv", "bin", "tht");
let entry;
try { entry = lstatSync(thtPath); } catch { throw new Error("trusted THT executable is unavailable"); }
if (!entry.isFile() || entry.isSymbolicLink() || realpathSync(thtPath) !== thtPath) throw new Error("trusted THT entrypoint identity is invalid");
assertRegularNonSymlink(thtPath, "trusted THT entrypoint");
accessSync(thtPath, fsConstants.X_OK);
const entrypointBytes = await readFile(thtPath, "utf8");
const lines = entrypointBytes.replaceAll("\r\n", "\n").split("\n");
@@ -253,41 +375,163 @@ export async function resolveProductionExecutables({ repositoryRoot } = {}) {
const sourceRoot = join(repo, "harness", "tht");
const pyproject = await readFile(join(repo, "harness", "pyproject.toml"), "utf8");
if (!/^tht\s*=\s*["']tht\.cli:app["']$/m.test(pyproject)) throw new Error("trusted THT console-script declaration is invalid");
const status = await runCommand({
executable: gitPath,
argv: ["-C", repo, "status", "--porcelain=v1", "--untracked-files=all", "--", "harness/tht", "harness/pyproject.toml"],
env: {
PATH: dirname(gitPath), HOME: "/nonexistent", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null",
GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "core.fsmonitor", GIT_CONFIG_VALUE_0: "false",
},
});
if (status.stdout !== "") throw new Error("trusted THT source is not tracked and clean");
const pythonVersion = basename(pythonLexical);
const sitePackages = join(repo, "harness", ".venv", "lib", pythonVersion, "site-packages");
const siteEntries = await readdir(sitePackages);
const allPthFiles = siteEntries.filter((name) => name.endsWith(".pth"));
const pthFiles = allPthFiles.filter((name) => /^__editable__\.tht-.*\.pth$/.test(name));
const finderFiles = siteEntries.filter((name) => /^__editable___tht_.*_finder\.py$/.test(name));
if (pthFiles.length !== 1 || allPthFiles.length !== 1 || finderFiles.length !== 1
|| siteEntries.some((name) => /^(?:sitecustomize|usercustomize|tht)\.py$/.test(name) || name === "tht")) {
throw new Error("trusted THT editable binding is ambiguous");
}
const pth = await readFile(join(sitePackages, pthFiles[0]), "utf8");
const finder = await readFile(join(sitePackages, finderFiles[0]), "utf8");
const finderModule = finderFiles[0].slice(0, -3);
if (pth.trim() !== `import ${finderModule}; ${finderModule}.install()`
|| !finder.includes(`MAPPING: dict[str, str] = {'tht': '${sourceRoot}'}`)
|| /\b(?:subprocess|socket|requests|httpx|urllib|multiprocessing)\b|\bos\.system\b|\bPopen\b/.test(finder)) {
throw new Error("trusted THT editable binding is invalid");
}
return {
gitPath, pythonPath, thtPath,
thtIdentity: {
entrypoint: "generated-console-script", entrypointSha256: sha256(entrypointBytes),
pythonPath: pythonLexical, pythonCanonicalPath: realpathSync(pythonLexical),
sourceRoot, sourceStatus: "tracked-clean", editableFinderSha256: sha256(finder),
},
const pthNames = siteEntries.filter((name) => name.endsWith(".pth"));
const finderNames = siteEntries.filter((name) => /^__editable___tht_.*_finder\.py$/.test(name));
if (pthNames.length !== 1 || finderNames.length !== 1) throw new Error("trusted THT editable binding is ambiguous");
const finderModule = finderNames[0].slice(0, -3);
const identity = {
repositoryRoot: repo, entrypoint: "generated-console-script", entrypointPath: thtPath,
entrypointSha256: sha256(entrypointBytes), pythonPath: pythonLexical,
pythonCanonicalPath: realpathSync(pythonLexical), pythonSha256: sha256(await readFile(realpathSync(pythonLexical))),
sourceRoot, sourceStatus: "git-index-byte-identical", sitePackages,
pthName: pthNames[0], pthPath: join(sitePackages, pthNames[0]), expectedPth: `import ${finderModule}; ${finderModule}.install()`,
finderName: finderNames[0], finderPath: join(sitePackages, finderNames[0]),
};
const binding = assertSafeSitePackages(identity);
identity.pthSha256 = sha256(binding.pth);
identity.editableFinderSha256 = sha256(binding.finder);
identity.sourceManifest = await gitTrackedSourceManifest(repo, gitPath);
assertBoundThtFiles(identity);
return { gitPath, pythonPath, thtPath, thtIdentity: identity };
}
let fallbackGitHooksPath;
function ownedFallbackGitHooksPath() {
if (!fallbackGitHooksPath) fallbackGitHooksPath = mkdtempSync(join(tmpdir(), `p1-git-hooks-${process.pid}-`));
return fallbackGitHooksPath;
}
function gitSafeConfig(hooksPath) {
return [
["core.hooksPath", hooksPath], ["core.attributesFile", "/dev/null"], ["core.fsmonitor", "false"],
["core.pager", "/bin/cat"], ["pager.status", "false"], ["diff.external", ""],
["interactive.diffFilter", ""], ["commit.gpgSign", "false"], ["tag.gpgSign", "false"],
["user.signingKey", ""], ["gpg.program", "/bin/false"], ["credential.helper", ""],
["core.askPass", "/bin/false"], ["sequence.editor", "/bin/false"], ["core.editor", "/bin/false"],
["protocol.allow", "never"], ["protocol.file.allow", "always"], ["protocol.ext.allow", "never"],
];
}
function hardenedGitArgv(argv, hooksPath) {
return [...gitSafeConfig(hooksPath).flatMap(([key, value]) => ["-c", `${key}=${value}`]), ...argv];
}
function baseSafeGitEnvironment(gitPath) {
return {
PATH: dirname(gitPath), HOME: "/nonexistent", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_SYSTEM: "/dev/null",
GIT_CONFIG_GLOBAL: "/dev/null", GIT_ATTR_NOSYSTEM: "1", GIT_TERMINAL_PROMPT: "0",
GIT_ASKPASS: "/bin/false", SSH_ASKPASS: "/bin/false", GIT_ALLOW_PROTOCOL: "file",
GIT_PROTOCOL_FROM_USER: "0", GIT_PAGER: "/bin/cat",
};
}
function assertEmptyHooksDirectory(path) {
let entry;
try { entry = lstatSync(path); } catch { throw new Error("unsafe Git repository state: hooks directory is unavailable"); }
if (!entry.isDirectory() || entry.isSymbolicLink() || realpathSync(path) !== path || readdirSync(path).length !== 0) {
throw new Error("unsafe Git repository state: hooks directory is not owned and empty");
}
}
function parseLocalGitConfig(bytes) {
let section;
const entries = [];
for (const raw of bytes.replaceAll("\r\n", "\n").split("\n")) {
const line = raw.trim();
if (!line || line.startsWith("#") || line.startsWith(";")) continue;
const sectionMatch = /^\[([A-Za-z0-9.-]+)(?:\s+"([^"\\]*)")?\]$/.exec(line);
if (sectionMatch) { section = sectionMatch[2] ? `${sectionMatch[1].toLowerCase()}.${sectionMatch[2]}` : sectionMatch[1].toLowerCase(); continue; }
const valueMatch = /^([A-Za-z0-9.-]+)\s*=\s*(.*)$/.exec(line);
if (!section || !valueMatch || /[\\\0]/.test(valueMatch[2])) throw new Error("unsafe Git repository state: local config is malformed");
entries.push([`${section}.${valueMatch[1].toLowerCase()}`, valueMatch[2]]);
}
return entries;
}
function safeLocalGitConfigEntry(key, value, runRoot) {
if (key === "core.repositoryformatversion") return value === "0";
if (["core.filemode", "core.bare", "core.logallrefupdates", "core.ignorecase", "core.precomposeunicode"].includes(key)) return /^(?:true|false)$/.test(value);
if (key === "remote.origin.url") return ownedGitPath(value, runRoot);
if (key === "remote.origin.fetch") return /^\+refs\/heads\/(?:\*|main|invalid-context):refs\/remotes\/origin\/(?:\*|main|invalid-context)$/.test(value);
if (/^branch\.(?:main|invalid-context)\.remote$/.test(key)) return value === "origin";
if (/^branch\.(?:main|invalid-context)\.merge$/.test(key)) return /^refs\/heads\/(?:main|invalid-context)$/.test(value);
if (key === "user.name") return FIXTURE_GIT_CONFIG.get("user.name")?.has(value) === true;
if (key === "user.email") return FIXTURE_GIT_CONFIG.get("user.email")?.has(value) === true;
return false;
}
function repositoryGitDirectory(argv, cwd, runRoot) {
let candidate;
if (argv[0] === "--git-dir") candidate = argv[1];
else if (argv[0] === "-C") candidate = join(argv[1], ".git");
else if (cwd) candidate = join(cwd, ".git");
if (!candidate || !ownedGitPath(resolve(candidate), runRoot) || !existsSync(candidate)) return undefined;
const entry = lstatSync(candidate);
if (entry.isFile() && !entry.isSymbolicLink()) {
const match = /^gitdir: (.+)\n?$/.exec(readFileSync(candidate, "utf8"));
if (!match) throw new Error("unsafe Git repository state: gitdir file is malformed");
candidate = resolve(dirname(candidate), match[1]);
} else if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error("unsafe Git repository state: git directory is unsafe");
return realpathSync(candidate);
}
function findAttributes(current, gitDirectory, findings = []) {
for (const entry of readdirSync(current, { withFileTypes: true })) {
const path = join(current, entry.name);
if (path === gitDirectory || (entry.name === ".git" && (entry.isDirectory() || entry.isFile()))) continue;
if (entry.isSymbolicLink()) throw new Error("unsafe Git repository state: worktree contains a symlink");
if (entry.isDirectory()) findAttributes(path, gitDirectory, findings);
else if (entry.name === ".gitattributes") findings.push(path);
}
return findings;
}
function validateGitDirectoryState(gitDirectory, runRoot) {
const configPath = join(gitDirectory, "config");
const configEntry = lstatSync(configPath);
if (!configEntry.isFile() || configEntry.isSymbolicLink()) throw new Error("unsafe Git repository state: local config is unsafe");
const entries = parseLocalGitConfig(readFileSync(configPath, "utf8"));
if (entries.some(([key, value]) => !safeLocalGitConfigEntry(key, value, runRoot))) {
throw new Error("unsafe Git repository state: local config is not exact");
}
const infoAttributes = join(gitDirectory, "info", "attributes");
if (existsSync(infoAttributes)) {
const entry = lstatSync(infoAttributes);
if (!entry.isFile() || entry.isSymbolicLink() || readFileSync(infoAttributes).length !== 0) {
throw new Error("unsafe Git repository state: info attributes are not empty");
}
}
const hooks = join(gitDirectory, "hooks");
if (existsSync(hooks)) for (const entry of readdirSync(hooks, { withFileTypes: true })) {
if (entry.isSymbolicLink() || !entry.isFile() || !entry.name.endsWith(".sample")) {
throw new Error("unsafe Git repository state: repository hook is present");
}
}
const worktree = dirname(gitDirectory);
if (basename(gitDirectory) === ".git" && findAttributes(worktree, gitDirectory).length > 0) {
throw new Error("unsafe Git repository state: worktree attributes are present");
}
return entries;
}
function exactRemoteTarget(argv, entries) {
const offset = argv[0] === "-c" || argv[0] === "-C" || argv[0] === "--git-dir" ? 2 : 0;
const verb = argv[offset]; const args = argv.slice(offset + 1);
if (verb === "clone") {
const operands = args.filter((value) => value !== "--bare" && value !== "--single-branch" && value !== "--"
&& value !== "--branch" && value !== "main" && value !== "invalid-context");
return operands.at(-2);
}
if (["fetch", "push"].includes(verb) && args.includes("origin")) {
return entries.find(([key]) => key === "remote.origin.url")?.[1];
}
return undefined;
}
function assertSafeGitRepositoryState(argv, cwd, runRoot, fixedHooksPath) {
assertEmptyHooksDirectory(fixedHooksPath);
if (argv[0] === "-c") assertEmptyHooksDirectory(argv[1].slice("core.hooksPath=".length));
const gitDirectory = repositoryGitDirectory(argv, cwd, runRoot);
const entries = gitDirectory ? validateGitDirectoryState(gitDirectory, runRoot) : [];
const target = exactRemoteTarget(argv, entries);
if (target !== undefined) {
if (!ownedGitPath(target, runRoot) || !existsSync(join(target, "config"))) {
throw new Error("unsafe Git repository state: remote target is not exact and owned");
}
validateGitDirectoryState(realpathSync(target), runRoot);
}
}
const FIXTURE_GIT_CONFIG = new Map([
@@ -350,17 +594,20 @@ export function validateGitInvocation(argv, { runRoot } = {}) {
case "clean": valid = prefix === "hooks" && exactArray(args, ["-fd", "--", "workspaces", "workspace-docs"]); break;
case "status": valid = (!prefix && (exactArray(args, ["--porcelain=v1"]) || exactArray(args, ["--porcelain"])))
|| (prefix === "hooks" && exactArray(args, ["--porcelain"]))
|| (prefix === "-C" && exactArray(args, ["--porcelain=v1", "--untracked-files=all", "--", "harness/tht", "harness/pyproject.toml"])); break;
|| (prefix === "-C" && (exactArray(args, ["--porcelain=v1", "--untracked-files=all"])
|| exactArray(args, ["--porcelain=v1", "--untracked-files=all", "--", "harness/tht", "harness/pyproject.toml"]))); break;
case "ls-files": valid = prefix === "-C" && exactArray(args, ["-s", "-z", "--", "harness/tht", "harness/pyproject.toml"]); break;
case "write-tree": valid = !prefix && args.length === 0; break;
case "show-ref": valid = (!prefix || prefix === "--git-dir" || prefix === "hooks") && args.length === 0; break;
case "symbolic-ref": valid = (!prefix || prefix === "hooks") && exactArray(args, ["--short", "HEAD"]); break;
case "rev-list": valid = ((prefix === "--git-dir" || prefix === "-C") && exactArray(args, ["--objects", "--all"]))
|| (prefix === "hooks" && exactArray(args, ["--left-right", "--count", "HEAD...@{upstream}"])); break;
case "ls-tree": valid = prefix === "hooks" && exactArray(args, ["-r", "--name-only", "HEAD", "--", "workspaces"]); break;
case "ls-tree": valid = (prefix === "hooks" && exactArray(args, ["-r", "--name-only", "HEAD", "--", "workspaces"]))
|| (prefix === "-C" && exactArray(args, ["-r", "-z", "HEAD", "--", "harness/tht", "harness/pyproject.toml"])); break;
case "cat-file": valid = (!prefix || prefix === "--git-dir" || prefix === "-C" || prefix === "hooks") && args.length === 2
&& ((args[0] === "-e" || args[0] === "-t" || args[0] === "blob") && object(args[1])); break;
case "show": valid = prefix === "hooks" && args.length === 1 && object(args[0]); break;
case "rev-parse": valid = (!prefix || prefix === "--git-dir" || prefix === "hooks") && args.length === 1 && object(args[0]); break;
case "rev-parse": valid = (!prefix || prefix === "--git-dir" || prefix === "hooks" || prefix === "-C") && args.length === 1 && object(args[0]); break;
default: valid = false;
}
if (!valid) throw new Error("Git command is prohibited");
@@ -421,7 +668,10 @@ export function installProductionSurfaceGuard({
if (productionSurfaceOwner) throw new Error("production surface guard is already active");
for (const value of [gitPath, pythonPath, thtPath, runRoot]) if (!isAbsolute(value)) throw new Error("production guard paths must be absolute");
if (typeof originalFetch !== "function") throw new Error("global fetch is unavailable");
if (!thtIdentity || thtIdentity.sourceStatus !== "tracked-clean") throw new Error("trusted THT identity is absent");
if (!thtIdentity || thtIdentity.sourceStatus !== "git-index-byte-identical") throw new Error("trusted THT identity is absent");
const gitHooksPath = join(runRoot, "installation", "runtime", "acceptance-git-hooks");
mkdirSync(gitHooksPath, { recursive: true, mode: 0o700 });
assertEmptyHooksDirectory(gitHooksPath);
if (failPatchAt !== undefined && (!Number.isInteger(failPatchAt) || failPatchAt < 1 || failPatchAt > 12)) throw new Error("invalid production patch failure probe");
const token = Symbol("p1-production-surface");
const events = [];
@@ -447,18 +697,21 @@ export function installProductionSurfaceGuard({
if (canonical === gitPath) {
validateGitInvocation(argv, { runRoot });
if (options.cwd !== undefined && !ownedGitPath(options.cwd, runRoot)) throw new Error("Git working directory is prohibited");
return { executable: gitPath, kind: "git" };
assertSafeGitRepositoryState(argv, options.cwd, runRoot, gitHooksPath);
const logical = argv[0] === "-c" ? argv.slice(2) : argv;
return { executable: gitPath, kind: "git", argv: hardenedGitArgv(logical, gitHooksPath) };
}
if (canonical === thtPath) {
validateThtInvocation(argv, { thtPath, runRoot, cwd: options.cwd });
return { executable: thtPath, kind: "tht" };
revalidateThtIdentity(thtIdentity);
return { executable: thtPath, kind: "tht", argv };
}
if (canonical === pythonPath) {
if (api !== "spawn" || argv.length !== 3 || argv[0] !== "-c" || argv[1] !== PYTHON_LOCK_HOLDER_PROGRAM
|| !isAbsolute(argv[2]) || relative(runRoot, argv[2]).startsWith("..") || basename(argv[2]) !== "repository.lock") {
throw new Error("child command is prohibited");
}
return { executable: pythonPath, kind: "python-lock-holder" };
return { executable: pythonPath, kind: "python-lock-holder", argv };
}
throw new Error("child command is prohibited");
};
@@ -479,7 +732,7 @@ export function installProductionSurfaceGuard({
const bounded = { ...options, env: options.env ?? environment, timeout: options.timeout ?? 30_000, maxBuffer: options.maxBuffer ?? MAX_OUTPUT, shell: false };
safeChildEvent(events, { api: "execFile", executable: resolved.executable, argv, outcome: "STARTED", detail: resolved.kind,
bounds: { timeoutMs: bounded.timeout, maxOutputBytes: bounded.maxBuffer, environment: "owned" } });
return originals.execFile(resolved.executable, argv, bounded, (error, stdout, stderr) => {
return originals.execFile(resolved.executable, resolved.argv, bounded, (error, stdout, stderr) => {
safeChildEvent(events, { api: "execFile", executable: resolved.executable, argv, outcome: error ? "FAIL" : "PASS", detail: resolved.kind });
callback?.(error, stdout, stderr);
});
@@ -499,7 +752,7 @@ export function installProductionSurfaceGuard({
const bounded = { ...options, env: options.env ?? environment, shell: false };
safeChildEvent(events, { api: "spawn", executable: resolved.executable, argv, outcome: "STARTED", detail: resolved.kind,
bounds: { timeoutMs: 300_000, maxOutputBytes: MAX_OUTPUT, environment: "owned" } });
const child = originals.spawn(resolved.executable, argv, bounded);
const child = originals.spawn(resolved.executable, resolved.argv, bounded);
let bytes = 0;
const count = (chunk) => { bytes += chunk.length; if (bytes > MAX_OUTPUT) child.kill("SIGKILL"); };
child.stdout?.on("data", count); child.stderr?.on("data", count);
@@ -567,6 +820,7 @@ export function installProductionSurfaceGuard({
}
return {
events, externalAttempts: network.externalAttempts,
gitPolicy: { hooksPath: gitHooksPath, fixedConfig: gitSafeConfig(gitHooksPath) },
addOwnedOrigin: network.addOwnedOrigin, hasOwnedOrigin: network.hasOwnedOrigin,
restore() {
if (restored) throw new Error("production surface guard restored twice");
@@ -601,7 +855,10 @@ export async function runCommand(options) {
policyError("command bounds are invalid", details());
}
return await new Promise((resolvePromise, reject) => {
const child = mutableChildProcess.execFile(canonical, argv, { cwd, env, timeout: timeoutMs, maxBuffer: maxOutputBytes, encoding: "utf8", shell: false }, (error, stdout, stderr) => {
const guardedByProduction = productionSurfaceOwner !== undefined;
const childArgv = canonical === allowedGit && !guardedByProduction ? hardenedGitArgv(argv, ownedFallbackGitHooksPath()) : argv;
const childEnv = canonical === allowedGit && !guardedByProduction ? { ...(env ?? {}), ...baseSafeGitEnvironment(canonical) } : env;
const child = mutableChildProcess.execFile(canonical, childArgv, { cwd, env: childEnv, timeout: timeoutMs, maxBuffer: maxOutputBytes, encoding: "utf8", shell: false }, (error, stdout, stderr) => {
const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
const result = { code, stdout: stdout ?? "", stderr: stderr ?? "" };
if (commandEventSink) commandEventSink.push({
@@ -671,7 +928,7 @@ async function walkFiles(root, current = root, out = []) {
for (const entry of await readdir(current, { withFileTypes: true })) {
const path = join(current, entry.name);
const rel = relative(root, path).split(sep).join("/");
if (entry.isSymbolicLink()) continue;
if (entry.isSymbolicLink()) throw new Error(`secret scan failed closed: symlink outside fixture-secrets: ${rel}`);
if (entry.isDirectory()) {
if (rel === "fixture-secrets") continue;
await walkFiles(root, path, out);
@@ -1060,7 +1317,50 @@ export function buildSafeEnvironment({ ambient = process.env, fixture = {} } = {
return safe;
}
async function manifestFiles(root, paths) {
const files = [];
const visit = async (absolute, rel) => {
const entry = await lstat(absolute);
if (entry.isSymbolicLink()) throw new Error(`provenance path is a symlink: ${rel}`);
if (entry.isDirectory()) {
for (const child of (await readdir(absolute, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
await visit(join(absolute, child.name), rel ? `${rel}/${child.name}` : child.name);
}
} else if (entry.isFile()) {
const bytes = await readFile(absolute);
files.push({ path: rel, bytes: bytes.length, sha256: sha256(bytes) });
} else throw new Error(`provenance path is not a regular file: ${rel}`);
};
for (const path of paths) await visit(join(root, path), path);
files.sort((a, b) => a.path.localeCompare(b.path));
return { files, manifestSha256: sha256(JSON.stringify(files)) };
}
export async function collectRepositoryProvenance({ repositoryRoot, gitPath = resolveTrustedSystemExecutableSync("git") }) {
const repo = canonicalRoot(repositoryRoot);
const gitEnv = baseSafeGitEnvironment(gitPath);
const readIdentity = async () => {
const [head, tree, status] = await Promise.all([
runCommand({ executable: gitPath, argv: ["-C", repo, "rev-parse", "HEAD"], env: gitEnv }),
runCommand({ executable: gitPath, argv: ["-C", repo, "rev-parse", "HEAD^{tree}"], env: gitEnv }),
runCommand({ executable: gitPath, argv: ["-C", repo, "status", "--porcelain=v1", "--untracked-files=all"], env: gitEnv }),
]);
return { head: head.stdout.trim(), tree: tree.stdout.trim(), status: status.stdout };
};
const before = await readIdentity();
if (!HEX40.test(before.head) || !HEX40.test(before.tree) || before.status !== "") throw new Error("repository is not clean at exact HEAD");
const backendRoot = join(repo, "backend");
const backendSource = await manifestFiles(backendRoot, [
"src", "scripts/p1-acceptance.mjs", "package.json", "package-lock.json", "tsconfig.json",
]);
const backendDist = await manifestFiles(backendRoot, ["dist"]);
const after = await readIdentity();
if (JSON.stringify(after) !== JSON.stringify(before)) throw new Error("repository provenance changed during binding");
return { schemaVersion: 1, head: before.head, tree: before.tree, clean: true, backendSource, backendDist };
}
async function setupContext(run, repositoryRoot, env, ctx = {}) {
const provenance = await collectRepositoryProvenance({ repositoryRoot, gitPath: resolveTrustedSystemExecutableSync("git") });
const executables = await resolveProductionExecutables({ repositoryRoot });
const harnessDir = realpathSync(join(repositoryRoot, "harness"));
const gitTracePath = join(run.root, "logs", "production-git-trace.jsonl");
@@ -1071,8 +1371,8 @@ async function setupContext(run, repositoryRoot, env, ctx = {}) {
const executablePath = [...new Set([dirname(executables.gitPath), dirname(executables.pythonPath), dirname(executables.thtPath)])].join(":");
const fixtureEnv = {
PATH: executablePath, HOME: ownedHome, TMPDIR: ownedTmp,
GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", GIT_TERMINAL_PROMPT: "0",
GIT_ASKPASS: "/bin/false", SSH_ASKPASS: "/bin/false", GIT_ALLOW_PROTOCOL: "file", GIT_PROTOCOL_FROM_USER: "0",
GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_SYSTEM: "/dev/null", GIT_CONFIG_GLOBAL: "/dev/null", GIT_ATTR_NOSYSTEM: "1",
GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "/bin/false", SSH_ASKPASS: "/bin/false", GIT_ALLOW_PROTOCOL: "file", GIT_PROTOCOL_FROM_USER: "0",
GIT_CONFIG_COUNT: "4", GIT_CONFIG_KEY_0: "commit.gpgSign", GIT_CONFIG_VALUE_0: "false",
GIT_CONFIG_KEY_1: "tag.gpgSign", GIT_CONFIG_VALUE_1: "false",
GIT_CONFIG_KEY_2: "credential.helper", GIT_CONFIG_VALUE_2: "",
@@ -1085,13 +1385,13 @@ async function setupContext(run, repositoryRoot, env, ctx = {}) {
THT_WORKSPACE_GIT_REMOTE: join(run.root, "remote.git"), THT_WORKSPACE_GIT_BRANCH: "main",
THT_WORKSPACE_GIT_AUTHOR_NAME: "P1 API Publisher", THT_WORKSPACE_GIT_AUTHOR_EMAIL: "p1-api@example.invalid",
THT_WORKSPACE_INSTALLATION_ID: "p1-acceptance", THT_WORKSPACE_SECRET_ROOTS: join(run.root, "fixture-secrets"),
THT_HOME: join(run.root, "installation", "runtime", "tht-home"),
THT_HOME: join(run.root, "installation", "runtime", "tht-home"), PYTHONDONTWRITEBYTECODE: "1", PYTHONNOUSERSITE: "1",
GIT_TRACE2_EVENT: gitTracePath,
};
Object.assign(ctx, {
run, repositoryRoot, descriptors: descriptors(), forbiddenValues: ctx.forbiddenValues ?? [],
env: buildSafeEnvironment({ ambient: env, fixture: fixtureEnv }),
httpRequests: [], services: [], gitTracePath, executables,
httpRequests: [], services: [], gitTracePath, executables, provenance,
expectedGitRepositories: ["remote.git", "author"],
});
await createTopology(run);
@@ -1293,7 +1593,10 @@ function productionChecks(ctx) {
const scenarios = [
{ id: "preflight", run: async () => {
const gitVersion = await git(["--version"]); await access(ctx.env.THT_BIN, fsConstants.X_OK);
return await log("preflight", { git: gitVersion.stdout.trim(), node: process.version, thtExecutable: true });
const preflight = await log("preflight", { git: gitVersion.stdout.trim(), node: process.version, thtExecutable: true,
repositoryHead: ctx.provenance.head, repositoryTree: ctx.provenance.tree, repositoryClean: ctx.provenance.clean });
preflight.artifacts.push(await evidence(ctx.run, "logs/provenance.json", ctx.provenance));
return preflight;
} },
{ id: "clean_state", run: async () => {
assert(RUN_ID.test(ctx.run.runId), "run identity invalid");
@@ -1697,7 +2000,7 @@ export async function runIntegration({
try {
run = await createOwnedRun({ repositoryRoot });
ctx = {
run, repositoryRoot, forbiddenValues: [], expectedGitRepositories: [], services: [],
run, repositoryRoot, forbiddenValues: [], expectedGitRepositories: [], services: [], listenerClosureEvidence: [],
originalFetch: globalThis.fetch,
};
commandEventSink = [];
@@ -1733,6 +2036,7 @@ export async function runIntegration({
if (!closed) closeError = new Error("listener still accepts connections after close");
} catch (error) { closeError = error; }
const state = closed ? "closed" : "close_failed";
ctx.listenerClosureEvidence.push({ name: service.name, host: "127.0.0.1", actualPort, state, listenerRefusedConnection: closed });
try {
await ownershipWriter(run, {
name: service.name, kind: "fastify", host: "127.0.0.1", requestedPort: 0,
@@ -1755,6 +2059,18 @@ export async function runIntegration({
activeCommandCheckId = undefined;
try {
assertUniqueResultArtifacts(results);
const finalOwnershipBytes = await readFile(join(run.root, "ownership.json"));
const finalOwnership = await readAndValidateOwnership({ repositoryRoot, runRoot: run.root, expectedNonce: run.nonce });
if (ctx.services.length > 0) {
assert(ctx.listenerClosureEvidence.length === ctx.services.length
&& ctx.listenerClosureEvidence.every(({ state, listenerRefusedConnection }) => state === "closed" && listenerRefusedConnection),
"final listener closure evidence is incomplete");
}
const finalOwnershipArtifact = await evidence(run, "logs/final-ownership.json", {
ownershipSha256: sha256(finalOwnershipBytes), listeners: finalOwnership.listeners,
listenerRefusalChecks: ctx.listenerClosureEvidence,
}, ctx.forbiddenValues);
attachResultArtifact(results, "ownership", finalOwnershipArtifact);
const commandArtifact = await evidence(run, "logs/command-events.json", { eventCount: commandEvents.length, events: commandEvents }, ctx.forbiddenValues);
attachResultArtifact(results, "preflight", commandArtifact);
if (ctx.networkGuard) {
@@ -1767,7 +2083,9 @@ export async function runIntegration({
const childArtifact = await evidence(run, "logs/production-child-events.json", {
executablePolicy, environmentPolicy: {
PATH: ctx.env.PATH, HOME: ctx.env.HOME, TMPDIR: ctx.env.TMPDIR,
gitGlobalConfigDisabled: true, gitSystemConfigDisabled: true, gitPromptsHelpersSigningDisabled: true,
gitGlobalConfigDisabled: true, gitSystemConfigDisabled: true, gitSystemAttributesDisabled: true,
gitPromptsHelpersSigningDisabled: true, gitLocalConfigAttributesHooksValidatedBeforeInvocation: true,
gitFixedConfigPrefix: ctx.networkGuard.gitPolicy.fixedConfig, gitOwnedEmptyHooksPath: ctx.networkGuard.gitPolicy.hooksPath,
gitAllowedProtocol: "file", maxOutputBytes: MAX_OUTPUT, maxTimeoutMs: 300_000,
},
eventCount: ctx.networkGuard.events.length, events: ctx.networkGuard.events,
+189 -7
View File
@@ -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/);
});
+2 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env -S -u BASH_ENV -u ENV /bin/bash
set -euo pipefail
script_path=${BASH_SOURCE[0]}
script_dir=${script_path%/*}
@@ -55,6 +55,7 @@ build_env=(/usr/bin/env -i "PATH=$owned_path" "HOME=$wrapper_root/home" "TMPDIR=
for name in LC_ALL TZ; do
[[ -n "${!name:-}" ]] && build_env+=("$name=${!name}")
done
/bin/rm -rf -- "$repo_root/backend/dist"
"${build_env[@]}" "$node_path" "$npm_path" --prefix "$repo_root/backend" run build
safe_env=(/usr/bin/env -i "PATH=$owned_path" "HOME=$wrapper_root/home" "TMPDIR=$wrapper_root/tmp" "LANG=${LANG:-C}"