fix: close P1 acceptance audit gaps

This commit is contained in:
2026-08-09 22:16:37 +02:00
parent 6b2fd71018
commit c1ca814440
4 changed files with 676 additions and 150 deletions
+10 -5
View File
@@ -5,7 +5,7 @@
## P1 configuration-process automated integration — PASS 2026-08-09 ## P1 configuration-process automated integration — PASS 2026-08-09
- Retained evidence: `.artifacts/p1-integration/p1-f9327d6f41dafbd8a8cd8f6c9efc0276/report.md` - Retained evidence: `.artifacts/p1-integration/p1-70727c7802050c76978d5007a634ede1/report.md`
- automated integration: PASS - automated integration: PASS
- manual acceptance: PENDING - manual acceptance: PENDING
- The contextual negatives use a separate `invalid-context` branch, remote, checkout, data, - The contextual negatives use a separate `invalid-context` branch, remote, checkout, data,
@@ -13,10 +13,15 @@
Persisted before/after semantic-state proofs show primary `main` remains valid, a missing-tree Persisted before/after semantic-state proofs show primary `main` remains valid, a missing-tree
publish is state-neutral, and an invalid pull advances only its disposable checkout while the publish is state-neutral, and an invalid pull advances only its disposable checkout while the
remote and last-valid active/snapshot/data/runtime state remain unchanged. remote and last-valid active/snapshot/data/runtime state remain unchanged.
- The retained audit hashes every request/response and descriptor/negative fixture, production - The retained audit has 132 unique declared artifacts whose final bytes match every SHA-256
Git Trace2 plus runner command events, and all declared artifacts with unique paths. The final declaration. It records canonical Git, Python lock-holder, and `tht` children under bounded
whole-run, reachable-Git, and virtual-report scan found no fixture canaries outside the excluded process/environment policy, both production listeners closed, and no rejected child/network
secret fixture directory. surface events. The in-capture reachable-object scan covered all four expected repositories
(156 objects, 48 blobs) with zero findings; the frozen final filesystem and virtual-report scan
also found no fixture canaries outside the excluded secret fixture directory.
- Final report hashes: `report.json`
`61d767ea90ad1055a1f6fdadec551752b36f44ca173959c33f470c57a4c706a2`;
`report.md` `dbbb37f47f5d686bde3a3516ff7fb3d06c8835aa3b72167f1984436c330446ef`.
## Internal Qdrant + Ollama semantic infrastructure — LIVE 2026-08-08 ## Internal Qdrant + Ollama semantic infrastructure — LIVE 2026-08-08
+501 -137
View File
@@ -1,13 +1,13 @@
#!/usr/bin/env node #!/usr/bin/env node
import { execFile } from "node:child_process";
import { createHash, randomBytes } from "node:crypto"; import { createHash, randomBytes } from "node:crypto";
import { import {
closeSync, constants as fsConstants, existsSync, fsyncSync, lstatSync, openSync, realpathSync, accessSync, closeSync, constants as fsConstants, existsSync, fsyncSync, lstatSync, openSync, realpathSync, statSync,
} from "node:fs"; } from "node:fs";
import { import {
access, chmod, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, symlink, writeFile, access, chmod, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, symlink, writeFile,
} from "node:fs/promises"; } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { createRequire, syncBuiltinESMExports } from "node:module";
import { Socket, isIP } from "node:net"; import { Socket, isIP } from "node:net";
import { import {
basename, dirname, isAbsolute, join, relative, resolve, sep, basename, dirname, isAbsolute, join, relative, resolve, sep,
@@ -15,9 +15,16 @@ import {
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { promisify } from "node:util"; import { promisify } from "node:util";
const execFileAsync = promisify(execFile); const require = createRequire(import.meta.url);
const mutableChildProcess = require("node:child_process");
const mutableDgram = require("node:dgram");
const mutableDns = require("node:dns");
const mutableWorkerThreads = require("node:worker_threads");
let commandEventSink; let commandEventSink;
let activeCommandCheckId; let activeCommandCheckId;
let activeExecutablePolicy;
let integrationOwner;
let productionSurfaceOwner;
const RUN_ID = /^p1-[0-9a-f]{32}$/; const RUN_ID = /^p1-[0-9a-f]{32}$/;
const HEX40 = /^[0-9a-f]{40}$/; const HEX40 = /^[0-9a-f]{40}$/;
const HEX64 = /^[0-9a-f]{64}$/; const HEX64 = /^[0-9a-f]{64}$/;
@@ -37,7 +44,18 @@ const TOPOLOGY = [
"exports/raw", "exports/extracted", "rendered", "logs", "exports/raw", "exports/extracted", "rendered", "logs",
]; ];
const ZIP_FILES = ["manifest.json", "workspace.yaml", "contract.env.example", "README.md"]; const ZIP_FILES = ["manifest.json", "workspace.yaml", "contract.env.example", "README.md"];
const MAX_OUTPUT = 1024 * 1024; const MAX_OUTPUT = 16 * 1024 * 1024;
const PYTHON_LOCK_HOLDER_PROGRAM = [
"import fcntl, os, sys",
"fd = os.open(sys.argv[1], os.O_RDWR | os.O_CREAT | getattr(os, 'O_NOFOLLOW', 0), 0o600)",
"try:",
" fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)",
"except BlockingIOError:",
" sys.exit(73)",
"sys.stdout.write('locked\\n')",
"sys.stdout.flush()",
"sys.stdin.buffer.read()",
].join("\n");
const modulePath = fileURLToPath(import.meta.url); const modulePath = fileURLToPath(import.meta.url);
const defaultRepositoryRoot = realpathSync(resolve(dirname(modulePath), "../..")); const defaultRepositoryRoot = realpathSync(resolve(dirname(modulePath), "../.."));
@@ -83,9 +101,12 @@ async function atomicWrite(path, bytes, mode = 0o600) {
const directory = openSync(dirname(path), fsConstants.O_RDONLY); const directory = openSync(dirname(path), fsConstants.O_RDONLY);
try { fsyncSync(directory); } finally { closeSync(directory); } try { fsyncSync(directory); } finally { closeSync(directory); }
} catch (error) { } catch (error) {
if (handle) await handle.close().catch(() => {}); let failure = error;
await rm(staging, { force: true }); if (handle) {
throw error; try { await handle.close(); } catch (closeError) { failure = closeError; }
}
try { await rm(staging, { force: true }); } catch (cleanupError) { failure = cleanupError; }
throw failure;
} }
} }
function exactOwnedResources(run) { function exactOwnedResources(run) {
@@ -119,10 +140,11 @@ function ownership(run, listeners = run.listeners) {
}; };
} }
async function writeOwnership(run, listenerUpdate) { async function writeOwnership(run, listenerUpdate) {
if (listenerUpdate) { const listeners = listenerUpdate
run.listeners = run.listeners.map((listener) => listener.name === listenerUpdate.name ? listenerUpdate : listener); ? run.listeners.map((listener) => listener.name === listenerUpdate.name ? listenerUpdate : listener)
} : run.listeners;
await atomicWrite(join(run.root, "ownership.json"), `${JSON.stringify(ownership(run), null, 2)}\n`); await atomicWrite(join(run.root, "ownership.json"), `${JSON.stringify(ownership(run, listeners), null, 2)}\n`);
run.listeners = listeners;
} }
export async function createOwnedRun({ repositoryRoot, runId, nonce, now, pid } = {}) { export async function createOwnedRun({ repositoryRoot, runId, nonce, now, pid } = {}) {
const repo = canonicalRoot(repositoryRoot); const repo = canonicalRoot(repositoryRoot);
@@ -149,7 +171,7 @@ function strictOwnership(value, run, expectedNonce) {
&& value.listeners.every((listener, index) => { && value.listeners.every((listener, index) => {
const expectedName = ["primary", "contextual"][index]; const expectedName = ["primary", "contextual"][index];
const common = listener?.name === expectedName && listener.kind === "fastify" && listener.host === "127.0.0.1" const common = listener?.name === expectedName && listener.kind === "fastify" && listener.host === "127.0.0.1"
&& listener.requestedPort === 0 && listener.pid === process.pid && ["not_started", "listening", "closed"].includes(listener.state); && listener.requestedPort === 0 && listener.pid === process.pid && ["not_started", "listening", "closed", "close_failed"].includes(listener.state);
return common && (listener.state === "not_started" return common && (listener.state === "not_started"
? !("actualPort" in listener) ? !("actualPort" in listener)
: Number.isInteger(listener.actualPort) && listener.actualPort >= 1 && listener.actualPort <= 65535); : Number.isInteger(listener.actualPort) && listener.actualPort >= 1 && listener.actualPort <= 65535);
@@ -191,22 +213,236 @@ export async function finalizeOwnedRun({ run, success, keep }) {
return true; return true;
} }
function resolveTrustedSystemExecutableSync(name) {
const candidates = process.platform === "win32"
? []
: [`/usr/bin/${name}`, `/bin/${name}`, `/opt/homebrew/bin/${name}`, `/usr/local/bin/${name}`];
for (const candidate of candidates) {
try {
accessSync(candidate, fsConstants.X_OK);
const canonical = realpathSync(candidate);
if (statSync(canonical).isFile()) return canonical;
} catch { /* try the next fixed trusted executable location */ }
}
throw new Error(`cannot resolve trusted system executable: ${name}`);
}
export async function resolveProductionExecutables({ repositoryRoot, thtBin } = {}) {
const repo = canonicalRoot(repositoryRoot);
const gitPath = resolveTrustedSystemExecutableSync("git");
const pythonPath = resolveTrustedSystemExecutableSync("python3");
const expectedThtRoot = join(repo, "harness", ".venv");
const candidateTht = thtBin ?? join(expectedThtRoot, "bin", "tht");
if (!isAbsolute(candidateTht)) throw new Error("THT executable must be absolute");
const thtPath = realpathSync(candidateTht);
const thtRelative = relative(expectedThtRoot, thtPath);
if (thtRelative.startsWith("..") || isAbsolute(thtRelative)) throw new Error("THT executable leaves the repository virtual environment");
await access(thtPath, fsConstants.X_OK);
return { gitPath, pythonPath, thtPath };
}
const GIT_VERBS = new Set([
"--version", "add", "cat-file", "checkout", "clean", "clone", "commit", "config", "fetch", "for-each-ref",
"init", "ls-tree", "merge", "merge-base", "push", "remote", "reset", "rev-list", "rev-parse", "show",
"show-ref", "status", "symbolic-ref", "write-tree",
]);
function gitVerb(argv) {
if (argv[0] === "--version") return "--version";
let index = 0;
while (index < argv.length) {
if (["-C", "--git-dir", "--work-tree", "-c"].includes(argv[index])) { index += 2; continue; }
if (argv[index].startsWith("--git-dir=") || argv[index].startsWith("--work-tree=")) { index += 1; continue; }
return argv[index];
}
return undefined;
}
function validateGitInvocation(argv) {
const verb = gitVerb(argv);
if (!verb || !GIT_VERBS.has(verb)) throw new Error("Git command is prohibited");
if (argv.some((value) => /^[a-z][a-z0-9+.-]*:\/\//i.test(value) || /^[^/\s]+@[^:\s]+:/.test(value))) {
throw new Error("Git network URL is prohibited");
}
return verb;
}
function boundedChildEnvironment(value, expected) {
const environment = value ?? process.env;
if (!environment || typeof environment !== "object" || Array.isArray(environment)) throw new Error("child environment is invalid");
const allowedExtra = new Set([
"GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL",
"THT_AUTH_USER_ID", "THT_AUTH_USERNAME", "THT_AUTH_IS_ADMIN", "THT_DWH_API_KEY", "THT_VEC_API_KEY",
"THT_VEC_WRITE_API_KEY", "THT_CA", "THT_SSL_CA",
]);
for (const [key, value] of Object.entries(environment)) {
if (typeof value !== "string" || (!(key in expected) && !allowedExtra.has(key))) throw new Error("child environment exceeds acceptance bounds");
}
for (const [key, value] of Object.entries(expected)) if (environment[key] !== value) throw new Error("child environment changed acceptance bounds");
return environment;
}
function safeChildEvent(events, { surface = "child_process", api, executable, argv = [], outcome, detail, bounds }) {
events.push({
surface, api, executable: executable ? basename(executable) : undefined,
argvLabels: argv.map((value) => isAbsolute(value) || value.includes(sep) ? "[path]" : /^[a-z]+:\/\//i.test(value) ? "[url]" : value.length > 80 ? "[value]" : value),
outcome, ...(activeCommandCheckId ? { checkId: activeCommandCheckId } : {}), ...(detail ? { detail } : {}), ...(bounds ? { bounds } : {}),
});
}
export function installProductionSurfaceGuard({ gitPath, pythonPath, thtPath, runRoot, environment, originalFetch = globalThis.fetch }) {
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");
const token = Symbol("p1-production-surface");
productionSurfaceOwner = token;
const events = [];
const originals = {
execFile: mutableChildProcess.execFile, spawn: mutableChildProcess.spawn,
exec: mutableChildProcess.exec, execSync: mutableChildProcess.execSync, execFileSync: mutableChildProcess.execFileSync,
spawnSync: mutableChildProcess.spawnSync, fork: mutableChildProcess.fork,
createSocket: mutableDgram.createSocket, Worker: mutableWorkerThreads.Worker,
dns: new Map(), dnsPromises: new Map(), dlopen: process.dlopen,
};
const resolveChild = (executable, argv) => {
const canonical = executable === "git" ? gitPath : executable === "python3" ? pythonPath : executable;
if (canonical === gitPath) return { executable: gitPath, kind: "git", verb: validateGitInvocation(argv) };
if (canonical === thtPath) return { executable: thtPath, kind: "tht" };
if (canonical === pythonPath) {
if (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" };
}
throw new Error("child command is prohibited");
};
const rejectChild = (api, args) => {
safeChildEvent(events, { api, executable: typeof args[0] === "string" ? args[0] : undefined, outcome: "REJECTED" });
throw new Error("child command is prohibited");
};
const guardedExecFile = function guardedExecFile(executable, argv, options, callback) {
if (!Array.isArray(argv)) return rejectChild("execFile", [executable]);
if (typeof options === "function") { callback = options; options = {}; }
options ??= {};
let resolved;
try { resolved = resolveChild(executable, argv); boundedChildEnvironment(options.env, environment); }
catch (error) { safeChildEvent(events, { api: "execFile", executable, argv, outcome: "REJECTED" }); throw error; }
const bounded = { ...options, env: options.env ?? environment, timeout: Math.min(options.timeout ?? 30_000, 300_000), maxBuffer: Math.min(options.maxBuffer ?? MAX_OUTPUT, 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) => {
safeChildEvent(events, { api: "execFile", executable: resolved.executable, argv, outcome: error ? "FAIL" : "PASS", detail: resolved.kind });
callback?.(error, stdout, stderr);
});
};
Object.defineProperty(guardedExecFile, promisify.custom, { value: (executable, argv, options) => new Promise((resolvePromise, reject) => {
guardedExecFile(executable, argv, options, (error, stdout, stderr) => error ? reject(Object.assign(error, { stdout, stderr })) : resolvePromise({ stdout, stderr }));
}) });
const guardedSpawn = function guardedSpawn(executable, argv, options = {}) {
if (!Array.isArray(argv)) return rejectChild("spawn", [executable]);
let resolved;
try { resolved = resolveChild(executable, argv); boundedChildEnvironment(options.env, environment); }
catch (error) { safeChildEvent(events, { api: "spawn", executable, argv, outcome: "REJECTED" }); throw error; }
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);
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);
const timer = setTimeout(() => child.kill("SIGKILL"), 300_000); timer.unref();
child.once("exit", (code) => { clearTimeout(timer); safeChildEvent(events, { api: "spawn", executable: resolved.executable, argv, outcome: code === 0 ? "PASS" : "FAIL", detail: resolved.kind }); });
child.once("error", () => { clearTimeout(timer); });
return child;
};
const owned = { execFile: guardedExecFile, spawn: guardedSpawn, child: new Map(), dns: new Map(), dnsPromises: new Map() };
mutableChildProcess.execFile = guardedExecFile;
mutableChildProcess.spawn = guardedSpawn;
for (const api of ["exec", "execSync", "execFileSync", "spawnSync", "fork"]) {
const wrapper = (...args) => rejectChild(api, args); owned.child.set(api, wrapper); mutableChildProcess[api] = wrapper;
}
const guardedCreateSocket = (..._args) => { safeChildEvent(events, { surface: "dgram", api: "createSocket", outcome: "REJECTED" }); throw new Error("prohibited production surface: dgram"); };
class ProhibitedWorker { constructor() { safeChildEvent(events, { surface: "worker_threads", api: "Worker", outcome: "REJECTED" }); throw new Error("prohibited production surface: worker_threads"); } }
mutableDgram.createSocket = guardedCreateSocket;
mutableWorkerThreads.Worker = ProhibitedWorker;
for (const name of ["lookup", "resolve", "resolve4", "resolve6", "resolveAny", "resolveCaa", "resolveCname", "resolveMx", "resolveNaptr", "resolveNs", "resolvePtr", "resolveSoa", "resolveSrv", "resolveTxt", "reverse", "Resolver"]) {
if (typeof mutableDns[name] !== "function") continue;
originals.dns.set(name, mutableDns[name]);
const original = originals.dns.get(name);
const wrapper = (...args) => {
if (name === "lookup" && ["127.0.0.1", "::1"].includes(args[0])) {
safeChildEvent(events, { surface: "dns", api: name, outcome: "PASS", detail: "owned-loopback-literal" });
return original(...args);
}
safeChildEvent(events, { surface: "dns", api: name, outcome: "REJECTED" });
throw new Error("prohibited production surface: dns");
};
owned.dns.set(name, wrapper); mutableDns[name] = wrapper;
}
for (const [name, value] of Object.entries(mutableDns.promises ?? {})) if (typeof value === "function") {
originals.dnsPromises.set(name, value);
const original = originals.dnsPromises.get(name);
const wrapper = async (...args) => {
if (name === "lookup" && ["127.0.0.1", "::1"].includes(args[0])) {
safeChildEvent(events, { surface: "dns", api: `promises.${name}`, outcome: "PASS", detail: "owned-loopback-literal" });
return await original(...args);
}
safeChildEvent(events, { surface: "dns", api: `promises.${name}`, outcome: "REJECTED" });
throw new Error("prohibited production surface: dns");
};
owned.dnsPromises.set(name, wrapper); mutableDns.promises[name] = wrapper;
}
const guardedDlopen = (..._args) => { safeChildEvent(events, { surface: "native_addon", api: "dlopen", outcome: "REJECTED" }); throw new Error("prohibited production surface: native addon"); };
process.dlopen = guardedDlopen;
syncBuiltinESMExports();
const network = installNetworkGuard(originalFetch);
activeExecutablePolicy = { gitPath, pythonPath, thtPath };
let restored = false;
return {
events, externalAttempts: network.externalAttempts,
addOwnedOrigin: network.addOwnedOrigin, hasOwnedOrigin: network.hasOwnedOrigin,
restore() {
if (restored) throw new Error("production surface guard restored twice");
restored = true;
let tampered = productionSurfaceOwner !== token;
const ownsToken = productionSurfaceOwner === token;
const restoreOwned = (target, key, wrapper, original) => {
if (target[key] !== wrapper) tampered = true;
if (ownsToken) target[key] = original;
};
const errors = [];
try { network.restore(); } catch (error) { errors.push(error); }
restoreOwned(mutableChildProcess, "execFile", guardedExecFile, originals.execFile);
restoreOwned(mutableChildProcess, "spawn", guardedSpawn, originals.spawn);
for (const [api, wrapper] of owned.child) restoreOwned(mutableChildProcess, api, wrapper, originals[api]);
restoreOwned(mutableDgram, "createSocket", guardedCreateSocket, originals.createSocket);
restoreOwned(mutableWorkerThreads, "Worker", ProhibitedWorker, originals.Worker);
for (const [name, wrapper] of owned.dns) restoreOwned(mutableDns, name, wrapper, originals.dns.get(name));
for (const [name, wrapper] of owned.dnsPromises) restoreOwned(mutableDns.promises, name, wrapper, originals.dnsPromises.get(name));
restoreOwned(process, "dlopen", guardedDlopen, originals.dlopen); syncBuiltinESMExports();
if (productionSurfaceOwner === token) productionSurfaceOwner = undefined;
if (activeExecutablePolicy?.gitPath === gitPath) activeExecutablePolicy = undefined;
if (tampered || errors.length) throw new Error("production surface guard ownership restoration failed");
},
};
}
export async function runCommand(options) { export async function runCommand(options) {
if (!options || typeof options !== "object" || Array.isArray(options)) throw new Error("command requires an options object"); if (!options || typeof options !== "object" || Array.isArray(options)) throw new Error("command requires an options object");
const allowed = new Set(["executable", "argv", "cwd", "env", "timeoutMs", "stdin", "maxOutputBytes"]); const allowed = new Set(["executable", "argv", "cwd", "env", "timeoutMs", "stdin", "maxOutputBytes"]);
for (const key of Object.keys(options)) if (!allowed.has(key)) throw new Error(`unsupported command option ${key}`); for (const key of Object.keys(options)) if (!allowed.has(key)) throw new Error(`unsupported command option ${key}`);
const { executable, argv, cwd, env, timeoutMs = 30_000, stdin, maxOutputBytes = MAX_OUTPUT } = options; const { executable, argv, cwd, env, timeoutMs = 30_000, stdin, maxOutputBytes = MAX_OUTPUT } = options;
if (typeof executable !== "string" || executable.length === 0 || /[;&|`$><\n\r]/.test(executable)) throw new Error("command executable is invalid"); if (typeof executable !== "string" || !isAbsolute(executable) || /[;&|`$><\n\r]/.test(executable)) throw new Error("command executable is invalid");
const allowlistedExecutable = executable === "git" || (isAbsolute(executable) && basename(executable) === "tht"); let canonical;
if (!allowlistedExecutable) throw new Error("command executable is not allowlisted"); try { canonical = realpathSync(executable); } catch { throw new Error("command executable is not allowlisted"); }
const allowedGit = activeExecutablePolicy?.gitPath ?? resolveTrustedSystemExecutableSync("git");
const allowedTht = activeExecutablePolicy?.thtPath;
if (canonical !== allowedGit && canonical !== allowedTht) throw new Error("command executable is not allowlisted");
if (!Array.isArray(argv) || argv.some((value) => typeof value !== "string")) throw new Error("command argv must be a string array"); if (!Array.isArray(argv) || argv.some((value) => typeof value !== "string")) throw new Error("command argv must be a string array");
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300_000) throw new Error("command timeout is invalid"); if (canonical === allowedGit) validateGitInvocation(argv);
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300_000 || !Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 1 || maxOutputBytes > MAX_OUTPUT) throw new Error("command bounds are invalid");
return await new Promise((resolvePromise, reject) => { return await new Promise((resolvePromise, reject) => {
const child = execFile(executable, argv, { cwd, env, timeout: timeoutMs, maxBuffer: maxOutputBytes, encoding: "utf8" }, (error, stdout, stderr) => { const child = mutableChildProcess.execFile(canonical, argv, { cwd, env, timeout: timeoutMs, maxBuffer: maxOutputBytes, encoding: "utf8", shell: false }, (error, stdout, stderr) => {
const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0; const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
const result = { code, stdout: stdout ?? "", stderr: stderr ?? "" }; const result = { code, stdout: stdout ?? "", stderr: stderr ?? "" };
if (commandEventSink) commandEventSink.push({ if (commandEventSink) commandEventSink.push({
executable: basename(executable), executable: basename(canonical),
argvLabels: argv.map((value) => isAbsolute(value) || value.includes(sep) ? "[path]" : /^[a-z]+:\/\//i.test(value) ? "[url]" : value.length > 80 ? "[value]" : value), argvLabels: argv.map((value) => isAbsolute(value) || value.includes(sep) ? "[path]" : /^[a-z]+:\/\//i.test(value) ? "[url]" : value.length > 80 ? "[value]" : value),
outcome: error ? "FAIL" : "PASS", outcome: error ? "FAIL" : "PASS",
...(activeCommandCheckId ? { checkId: activeCommandCheckId } : {}), ...(activeCommandCheckId ? { checkId: activeCommandCheckId } : {}),
@@ -217,7 +453,7 @@ export async function runCommand(options) {
if (stdin !== undefined) { child.stdin.end(stdin); } if (stdin !== undefined) { child.stdin.end(stdin); }
}); });
} }
async function git(argv, options = {}) { return await runCommand({ executable: "git", argv, ...options }); } async function git(argv, options = {}) { return await runCommand({ executable: activeExecutablePolicy?.gitPath ?? resolveTrustedSystemExecutableSync("git"), argv, ...options }); }
async function tht(executable, argv, options = {}) { return await runCommand({ executable, argv, ...options }); } async function tht(executable, argv, options = {}) { return await runCommand({ executable, argv, ...options }); }
function safeArtifactPath(path) { function safeArtifactPath(path) {
if (typeof path !== "string" || !SAFE_RELATIVE.test(path) || path.startsWith(".") || path.includes("//")) throw new Error("unsafe artifact path"); if (typeof path !== "string" || !SAFE_RELATIVE.test(path) || path.startsWith(".") || path.includes("//")) throw new Error("unsafe artifact path");
@@ -282,8 +518,8 @@ async function walkFiles(root, current = root, out = []) {
} }
return out; return out;
} }
async function gitObjectFindings(runRoot, forbiddenValues, expectedGitRepositories) { async function gitObjectScan(runRoot, forbiddenValues, expectedGitRepositories) {
const findings = []; const findings = []; const repositories = [];
for (const rel of expectedGitRepositories) { for (const rel of expectedGitRepositories) {
const directory = join(runRoot, rel); const directory = join(runRoot, rel);
if (!existsSync(directory)) throw new Error(`Git secret scan failed closed: missing expected Git repository: ${rel}`); if (!existsSync(directory)) throw new Error(`Git secret scan failed closed: missing expected Git repository: ${rel}`);
@@ -292,6 +528,7 @@ async function gitObjectFindings(runRoot, forbiddenValues, expectedGitRepositori
try { try {
objects = (await git([...args, "rev-list", "--objects", "--all"])).stdout.trim().split("\n").filter(Boolean); objects = (await git([...args, "rev-list", "--objects", "--all"])).stdout.trim().split("\n").filter(Boolean);
} catch { throw new Error(`Git secret scan failed closed during enumeration: ${basename(directory)}`); } } catch { throw new Error(`Git secret scan failed closed during enumeration: ${basename(directory)}`); }
let blobCount = 0;
for (const line of objects) { for (const line of objects) {
const oid = line.split(" ", 1)[0]; const oid = line.split(" ", 1)[0];
let type; let bytes; let type; let bytes;
@@ -299,21 +536,26 @@ async function gitObjectFindings(runRoot, forbiddenValues, expectedGitRepositori
type = (await git([...args, "cat-file", "-t", oid])).stdout.trim(); type = (await git([...args, "cat-file", "-t", oid])).stdout.trim();
if (!/^(blob|tree|commit|tag)$/.test(type)) throw new Error("invalid object type"); if (!/^(blob|tree|commit|tag)$/.test(type)) throw new Error("invalid object type");
if (type !== "blob") continue; if (type !== "blob") continue;
blobCount += 1;
bytes = Buffer.from((await git([...args, "cat-file", "blob", oid], { maxOutputBytes: 16 * 1024 * 1024 })).stdout); bytes = Buffer.from((await git([...args, "cat-file", "blob", oid], { maxOutputBytes: 16 * 1024 * 1024 })).stdout);
} catch { throw new Error(`Git secret scan failed closed during object inspection: ${basename(directory)}:${oid}`); } } catch { throw new Error(`Git secret scan failed closed during object inspection: ${basename(directory)}:${oid}`); }
if (containsAny(bytes, forbiddenValues)) findings.push({ path: `git-object:${basename(directory)}:${oid}` }); if (containsAny(bytes, forbiddenValues)) findings.push({ path: `git-object:${rel}:${oid}` });
} }
repositories.push({ path: rel, objectCount: objects.length, blobCount });
} }
return findings; return { findings, repositories };
} }
export async function scanSecrets({ runRoot, forbiddenValues, virtualFiles = [], expectedGitRepositories = ["remote.git", "author"] }) { export async function scanSecretsDetailed({ runRoot, forbiddenValues, virtualFiles = [], expectedGitRepositories = ["remote.git", "author"] }) {
const values = forbiddenValues.filter((value) => typeof value === "string" && value.length >= 8); const values = forbiddenValues.filter((value) => typeof value === "string" && value.length >= 8);
const findings = []; const findings = [];
for (const file of await walkFiles(runRoot)) if (containsAny(await readFile(file.path), values)) findings.push({ path: file.rel }); for (const file of await walkFiles(runRoot)) if (containsAny(await readFile(file.path), values)) findings.push({ path: file.rel });
for (const file of virtualFiles) if (containsAny(Buffer.from(file.bytes), values)) findings.push({ path: file.path }); for (const file of virtualFiles) if (containsAny(Buffer.from(file.bytes), values)) findings.push({ path: file.path });
findings.push(...await gitObjectFindings(runRoot, values, expectedGitRepositories)); const gitScan = await gitObjectScan(runRoot, values, expectedGitRepositories);
return findings; findings.push(...gitScan.findings);
return { findings, repositories: gitScan.repositories };
} }
export async function scanSecrets(options) { return (await scanSecretsDetailed(options)).findings; }
export function negativeRequestEvidence(caseLabel, expectedInputField) { export function negativeRequestEvidence(caseLabel, expectedInputField) {
if (!/^[a-z0-9-]+$/.test(caseLabel) || !/^[a-z_]+(?:\.[a-z_]+)*$/.test(expectedInputField)) throw new Error("unsafe negative-case evidence"); if (!/^[a-z0-9-]+$/.test(caseLabel) || !/^[a-z_]+(?:\.[a-z_]+)*$/.test(expectedInputField)) throw new Error("unsafe negative-case evidence");
return { case: caseLabel, expectedInputField }; return { case: caseLabel, expectedInputField };
@@ -360,7 +602,7 @@ export function installNetworkGuard(fetchImplementation = globalThis.fetch) {
const normalized = host === "localhost" || host === "::1" ? "127.0.0.1" : host; const normalized = host === "localhost" || host === "::1" ? "127.0.0.1" : host;
return isIP(normalized) !== 0 && normalized === "127.0.0.1" && ownedOrigins.has(`http://127.0.0.1:${port}`); return isIP(normalized) !== 0 && normalized === "127.0.0.1" && ownedOrigins.has(`http://127.0.0.1:${port}`);
}; };
globalThis.fetch = async (input, init) => { const guardedFetch = async (input, init) => {
const candidate = new URL(typeof input === "string" || input instanceof URL ? input : input.url); const candidate = new URL(typeof input === "string" || input instanceof URL ? input : input.url);
if (!ownedOrigins.has(candidate.origin)) { if (!ownedOrigins.has(candidate.origin)) {
externalAttempts.push({ transport: "fetch", protocol: candidate.protocol, loopback: candidate.hostname === "127.0.0.1" }); externalAttempts.push({ transport: "fetch", protocol: candidate.protocol, loopback: candidate.hostname === "127.0.0.1" });
@@ -368,7 +610,7 @@ export function installNetworkGuard(fetchImplementation = globalThis.fetch) {
} }
return await fetchImplementation(input, init); return await fetchImplementation(input, init);
}; };
Socket.prototype.connect = function guardedSocketConnect(...args) { const guardedSocketConnect = function guardedSocketConnect(...args) {
const destination = socketDestination(args); const destination = socketDestination(args);
if (!("host" in destination) || !isOwned(destination.host, destination.port)) { if (!("host" in destination) || !isOwned(destination.host, destination.port)) {
externalAttempts.push({ transport: "socket", loopback: destination.host === "127.0.0.1" }); externalAttempts.push({ transport: "socket", loopback: destination.host === "127.0.0.1" });
@@ -376,16 +618,20 @@ export function installNetworkGuard(fetchImplementation = globalThis.fetch) {
} }
return originalConnect.apply(this, args); return originalConnect.apply(this, args);
}; };
globalThis.fetch = guardedFetch;
Socket.prototype.connect = guardedSocketConnect;
let restored = false; let restored = false;
return { return {
externalAttempts, externalAttempts,
addOwnedOrigin(value) { ownedOrigins.add(loopbackOrigin(value)); }, addOwnedOrigin(value) { ownedOrigins.add(loopbackOrigin(value)); },
hasOwnedOrigin(value) { return ownedOrigins.has(loopbackOrigin(value)); }, hasOwnedOrigin(value) { return ownedOrigins.has(loopbackOrigin(value)); },
restore() { restore() {
if (restored) return; if (restored) throw new Error("network guard restored twice");
restored = true; restored = true;
const tampered = globalThis.fetch !== guardedFetch || Socket.prototype.connect !== guardedSocketConnect;
globalThis.fetch = originalFetch; globalThis.fetch = originalFetch;
Socket.prototype.connect = originalConnect; Socket.prototype.connect = originalConnect;
if (tampered) throw new Error("network guard ownership changed");
}, },
}; };
} }
@@ -535,7 +781,7 @@ async function startProductionBackend(ctx, { name, env, runtimeConfigPath }) {
try { try {
address = await app.listen({ host: "127.0.0.1", port: 0 }); address = await app.listen({ host: "127.0.0.1", port: 0 });
} catch (error) { } catch (error) {
await app.close().catch(() => {}); try { await app.close(); } catch { throw new Error("production listener start and close both failed"); }
throw error; throw error;
} }
const url = new URL(address); const url = new URL(address);
@@ -567,7 +813,10 @@ export function exportArchiveEvidencePath(requestId) {
return `exports/raw/${requestId}.zip`; return `exports/raw/${requestId}.zip`;
} }
function trackArtifact(ctx, artifact) { function trackArtifact(ctx, artifact) {
if (ctx.activeArtifacts && !ctx.activeArtifacts.some(({ path }) => path === artifact.path)) ctx.activeArtifacts.push(artifact); if (ctx.activeArtifacts) {
if (ctx.activeArtifacts.some(({ path }) => path === artifact.path)) throw new Error("artifact path is duplicated within check");
ctx.activeArtifacts.push(artifact);
}
return artifact; return artifact;
} }
@@ -632,7 +881,7 @@ async function snapshotDigest(path) {
for (const file of files) result[file.rel] = sha256(await readFile(file.path)); for (const file of files) result[file.rel] = sha256(await readFile(file.path));
return result; return result;
} }
const SAFE_AMBIENT_ENV = Object.freeze(["PATH", "HOME", "LANG", "LC_ALL", "TMPDIR", "TZ", "NODE_EXTRA_CA_CERTS"]); const SAFE_AMBIENT_ENV = Object.freeze(["LANG", "LC_ALL", "TZ"]);
export function buildSafeEnvironment({ ambient = process.env, fixture = {} } = {}) { export function buildSafeEnvironment({ ambient = process.env, fixture = {} } = {}) {
const safe = {}; const safe = {};
for (const key of SAFE_AMBIENT_ENV) if (typeof ambient[key] === "string") safe[key] = ambient[key]; for (const key of SAFE_AMBIENT_ENV) if (typeof ambient[key] === "string") safe[key] = ambient[key];
@@ -644,11 +893,24 @@ export function buildSafeEnvironment({ ambient = process.env, fixture = {} } = {
} }
async function setupContext(run, repositoryRoot, env, ctx = {}) { async function setupContext(run, repositoryRoot, env, ctx = {}) {
const thtBin = realpathSync(env.THT_BIN ?? join(repositoryRoot, "harness", ".venv", "bin", "tht")); const executables = await resolveProductionExecutables({
repositoryRoot, thtBin: env.THT_BIN ?? join(repositoryRoot, "harness", ".venv", "bin", "tht"),
});
const harnessDir = realpathSync(join(repositoryRoot, "harness")); const harnessDir = realpathSync(join(repositoryRoot, "harness"));
const gitTracePath = join(run.root, "logs", "production-git-trace.jsonl"); const gitTracePath = join(run.root, "logs", "production-git-trace.jsonl");
const ownedHome = join(run.root, "installation", "runtime", "acceptance-home");
const ownedTmp = join(run.root, "installation", "runtime", "tmp");
await mkdir(ownedHome, { recursive: true, mode: 0o700 });
await mkdir(ownedTmp, { recursive: true, mode: 0o700 });
const executablePath = [...new Set([dirname(executables.gitPath), dirname(executables.pythonPath), dirname(executables.thtPath)])].join(":");
const fixtureEnv = { const fixtureEnv = {
HOST: "127.0.0.1", PORT: "0", AUTH_MODE: "none", THT_BIN: thtBin, 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_COUNT: "3", 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: "",
HOST: "127.0.0.1", PORT: "0", AUTH_MODE: "none", THT_BIN: executables.thtPath,
THT_HARNESS_DIR: harnessDir, THT_DATA_ROOT: join(run.root, "installation", "data"), THT_HARNESS_DIR: harnessDir, THT_DATA_ROOT: join(run.root, "installation", "data"),
SETTINGS_FILE: join(run.root, "installation", "data", "settings.json"), SETTINGS_FILE: join(run.root, "installation", "data", "settings.json"),
MAINTENANCE_STATE_FILE: join(run.root, "installation", "data", "maintenance.json"), MAINTENANCE_STATE_FILE: join(run.root, "installation", "data", "maintenance.json"),
@@ -662,7 +924,7 @@ async function setupContext(run, repositoryRoot, env, ctx = {}) {
Object.assign(ctx, { Object.assign(ctx, {
run, repositoryRoot, descriptors: descriptors(), forbiddenValues: ctx.forbiddenValues ?? [], run, repositoryRoot, descriptors: descriptors(), forbiddenValues: ctx.forbiddenValues ?? [],
env: buildSafeEnvironment({ ambient: env, fixture: fixtureEnv }), env: buildSafeEnvironment({ ambient: env, fixture: fixtureEnv }),
httpRequests: [], services: [], gitTracePath, httpRequests: [], services: [], gitTracePath, executables,
expectedGitRepositories: ["remote.git", "author"], expectedGitRepositories: ["remote.git", "author"],
}); });
await createTopology(run); await createTopology(run);
@@ -773,7 +1035,11 @@ async function traceSize(path) {
} }
function uniqueArtifacts(artifacts) { function uniqueArtifacts(artifacts) {
const seen = new Set(); const seen = new Set();
return artifacts.filter((artifact) => !seen.has(artifact.path) && seen.add(artifact.path)); for (const artifact of artifacts) {
if (seen.has(artifact.path)) throw new Error("artifact path is duplicated within check");
seen.add(artifact.path);
}
return artifacts;
} }
async function commandsObservedForCheck(ctx, checkId, traceBefore) { async function commandsObservedForCheck(ctx, checkId, traceBefore) {
const commands = new Set((commandEventSink ?? []).filter((event) => event.checkId === checkId).map((event) => event.executable)); const commands = new Set((commandEventSink ?? []).filter((event) => event.checkId === checkId).map((event) => event.executable));
@@ -832,9 +1098,27 @@ async function assertNoP1ScopeEntrypoints(ctx) {
const packageJson = JSON.parse(await readFile(join(ctx.repositoryRoot, "backend", "package.json"), "utf8")); const packageJson = JSON.parse(await readFile(join(ctx.repositoryRoot, "backend", "package.json"), "utf8"));
const entrypointBytes = JSON.stringify({ main: packageJson.main, bin: packageJson.bin, exports: packageJson.exports, scripts: packageJson.scripts }); const entrypointBytes = JSON.stringify({ main: packageJson.main, bin: packageJson.bin, exports: packageJson.exports, scripts: packageJson.scripts });
const forbiddenPackageEntrypoints = /(?:acquire|preprocess)Evidence|Evidence(?:Adapter|Acquisition|Preprocessor)/i.test(entrypointBytes); const forbiddenPackageEntrypoints = /(?:acquire|preprocess)Evidence|Evidence(?:Adapter|Acquisition|Preprocessor)/i.test(entrypointBytes);
const auditedSurfaceFiles = [];
for (const group of ["workspaces", "routes"]) {
const root = join(ctx.repositoryRoot, "backend", "dist", group);
for (const name of (await readdir(root)).filter((value) => value.endsWith(".js")).sort()) {
auditedSurfaceFiles.push({ path: `${group}/${name}`, source: await readFile(join(root, name), "utf8") });
}
}
const prohibitedProcessSurfaces = auditedSurfaceFiles.filter(({ source }) => /node:(?:dgram|worker_threads|dns)|\.node(?:["']|$)|process\.dlopen|node-gyp|bindings\s*\(/.test(source));
const networkAdapterModules = auditedSurfaceFiles.filter(({ source }) => /node:(?:net|http|https)|globalThis\.fetch|\bfetch\s*\(/.test(source)).map(({ path }) => path);
const childProcessModules = auditedSurfaceFiles.filter(({ source }) => /node:child_process/.test(source)).map(({ path }) => path);
assert(JSON.stringify(networkAdapterModules) === JSON.stringify(["workspaces/diagnostics.js"])
&& JSON.stringify(childProcessModules) === JSON.stringify(["workspaces/diagnostics.js", "workspaces/git-repository.js"])
&& prohibitedProcessSurfaces.length === 0,
"unexpected production process/network adapter entrypoint surface present");
assert(forbiddenModuleNames.length === 0 && forbiddenExports.length === 0 && forbiddenRoutes.length === 0 && !forbiddenPackageEntrypoints, assert(forbiddenModuleNames.length === 0 && forbiddenExports.length === 0 && forbiddenRoutes.length === 0 && !forbiddenPackageEntrypoints,
"prohibited P1 adapter/acquisition/preprocessing entrypoint surface present"); "prohibited P1 adapter/acquisition/preprocessing entrypoint surface present");
return { moduleFilesAudited: modules.sort(), routeAppsAudited: routeSurfaces.map(({ name }) => name), packageEntrypointsAudited: true }; return {
moduleFilesAudited: modules.sort(), routeAppsAudited: routeSurfaces.map(({ name }) => name), packageEntrypointsAudited: true,
productionSurfaceFilesAudited: auditedSurfaceFiles.map(({ path }) => path), networkAdapterModules,
childProcessModules, workerDgramDnsNativeEntrypoints: [],
};
} }
function productionChecks(ctx) { function productionChecks(ctx) {
@@ -941,7 +1225,6 @@ function productionChecks(ctx) {
const currentSnapshot = await currentSnapshotManifest(ctx, current.commit); ctx.currentManifest = currentSnapshot.manifest; const currentSnapshot = await currentSnapshotManifest(ctx, current.commit); ctx.currentManifest = currentSnapshot.manifest;
return { commands: ["git"], artifacts: [ return { commands: ["git"], artifacts: [
await evidence(ctx.run, "logs/content-only-revision.json", { oldCommit: ctx.oldRevision.commit, newCommit: current.commit, descriptorBlob: current.blob, oldFilesystemRoot: ctx.oldFilesystemRoot, newFilesystemRoot: newRoot, oldSnapshotImmutable: true }), await evidence(ctx.run, "logs/content-only-revision.json", { oldCommit: ctx.oldRevision.commit, newCommit: current.commit, descriptorBlob: current.blob, oldFilesystemRoot: ctx.oldFilesystemRoot, newFilesystemRoot: newRoot, oldSnapshotImmutable: true }),
await fileArtifact(ctx.run.root, relative(ctx.run.root, currentSnapshot.path)), await fileArtifact(ctx.run.root, relative(ctx.run.root, current.snapshotPath)),
] }; ] };
} }, } },
{ id: "snapshot_and_docs", run: async () => { { id: "snapshot_and_docs", run: async () => {
@@ -950,16 +1233,17 @@ function productionChecks(ctx) {
const extracted = join(ctx.run.root, "exports", "extracted", id); const extracted = join(ctx.run.root, "exports", "extracted", id);
for (const name of ZIP_FILES) { for (const name of ZIP_FILES) {
assert((await lstat(join(extracted, name))).isFile(), `missing extracted ${name}`); assert((await lstat(join(extracted, name))).isFile(), `missing extracted ${name}`);
artifacts.push(await fileArtifact(ctx.run.root, `exports/extracted/${id}/${name}`)); await access(join(extracted, name), fsConstants.R_OK);
} }
const revision = revisionFromManifest(ctx.currentManifest, id); const revision = revisionFromManifest(ctx.currentManifest, id);
for (const suffix of [".yaml", ".env.example", ".md", "snapshot.json"]) { for (const suffix of [".yaml", ".env.example", ".md"]) {
const file = suffix === "snapshot.json" ? join(dirname(revision.snapshotPath), suffix) : join(dirname(revision.snapshotPath), `${id}${suffix}`); const file = join(dirname(revision.snapshotPath), `${id}${suffix}`);
assert(existsSync(file), `snapshot artifact absent ${file}`); assert(existsSync(file), `snapshot artifact absent ${file}`);
artifacts.push(await fileArtifact(ctx.run.root, relative(ctx.run.root, file))); artifacts.push(await fileArtifact(ctx.run.root, relative(ctx.run.root, file)));
} }
assert(!existsSync(join(dirname(revision.snapshotPath), "workspace-content")), "snapshot materialized source tree"); assert(!existsSync(join(dirname(revision.snapshotPath), "workspace-content")), "snapshot materialized source tree");
} }
artifacts.push(await fileArtifact(ctx.run.root, relative(ctx.run.root, join(dirname(ctx.currentRevision.snapshotPath), "snapshot.json"))));
artifacts.unshift(await evidence(ctx.run, "logs/snapshot-and-docs.json", { exactBundleFiles: ZIP_FILES, generatedDocs: true, immutableSnapshots: true, derivedFromManifest: true })); artifacts.unshift(await evidence(ctx.run, "logs/snapshot-and-docs.json", { exactBundleFiles: ZIP_FILES, generatedDocs: true, immutableSnapshots: true, derivedFromManifest: true }));
return { commands: [], artifacts }; return { commands: [], artifacts };
} }, } },
@@ -1113,17 +1397,30 @@ function productionChecks(ctx) {
const gitTraceProof = await assertProductionGitTrace(ctx); const gitTraceProof = await assertProductionGitTrace(ctx);
assert(present.length === 0 && prohibitedRoutesCalled.length === 0 && prohibitedCommands.length === 0, "prohibited P1 scope operation observed"); assert(present.length === 0 && prohibitedRoutesCalled.length === 0 && prohibitedCommands.length === 0, "prohibited P1 scope operation observed");
assert(ctx.networkGuard.externalAttempts.length === 0, "external network connection attempted"); assert(ctx.networkGuard.externalAttempts.length === 0, "external network connection attempted");
const rejectedSurfaces = ctx.networkGuard.events.filter(({ outcome }) => outcome === "REJECTED");
const rejectedChildren = rejectedSurfaces.filter(({ surface }) => surface === "child_process");
const childKinds = new Set(ctx.networkGuard.events.filter(({ surface }) => surface === "child_process").map(({ detail }) => detail).filter(Boolean));
assert(rejectedSurfaces.length === 0 && rejectedChildren.length === 0 && ["git", "python-lock-holder", "tht"].every((kind) => childKinds.has(kind)),
"production child allowlist evidence is incomplete");
assert(ctx.services.length === 2 && ctx.services.every(({ baseUrl }) => ctx.networkGuard.hasOwnedOrigin(baseUrl)), "listener was not an owned loopback origin"); assert(ctx.services.length === 2 && ctx.services.every(({ baseUrl }) => ctx.networkGuard.hasOwnedOrigin(baseUrl)), "listener was not an owned loopback origin");
return await log("no-p1-scope-artifacts", { return await log("no-p1-scope-artifacts", {
absentArtifacts: forbidden, moduleEntrypointSurfaceAbsent: true, routeEntrypointSurfaceAbsent: true, absentArtifacts: forbidden, moduleEntrypointSurfaceAbsent: true, routeEntrypointSurfaceAbsent: true,
...surfaceAudit, productionGitTrace: gitTraceProof, networkGuardInstalledBeforeProduction: true, externalNetworkAttempts: [], ...surfaceAudit, productionGitTrace: gitTraceProof, networkGuardInstalledBeforeProduction: true, externalNetworkAttempts: [],
exactProductionChildApi: true, productionChildKinds: [...childKinds].sort(), rejectedProductionChildren: [],
ownedLoopbackOrigins: ctx.services.map(({ name }) => name), ownedLoopbackOrigins: ctx.services.map(({ name }) => name),
}); });
} }, } },
{ id: "secret_scan", run: async () => { { id: "secret_scan", run: async () => {
const findings = await scanSecrets({ runRoot: ctx.run.root, forbiddenValues: ctx.forbiddenValues }); const scan = await scanSecretsDetailed({
assert(findings.length === 0, "secret canary found outside exclusion"); runRoot: ctx.run.root, forbiddenValues: ctx.forbiddenValues,
return await log("secret-scan", { scanned: true, gitEnumerationFailClosed: true, excluded: "fixture-secrets", findings: [] }); expectedGitRepositories: ctx.expectedGitRepositories,
});
assert(scan.findings.length === 0, "secret canary found outside exclusion");
ctx.gitSecretScanFrozen = true;
return await log("secret-scan", {
scanned: true, gitEnumerationFailClosed: true, excluded: "fixture-secrets",
expectedGitRepositories: [...ctx.expectedGitRepositories], repositories: scan.repositories, findings: [],
});
} }, } },
{ id: "cleanup_confinement", run: async () => { { id: "cleanup_confinement", run: async () => {
const fakeRepo = join(ctx.run.root, "installation", "runtime", "cleanup-test"); const fakeRepo = join(ctx.run.root, "installation", "runtime", "cleanup-test");
@@ -1161,11 +1458,22 @@ function completeFailedResults(results, firstError = "Acceptance setup failed sa
return completed; return completed;
} }
function deduplicateResultArtifacts(results) { function assertUniqueResultArtifacts(results) {
const seen = new Set(); const seen = new Set();
for (const result of results) result.artifacts = result.artifacts.filter(({ path }) => !seen.has(path) && seen.add(path)); for (const result of results) for (const artifact of result.artifacts) {
if (seen.has(artifact.path)) throw new Error("artifact path is duplicated across checks");
seen.add(artifact.path);
}
return results; return results;
} }
async function verifyDeclaredArtifacts(runRoot, results) {
assertUniqueResultArtifacts(results);
for (const result of results) for (const artifact of result.artifacts) {
safeArtifactPath(artifact.path);
const current = sha256(await readFile(join(runRoot, artifact.path)));
if (current !== artifact.sha256) throw new Error("declared artifact hash mismatch");
}
}
function minimalFailClosedReport(run, keep) { function minimalFailClosedReport(run, keep) {
const checks = CHECK_IDS.map((id, index) => failedCheck( const checks = CHECK_IDS.map((id, index) => failedCheck(
id, nowIso(), index === 0 ? "Acceptance audit failed closed." : "Not executed after fail-closed audit.", id, nowIso(), index === 0 ? "Acceptance audit failed closed." : "Not executed after fail-closed audit.",
@@ -1189,99 +1497,155 @@ function attachResultArtifact(results, checkId, artifact) {
result.artifacts.push(artifact); result.artifacts.push(artifact);
} }
export async function runIntegration({ repositoryRoot = defaultRepositoryRoot, keep = false, env = process.env, failAt = env.P1_ACCEPTANCE_FAIL_AT, checks, setup, announce } = {}) { async function listenerRefuses(baseUrl, timeoutMs = 1_000) {
const savedEnv = { ...process.env }; const url = new URL(baseUrl);
let run; let ctx; let results = []; let fatal; return await new Promise((resolvePromise) => {
try { const socket = new Socket(); let settled = false;
run = await createOwnedRun({ repositoryRoot }); const finish = (refuses) => { if (settled) return; settled = true; socket.destroy(); resolvePromise(refuses); };
ctx = { const timer = setTimeout(() => finish(false), timeoutMs); timer.unref();
run, repositoryRoot, forbiddenValues: [], expectedGitRepositories: [], services: [], socket.once("connect", () => { clearTimeout(timer); finish(false); });
originalFetch: globalThis.fetch, socket.once("error", () => { clearTimeout(timer); finish(true); });
}; try { socket.connect({ host: "127.0.0.1", port: Number(url.port) }); } catch { clearTimeout(timer); finish(true); }
commandEventSink = []; });
const selectedSetup = setup ?? (checks === undefined ? setupContext : undefined); }
if (selectedSetup) { function environmentMatches(expected) {
const configured = await selectedSetup(run, repositoryRoot, env, ctx); const currentKeys = Object.keys(process.env).sort(); const expectedKeys = Object.keys(expected).sort();
if (configured && configured !== ctx) Object.assign(ctx, configured); return JSON.stringify(currentKeys) === JSON.stringify(expectedKeys)
} && expectedKeys.every((key) => process.env[key] === expected[key]);
if (checks === undefined) {
if (!ctx.env) throw new Error("acceptance setup returned no environment");
replaceProcessEnvironment(ctx.env);
ctx.networkGuard = installNetworkGuard(ctx.originalFetch);
checks = productionChecks(ctx);
} else if (ctx.env) {
replaceProcessEnvironment(ctx.env);
}
results = await executeChecks({ checks, failAt });
} catch (error) {
fatal = error;
if (run) results = completeFailedResults(results);
} finally {
if (ctx?.services) {
for (const service of [...ctx.services].reverse()) {
await service.app.close().catch(() => {});
await writeOwnership(run, {
name: service.name, kind: "fastify", host: "127.0.0.1", requestedPort: 0,
actualPort: Number(new URL(service.baseUrl).port), pid: process.pid, state: "closed",
}).catch(() => {});
}
}
ctx?.networkGuard?.restore();
replaceProcessEnvironment(savedEnv);
}
if (!run) throw fatal;
results = deduplicateResultArtifacts(completeFailedResults(results));
let auditFailed = false;
const commandEvents = commandEventSink ?? [];
commandEventSink = undefined;
activeCommandCheckId = undefined;
try {
const commandArtifact = await evidence(run, "logs/command-events.json", { eventCount: commandEvents.length, events: commandEvents }, ctx.forbiddenValues);
attachResultArtifact(results, "preflight", commandArtifact);
if (ctx.gitTracePath) {
const gitTraceBytes = await readFile(ctx.gitTracePath);
for (const line of gitTraceBytes.toString("utf8").split("\n").filter(Boolean)) JSON.parse(line);
attachResultArtifact(results, "no_p1_scope_artifacts", await fileArtifact(run.root, relative(run.root, ctx.gitTracePath)));
}
} catch {
auditFailed = true;
}
deduplicateResultArtifacts(results);
let report = {
schemaVersion: 1, runId: run.runId, startedAt: run.startedAt, finishedAt: nowIso(),
command: `p1-acceptance integration${keep ? " --keep" : ""}`,
overall: !fatal && deriveOverall(results) === "PASS" ? "PASS" : "FAIL", checks: results,
};
let bytes;
try {
bytes = reportBytes(report);
const findings = await scanSecrets({
runRoot: run.root,
forbiddenValues: ctx.forbiddenValues,
expectedGitRepositories: ctx.expectedGitRepositories,
virtualFiles: [{ path: "report.json", bytes: bytes.json }, { path: "report.md", bytes: bytes.markdown }],
});
if (findings.length > 0) auditFailed = true;
} catch {
auditFailed = true;
}
if (auditFailed) {
report = minimalFailClosedReport(run, keep);
bytes = reportBytes(report);
if (containsAny(bytes.json, ctx.forbiddenValues) || containsAny(bytes.markdown, ctx.forbiddenValues)) {
throw new Error("sanitized fail-closed report unexpectedly contains a forbidden value");
}
}
await atomicWrite(join(run.root, "report.json"), bytes.json);
await atomicWrite(join(run.root, "report.md"), bytes.markdown);
const finalSuccess = report.overall === "PASS";
if (announce) await announce({ report, runRoot: run.root, keep });
const removed = await finalizeOwnedRun({ run, success: finalSuccess, keep });
return { exitCode: finalSuccess ? 0 : 1, runRoot: run.root, retained: !removed, report };
} }
export async function runIntegration({
repositoryRoot = defaultRepositoryRoot, keep = false, env = process.env,
failAt = env.P1_ACCEPTANCE_FAIL_AT, checks, setup, announce, ownershipWriter = writeOwnership,
} = {}) {
if (integrationOwner) throw new Error("P1 acceptance integration is already active");
const acquisitionToken = Symbol("p1-integration-owner");
integrationOwner = acquisitionToken;
const savedEnv = { ...process.env };
let installedEnv; let run; let ctx; let results = []; let fatal; let auditFailed = false;
try {
try {
run = await createOwnedRun({ repositoryRoot });
ctx = {
run, repositoryRoot, forbiddenValues: [], expectedGitRepositories: [], services: [],
originalFetch: globalThis.fetch,
};
commandEventSink = [];
const selectedSetup = setup ?? (checks === undefined ? setupContext : undefined);
if (selectedSetup) {
const configured = await selectedSetup(run, repositoryRoot, env, ctx);
if (configured && configured !== ctx) Object.assign(ctx, configured);
}
if (checks === undefined) {
if (!ctx.env || !ctx.executables) throw new Error("acceptance setup returned no bounded environment");
installedEnv = { ...ctx.env };
replaceProcessEnvironment(installedEnv);
ctx.networkGuard = installProductionSurfaceGuard({
...ctx.executables, runRoot: run.root, environment: installedEnv, originalFetch: ctx.originalFetch,
});
checks = productionChecks(ctx);
} else if (ctx.env) {
installedEnv = { ...ctx.env };
replaceProcessEnvironment(installedEnv);
}
results = await executeChecks({ checks, failAt });
} catch (error) {
fatal = error;
if (run) results = completeFailedResults(results);
} finally {
if (ctx?.services) {
for (const service of [...ctx.services].reverse()) {
const actualPort = Number(new URL(service.baseUrl).port);
let closed = false; let closeError;
try {
await service.app.close();
closed = await listenerRefuses(service.baseUrl);
if (!closed) closeError = new Error("listener still accepts connections after close");
} catch (error) { closeError = error; }
const state = closed ? "closed" : "close_failed";
try {
await ownershipWriter(run, {
name: service.name, kind: "fastify", host: "127.0.0.1", requestedPort: 0,
actualPort, pid: process.pid, state,
});
} catch (error) { closeError ??= error; }
if (closeError) { fatal ??= closeError; auditFailed = true; }
}
}
try { ctx?.networkGuard?.restore(); } catch (error) { fatal ??= error; auditFailed = true; }
if (installedEnv && !environmentMatches(installedEnv)) { fatal ??= new Error("acceptance environment ownership changed"); auditFailed = true; }
try { replaceProcessEnvironment(savedEnv); } catch (error) { fatal ??= error; auditFailed = true; }
if (!environmentMatches(savedEnv)) { fatal ??= new Error("acceptance environment restoration failed"); auditFailed = true; }
}
if (!run) throw fatal;
results = completeFailedResults(results);
const commandEvents = commandEventSink ?? [];
commandEventSink = undefined;
activeCommandCheckId = undefined;
try {
assertUniqueResultArtifacts(results);
const commandArtifact = await evidence(run, "logs/command-events.json", { eventCount: commandEvents.length, events: commandEvents }, ctx.forbiddenValues);
attachResultArtifact(results, "preflight", commandArtifact);
if (ctx.networkGuard) {
const executablePolicy = {};
for (const [name, executablePath] of Object.entries(ctx.executables)) {
executablePolicy[name] = { path: executablePath, sha256: sha256(await readFile(executablePath)) };
}
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,
gitAllowedProtocol: "file", maxOutputBytes: MAX_OUTPUT, maxTimeoutMs: 300_000,
},
eventCount: ctx.networkGuard.events.length, events: ctx.networkGuard.events,
}, ctx.forbiddenValues);
attachResultArtifact(results, "no_p1_scope_artifacts", childArtifact);
}
if (ctx.gitTracePath) {
const gitTraceBytes = await readFile(ctx.gitTracePath);
for (const line of gitTraceBytes.toString("utf8").split("\n").filter(Boolean)) JSON.parse(line);
attachResultArtifact(results, "no_p1_scope_artifacts", await fileArtifact(run.root, relative(run.root, ctx.gitTracePath)));
}
assertUniqueResultArtifacts(results);
if (ctx.executables && !ctx.gitSecretScanFrozen) throw new Error("expected Git secret scan was not frozen inside secret_scan");
} catch { auditFailed = true; }
let report = {
schemaVersion: 1, runId: run.runId, startedAt: run.startedAt, finishedAt: nowIso(),
command: `p1-acceptance integration${keep ? " --keep" : ""}`,
overall: !fatal && !auditFailed && deriveOverall(results) === "PASS" ? "PASS" : "FAIL", checks: results,
};
let bytes;
try {
bytes = reportBytes(report);
const findings = await scanSecrets({
runRoot: run.root, forbiddenValues: ctx.forbiddenValues, expectedGitRepositories: [],
virtualFiles: [{ path: "report.json", bytes: bytes.json }, { path: "report.md", bytes: bytes.markdown }],
});
if (findings.length > 0) throw new Error("final filesystem or virtual secret scan failed");
await verifyDeclaredArtifacts(run.root, results);
} catch { auditFailed = true; }
if (auditFailed) {
report = minimalFailClosedReport(run, keep);
bytes = reportBytes(report);
if (containsAny(bytes.json, ctx.forbiddenValues) || containsAny(bytes.markdown, ctx.forbiddenValues)) {
throw new Error("sanitized fail-closed report unexpectedly contains a forbidden value");
}
}
await atomicWrite(join(run.root, "report.json"), bytes.json);
await atomicWrite(join(run.root, "report.md"), bytes.markdown);
const finalSuccess = report.overall === "PASS";
if (announce) await announce({ report, runRoot: run.root, keep });
const removed = await finalizeOwnedRun({ run, success: finalSuccess, keep });
return { exitCode: finalSuccess ? 0 : 1, runRoot: run.root, retained: !removed, report };
} finally {
commandEventSink = undefined;
activeCommandCheckId = undefined;
if (integrationOwner === acquisitionToken) integrationOwner = undefined;
else if (integrationOwner !== undefined) throw new Error("P1 acceptance integration ownership changed");
}
}
export async function main(argv = process.argv.slice(2), env = process.env) { export async function main(argv = process.argv.slice(2), env = process.env) {
if (argv.length < 1 || argv[0] !== "integration" || argv.length > 2 || (argv.length === 2 && argv[1] !== "--keep")) { if (argv.length < 1 || argv[0] !== "integration" || argv.length > 2 || (argv.length === 2 && argv[1] !== "--keep")) {
console.error("usage: p1-acceptance integration [--keep]"); return 2; console.error("usage: p1-acceptance integration [--keep]"); return 2;
+159 -4
View File
@@ -8,6 +8,8 @@ import { dirname, join } from "node:path";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { createServer, connect } from "node:net"; import { createServer, connect } from "node:net";
import dgram from "node:dgram";
import { Worker } from "node:worker_threads";
import test from "node:test"; import test from "node:test";
import { import {
@@ -16,6 +18,8 @@ import {
buildSafeEnvironment, buildSafeEnvironment,
installExternalFetchGuard, installExternalFetchGuard,
installNetworkGuard, installNetworkGuard,
installProductionSurfaceGuard,
resolveProductionExecutables,
negativeRequestEvidence, negativeRequestEvidence,
cleanupOwnedRun, cleanupOwnedRun,
createOwnedRun, createOwnedRun,
@@ -275,13 +279,17 @@ test("command helper accepts only executable plus separate argv", async () => {
await assert.rejects(runCommand({ executable: "/bin/echo", argv: [], shell: true })); await assert.rejects(runCommand({ executable: "/bin/echo", argv: [], shell: true }));
await assert.rejects(runCommand({ executable: "git status; rm -rf /", argv: [] })); 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: "/tmp/git", argv: ["--version"] }), /command executable is not allowlisted/);
await assert.rejects(runCommand({ executable: "tht", argv: ["config", "check"] }), /command executable is not allowlisted/); await assert.rejects(runCommand({ executable: "tht", argv: ["config", "check"] }), /command executable is invalid/);
const repositoryRoot = await fakeRepository(); const repositoryRoot = await fakeRepository();
const executable = join(repositoryRoot, "executable with spaces"); const executable = join(repositoryRoot, "executable with spaces");
await writeFile(executable, "#!/bin/sh\nprintf '%s' \"$1\"\n", { mode: 0o700 }); await writeFile(executable, "#!/bin/sh\nprintf '%s' \"$1\"\n", { mode: 0o700 });
await chmod(executable, 0o700); await chmod(executable, 0o700);
await assert.rejects(runCommand({ executable, argv: ["literal;not-a-shell"] }), /command executable is not allowlisted/); await assert.rejects(runCommand({ executable, argv: ["literal;not-a-shell"] }), /command executable is not allowlisted/);
const result = await runCommand({ executable: "git", argv: ["--version"] }); const tht = join(repositoryRoot, "harness", ".venv", "bin", "tht");
await mkdir(dirname(tht), { recursive: true });
await writeFile(tht, "#!/bin/sh\nexit 0\n", { mode: 0o700 });
const { gitPath } = await resolveProductionExecutables({ repositoryRoot, thtBin: tht, ambientPath: process.env.PATH });
const result = await runCommand({ executable: gitPath, argv: ["--version"] });
assert.match(result.stdout, /^git version /); assert.match(result.stdout, /^git version /);
assert.equal(result.code, 0); assert.equal(result.code, 0);
}); });
@@ -293,8 +301,7 @@ test("safe environment rejects ambient THT and keeps only strict process allowli
fixture: { THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets" }, fixture: { THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets" },
}); });
assert.deepEqual(safe, { assert.deepEqual(safe, {
PATH: "/safe/bin", HOME: "/home/test", LANG: "C", LANG: "C", THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets",
THT_BIN: "/fixture/tht", THT_WORKSPACE_SECRET_ROOTS: "/fixture/secrets",
}); });
}); });
@@ -434,3 +441,151 @@ test("secret scan fails closed when either expected Git repository is missing",
await assert.rejects(scanSecrets({ runRoot: run.root, forbiddenValues: ["CANARY-value-123"] }), new RegExp(`missing expected Git repository: ${missing.replace(".", "\\.")}`)); await assert.rejects(scanSecrets({ runRoot: run.root, forbiddenValues: ["CANARY-value-123"] }), new RegExp(`missing expected Git repository: ${missing.replace(".", "\\.")}`));
} }
}); });
test("runIntegration fails closed when a later duplicate overwrites stale artifact evidence", async () => {
const repositoryRoot = await fakeRepository();
const checks = exactScenarios(async (id) => {
if (id === CHECK_IDS[0]) {
await mkdir(join(repositoryRoot, ".artifacts", "p1-integration", "scratch"), { recursive: true });
}
return { commands: [], artifacts: [] };
});
const result = await runIntegration({
repositoryRoot, keep: true,
setup: async (run, _repositoryRoot, _env, ctx) => {
const path = join(run.root, "logs", "overwritten.json");
await mkdir(dirname(path), { recursive: true });
await writeFile(path, "first");
const stale = { path: "logs/overwritten.json", sha256: "a7937b64b8caa58f03721bb6bacf9e92a2c78987f5d1692a065a4698e006c4ca" };
checks[0].run = async () => ({ commands: [], artifacts: [stale] });
checks[1].run = async () => {
await writeFile(path, "second");
return { commands: [], artifacts: [{ path: stale.path, sha256: "16367aacb67a4a017c8da8ab95682ccb389c61bb315f3425e2f2666f2476d1ce" }] };
};
return ctx;
},
checks,
});
assert.equal(result.exitCode, 1);
assert.equal(result.report.checks.length, 15);
assert(result.report.checks.every(({ status, artifacts }) => status === "FAIL" && artifacts.length === 0));
});
test("production surface guard rejects and records UDP, Worker, git ls-remote, and unexpected python", async () => {
const repositoryRoot = await fakeRepository();
const thtPath = join(repositoryRoot, "harness", ".venv", "bin", "tht");
await mkdir(dirname(thtPath), { recursive: true });
await writeFile(thtPath, "#!/bin/sh\nexit 0\n", { mode: 0o700 });
await chmod(thtPath, 0o700);
const executables = await resolveProductionExecutables({ repositoryRoot, thtBin: thtPath, ambientPath: process.env.PATH });
const guard = installProductionSurfaceGuard({
...executables, runRoot: repositoryRoot, environment: { ...process.env }, originalFetch: globalThis.fetch,
});
try {
assert.throws(() => dgram.createSocket("udp4"), /prohibited production surface/);
assert.throws(() => new Worker("", { eval: true }), /prohibited production surface/);
await assert.rejects(runCommand({ executable: executables.gitPath, argv: ["ls-remote", "https://example.com/repo.git"] }), /Git command is prohibited/);
const childProcess = await import("node:child_process");
assert.throws(() => childProcess.spawn(executables.pythonPath, ["-c", "print('unexpected')"]), /child command is prohibited/);
assert.deepEqual(new Set(guard.events.filter(({ outcome }) => outcome === "REJECTED").map(({ surface }) => surface)),
new Set(["dgram", "worker_threads", "child_process"]));
} finally {
guard.restore();
}
});
test("listener close rejection retains listening truth and forces exact-15 FAIL", async () => {
const repositoryRoot = await fakeRepository();
const server = createServer();
await new Promise((resolvePromise, reject) => server.listen(0, "127.0.0.1", (error) => error ? reject(error) : resolvePromise()));
const address = server.address();
assert(address && typeof address === "object");
const result = await runIntegration({
repositoryRoot, keep: false, checks: exactScenarios(),
setup: async (run, _repositoryRoot, _env, ctx) => {
ctx.services = [{ name: "primary", baseUrl: `http://127.0.0.1:${address.port}`, app: { close: async () => { throw new Error("close rejected"); } } }];
const value = JSON.parse(await readFile(join(run.root, "ownership.json"), "utf8"));
value.listeners[0] = { name: "primary", kind: "fastify", host: "127.0.0.1", requestedPort: 0, actualPort: address.port, pid: process.pid, state: "listening" };
await writeFile(join(run.root, "ownership.json"), `${JSON.stringify(value, null, 2)}\n`);
return ctx;
},
});
assert.equal(result.exitCode, 1);
assert.equal(result.retained, true);
const owner = JSON.parse(await readFile(join(result.runRoot, "ownership.json"), "utf8"));
assert.notEqual(owner.listeners[0].state, "closed");
assert(result.report.checks.every(({ status }) => status === "FAIL"));
await new Promise((resolvePromise) => server.close(resolvePromise));
});
test("ownership close write failure forces retained exact-15 FAIL", async () => {
const repositoryRoot = await fakeRepository();
const server = createServer();
await new Promise((resolvePromise, reject) => server.listen(0, "127.0.0.1", (error) => error ? reject(error) : resolvePromise()));
const address = server.address();
assert(address && typeof address === "object");
const result = await runIntegration({
repositoryRoot, keep: false, checks: exactScenarios(),
ownershipWriter: async (_run, update) => { if (update?.state === "closed") throw new Error("owned write rejected"); },
setup: async (_run, _repositoryRoot, _env, ctx) => {
ctx.services = [{ name: "primary", baseUrl: `http://127.0.0.1:${address.port}`, app: { close: async () => await new Promise((resolvePromise) => server.close(resolvePromise)) } }];
return ctx;
},
});
assert.equal(result.exitCode, 1);
assert.equal(result.retained, true);
assert(result.report.checks.every(({ status }) => status === "FAIL"));
});
test("nested runIntegration is rejected before process-global mutation and outer restoration remains owned", async () => {
const repositoryRoot = await fakeRepository();
const originalFetch = globalThis.fetch;
const originalPath = process.env.PATH;
let nestedError;
const result = await runIntegration({
repositoryRoot, keep: true, checks: exactScenarios(),
setup: async (_run, _repositoryRoot, _env, ctx) => {
try { await runIntegration({ repositoryRoot, keep: true, checks: exactScenarios() }); } catch (error) { nestedError = error; }
assert.equal(globalThis.fetch, originalFetch);
assert.equal(process.env.PATH, originalPath);
return ctx;
},
});
assert.match(nestedError?.message ?? "", /already active/);
assert.equal(result.exitCode, 0);
assert.equal(globalThis.fetch, originalFetch);
assert.equal(process.env.PATH, originalPath);
});
test("environment tampering fails the audit and restores the caller environment", async () => {
const repositoryRoot = await fakeRepository();
const before = { ...process.env };
const checks = exactScenarios(async (id) => {
if (id === CHECK_IDS[0]) process.env.P1_ACCEPTANCE_UNOWNED = "tampered";
return { commands: [], artifacts: [] };
});
const result = await runIntegration({
repositoryRoot, keep: true, checks,
setup: async (_run, _repositoryRoot, _env, ctx) => { ctx.env = { P1_ACCEPTANCE_OWNED: "yes" }; return ctx; },
});
assert.equal(result.exitCode, 1);
assert(result.report.checks.every(({ status }) => status === "FAIL"));
assert.deepEqual({ ...process.env }, before);
});
test("production guard detects global tampering and restores without stranding patches", async () => {
const repositoryRoot = await fakeRepository();
const thtPath = join(repositoryRoot, "harness", ".venv", "bin", "tht");
await mkdir(dirname(thtPath), { recursive: true });
await writeFile(thtPath, "#!/bin/sh\nexit 0\n", { mode: 0o700 });
const executables = await resolveProductionExecutables({ repositoryRoot, thtBin: thtPath });
const originalFetch = globalThis.fetch;
const guard = installProductionSurfaceGuard({ ...executables, runRoot: repositoryRoot, environment: { ...process.env }, originalFetch });
globalThis.fetch = originalFetch;
assert.throws(() => guard.restore(), /ownership restoration failed/);
assert.equal(globalThis.fetch, originalFetch);
const childProcess = await import("node:child_process");
assert.doesNotThrow(() => childProcess.spawn);
});
+6 -4
View File
@@ -5,17 +5,19 @@ if [[ $# -lt 1 || "$1" != "integration" || $# -gt 2 || ( $# -eq 2 && "$2" != "--
printf 'usage: %s integration [--keep]\n' "$0" >&2 printf 'usage: %s integration [--keep]\n' "$0" >&2
exit 2 exit 2
fi fi
for command in node npm git; do command -v "$command" >/dev/null || { printf 'missing command: %s\n' "$command" >&2; exit 127; }; done for command in node npm git python3; do command -v "$command" >/dev/null || { printf 'missing command: %s\n' "$command" >&2; exit 127; }; done
node_command="$(command -v node)"
node_bin="$(cd "$(dirname "$node_command")" && pwd -P)/$(basename "$node_command")"
THT_BIN="${THT_BIN:-$repo_root/harness/.venv/bin/tht}" THT_BIN="${THT_BIN:-$repo_root/harness/.venv/bin/tht}"
[[ "$THT_BIN" = /* && -x "$THT_BIN" ]] || { printf 'THT_BIN must be an absolute executable path\n' >&2; exit 127; } [[ "$THT_BIN" = /* && -x "$THT_BIN" ]] || { printf 'THT_BIN must be an absolute executable path\n' >&2; exit 127; }
npm --prefix "$repo_root/backend" run build npm --prefix "$repo_root/backend" run build
safe_env=(env -i "PATH=$PATH" "HOME=${HOME:-/nonexistent}" "LANG=${LANG:-C}" "THT_BIN=$THT_BIN") safe_env=(env -i "PATH=/usr/bin:/bin" "HOME=/nonexistent" "TMPDIR=/tmp" "LANG=${LANG:-C}" "THT_BIN=$THT_BIN")
for name in LC_ALL TMPDIR TZ; do for name in LC_ALL TZ; do
[[ -n "${!name:-}" ]] && safe_env+=("$name=${!name}") [[ -n "${!name:-}" ]] && safe_env+=("$name=${!name}")
done done
[[ -n "${P1_ACCEPTANCE_FAIL_AT:-}" ]] && safe_env+=("P1_ACCEPTANCE_FAIL_AT=$P1_ACCEPTANCE_FAIL_AT") [[ -n "${P1_ACCEPTANCE_FAIL_AT:-}" ]] && safe_env+=("P1_ACCEPTANCE_FAIL_AT=$P1_ACCEPTANCE_FAIL_AT")
set +e set +e
"${safe_env[@]}" node "$repo_root/backend/scripts/p1-acceptance.mjs" "$@" "${safe_env[@]}" "$node_bin" "$repo_root/backend/scripts/p1-acceptance.mjs" "$@"
status=$? status=$?
set -e set -e
exit "$status" exit "$status"