Files
clean-architecture-frontend…/tests/unit/provider-guardian-transaction.test.ts
T
DongHyeonkaandClaude Opus 5 9ca5c3f668 fix: say why a sandbox failed to launch, and observe a run instead of an instant
The cgroup test read the live process tree with one `ps` per pid and asserted
while the provider was running. That was a race it used to win only because the
sandbox was slow; now a whole run finishes in a few hundred milliseconds and
`systemctl show` alone costs longer than the thing it describes. It records the
tree from `/proc` every 5ms and asserts on the recording once the run is over,
because the assertions were always about what the run contained.

That restructuring immediately paid for itself: the supervisor had been failing
to launch the sandbox at all, and the test was dying on the observation before
it ever checked the exit code.

It could not say why, because the supervisor consumed the child's output solely
to enforce a byte cap and then discarded it — `exit=1` and nothing else. It now
keeps the lines the sandbox tooling itself emits (`bwrap:`, `prlimit:`,
`systemd-run:`, `systemctl:`), which cannot carry provider credentials because
the provider command and its secrets travel in the args file. The failure now
reads:

  sandboxed external provider failed: exit=1; sandbox reported:
  bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted

which is a host restriction — `kernel.apparmor_restrict_unprivileged_userns=1`
— reproducible in two lines of shell containing none of this repository's code,
and recorded in the ledger as such rather than carried as a product defect.

Suites that spawn processes, build archives and sign evidence were given a 30s
budget. The 10s default is sized for pure-JS unit tests; raising it globally
would hide a genuinely hung test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 21:12:06 +09:00

1114 lines
43 KiB
TypeScript

import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import { closeSync, constants, fchmodSync, openSync, watch } from "node:fs";
import {
lstat,
mkdir,
mkdtemp,
open,
readFile,
readdir,
readlink,
rm,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
/**
* This suite's budget, not the file's. The 10s default is sized for pure-JS
* unit tests; these spawn processes, build archives and sign evidence, and on a
* machine running the rest of the suite in parallel they legitimately need
* longer. Raising the global default instead would hide a genuinely hung test.
*/
const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
const roots: string[] = [];
afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
});
describe("provider guardian transaction protocol", () => {
it("encodes a v2 guard without accepting filesystem paths or identities", async () => {
const protocol = await import("../../scripts/lib/provider-guardian-protocol.ts") as
Record<string, unknown>;
expect(protocol.encodeProviderGuardianGuard).toBeTypeOf("function");
const encode = protocol.encodeProviderGuardianGuard as (input: Readonly<{
kind: "vulnerability";
nonce: Buffer;
deadlineEpochMs: number;
}>) => Buffer;
const nonce = Buffer.alloc(32, 0x5a);
const encoded = encode({
kind: "vulnerability",
nonce,
deadlineEpochMs: 1_800_000_000_000,
});
expect(encoded.readUInt32BE(0)).toBe(encoded.byteLength - 4);
expect(JSON.parse(encoded.subarray(4).toString("utf8"))).toEqual({
type: "guard",
version: 2,
kind: "vulnerability",
nonce: "5a".repeat(32),
deadlineEpochMs: 1_800_000_000_000,
});
expect(
(protocol.providerGuardianSealedTempLeaf as (kind: "vulnerability", value: Buffer) => string)(
"vulnerability",
nonce,
),
).toBe(`.vulnerability-report.json.guardian-${"5a".repeat(16)}.tmp`);
expect(
(protocol.providerGuardianRawStagingLeaf as (
kind: "vulnerability",
value: Buffer,
) => string)("vulnerability", nonce),
).toBe(`.vulnerability-report.json.guardian-${"5a".repeat(16)}.raw.tmp`);
});
it("decodes only the exact canonical v2 guard within the lease window", async () => {
const protocol = await import("../../scripts/lib/provider-guardian-protocol.ts") as
Record<string, unknown>;
expect(protocol.decodeProviderGuardianGuard).toBeTypeOf("function");
const decode = protocol.decodeProviderGuardianGuard as (
payload: Buffer,
options: Readonly<{ nowEpochMs: number; maxLeaseMs: number }>,
) => Readonly<{ kind: string; nonce: Buffer; deadlineEpochMs: number }>;
const canonical = JSON.stringify({
type: "guard",
version: 2,
kind: "provenance",
nonce: "3c".repeat(32),
deadlineEpochMs: 5_000,
});
expect(decode(Buffer.from(canonical), { nowEpochMs: 1_000, maxLeaseMs: 5_000 }))
.toEqual({
kind: "provenance",
nonce: Buffer.alloc(32, 0x3c),
deadlineEpochMs: 5_000,
});
for (const invalid of [
canonical.replace('"nonce":', '"nonce":"3c'.concat('"'.repeat(0), ',"nonce":')),
canonical.replace(/\}$/u, ',"rawIno":9}'),
`${canonical} `,
canonical.replace("5000", "1000"),
canonical.replace("5000", "7000"),
canonical.replace("3c".repeat(32), "3c".repeat(31)),
]) {
expect(() => decode(Buffer.from(invalid), { nowEpochMs: 1_000, maxLeaseMs: 5_000 }))
.toThrow(/provider guardian/u);
}
expect(() => decode(Buffer.from([0xff]), { nowEpochMs: 1_000, maxLeaseMs: 5_000 }))
.toThrow(/provider guardian/u);
});
it("authenticates READY, publish, PUBLISHED, and v2 commit with exact fields", async () => {
const protocol = await import("../../scripts/lib/provider-guardian-protocol.ts") as
Record<string, unknown>;
for (const name of [
"decodeProviderGuardianReady",
"encodeProviderGuardianPublish",
"decodeProviderGuardianPublish",
"encodeProviderGuardianPublished",
"decodeProviderGuardianPublished",
]) {
expect(protocol[name], name).toBeTypeOf("function");
}
const nonce = Buffer.alloc(32, 0x7a);
const readyPayload = Buffer.from(JSON.stringify({
type: "ready",
version: 2,
nonce: "7a".repeat(32),
rawDev: 12,
rawIno: 34,
sealedTempLeaf: ".vulnerability-report.json.guardian-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.tmp",
sealedDev: 56,
sealedIno: 78,
}));
const decodeReady = protocol.decodeProviderGuardianReady as (
payload: Buffer,
expectedNonce: Buffer,
) => Record<string, unknown>;
expect(decodeReady(readyPayload, nonce)).toEqual({
nonce,
rawDev: 12,
rawIno: 34,
sealedTempLeaf: ".vulnerability-report.json.guardian-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.tmp",
sealedDev: 56,
sealedIno: 78,
});
expect(() => decodeReady(readyPayload, Buffer.alloc(32, 0x7b)))
.toThrow(/authentication/u);
const encodePublish = protocol.encodeProviderGuardianPublish as (input: Record<string, unknown>) => Buffer;
const publishFrame = encodePublish({
nonce,
sealedDev: 56,
sealedIno: 78,
size: 123,
sha256: "ab".repeat(32),
});
expect(JSON.parse(publishFrame.subarray(4).toString("utf8"))).toEqual({
type: "publish",
version: 2,
nonce: "7a".repeat(32),
sealedDev: 56,
sealedIno: 78,
size: 123,
sha256: "ab".repeat(32),
});
const decodePublish = protocol.decodeProviderGuardianPublish as (
payload: Buffer,
expectedNonce: Buffer,
) => Record<string, unknown>;
expect(decodePublish(publishFrame.subarray(4), nonce)).toEqual({
nonce,
sealedDev: 56,
sealedIno: 78,
size: 123,
sha256: "ab".repeat(32),
});
const encodePublished = protocol.encodeProviderGuardianPublished as (
input: Readonly<{ nonce: Buffer; sealedDev: number; sealedIno: number }>,
) => Buffer;
const published = encodePublished({ nonce, sealedDev: 56, sealedIno: 78 });
const decodePublished = protocol.decodeProviderGuardianPublished as (
payload: Buffer,
expectedNonce: Buffer,
expectedIdentity: Readonly<{ dev: number; ino: number }>,
) => void;
expect(() => decodePublished(published.subarray(4), nonce, { dev: 56, ino: 78 }))
.not.toThrow();
const encodeCommit = protocol.encodeProviderGuardianCommit as (value: Buffer) => Buffer;
expect(JSON.parse(encodeCommit(nonce).subarray(4).toString("utf8"))).toEqual({
type: "commit",
version: 2,
nonce: "7a".repeat(32),
});
});
it("creates and returns owned raw and sealed-temp identities before READY", async () => {
const {
decodeProviderGuardianReady,
encodeProviderGuardianGuard,
} = await import("../../scripts/lib/provider-guardian-protocol.ts");
const workspace = await createWorkspace("provider-guardian-ready-");
const rawPath = path.join(
workspace,
"provider-evidence/untrusted/vulnerability-report.json",
);
const nonce = Buffer.alloc(32, 0x4d);
const child = spawnGuardian(workspace, nonce);
const completion = waitForChild(child);
const readyPayload = readFrame(child.stdout!);
child.stdin!.write(encodeProviderGuardianGuard({
kind: "vulnerability",
nonce,
deadlineEpochMs: Date.now() + 2_000,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "guardian READY"),
nonce,
);
const raw = await lstat(rawPath);
const sealedTempPath = path.join(workspace, "provider-evidence", ready.sealedTempLeaf);
const sealedTemp = await lstat(sealedTempPath);
expect({ dev: raw.dev, ino: raw.ino, mode: raw.mode & 0o777 }).toEqual({
dev: ready.rawDev,
ino: ready.rawIno,
mode: 0o600,
});
expect({ dev: sealedTemp.dev, ino: sealedTemp.ino, mode: sealedTemp.mode & 0o777 })
.toEqual({ dev: ready.sealedDev, ino: ready.sealedIno, mode: 0o600 });
child.stdin!.end();
await expect(within(completion, 1_000, "guardian abort")).resolves.toEqual({
code: 125,
signal: null,
});
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(sealedTempPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(readdir(path.join(workspace, "provider-evidence/untrusted"))).resolves.toEqual([]);
});
it("creates no evidence for empty or truncated guard input", async () => {
const { startProviderGuardian } = await import(
"../../scripts/lib/provider-guardian-client.ts"
);
for (const [label, input, expectedCode] of [
["empty", undefined, 125],
["truncated-prefix", Buffer.from([0, 0]), 126],
] as const) {
const workspace = await createWorkspace(`provider-guardian-${label}-`);
const child = spawnGuardian(workspace);
const completion = waitForChild(child);
if (input) child.stdin!.end(input);
else child.stdin!.end();
await expect(within(completion, 1_000, `${label} guardian EOF`)).resolves.toEqual({
code: expectedCode,
signal: null,
});
await expect(readdir(path.join(workspace, "provider-evidence/untrusted"))).resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence"))).resolves.toEqual([
"untrusted",
]);
const retry = await startProviderGuardian({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 3_000,
guardianScript: path.resolve("scripts/lib/provider-raw-guardian.ts"),
});
await retry.abort();
await expect(readdir(path.join(workspace, "provider-evidence/untrusted")))
.resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence")))
.resolves.toEqual(["untrusted"]);
}
});
it.each(["raw", "sealed"] as const)(
"cleans the independently bound %s private alias when peer bootstrap validation fails",
async (boundKind) => {
const workspace = await createWorkspace(`provider-guardian-partial-bootstrap-${boundKind}-`);
const nonce = Buffer.alloc(32, boundKind === "raw" ? 0x71 : 0x72);
const fixture = spawnGuardianWithPrivateModes(workspace, nonce, {
rawMode: boundKind === "raw" ? 0o600 : 0o400,
sealedMode: boundKind === "sealed" ? 0o600 : 0o400,
});
await expect(within(
waitForChild(fixture.child),
1_000,
`${boundKind} partial bootstrap exit`,
)).resolves.toEqual({ code: 126, signal: null });
const boundPath = boundKind === "raw" ? fixture.rawStagingPath : fixture.sealedTempPath;
const invalidPath = boundKind === "raw" ? fixture.sealedTempPath : fixture.rawStagingPath;
await expect(lstat(boundPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(invalidPath)).resolves.toEqual(expect.objectContaining({
mode: expect.any(Number),
}));
await rm(invalidPath);
await expect(readdir(path.join(workspace, "provider-evidence/untrusted")))
.resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence")))
.resolves.toEqual(["untrusted"]);
},
);
it("cleans guardian-created objects when the parent closes immediately after guard", async () => {
const { encodeProviderGuardianGuard } = await import(
"../../scripts/lib/provider-guardian-protocol.ts"
);
const workspace = await createWorkspace("provider-guardian-parent-startup-death-");
const nonce = Buffer.alloc(32, 0x2b);
const child = spawnGuardian(workspace, nonce);
const completion = waitForChild(child);
child.stdin!.end(encodeProviderGuardianGuard({
kind: "vulnerability",
nonce,
deadlineEpochMs: Date.now() + 2_000,
}));
await expect(within(completion, 1_000, "guardian parent startup death"))
.resolves.toEqual({ code: 125, signal: null });
await expect(readdir(path.join(workspace, "provider-evidence/untrusted"))).resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence"))).resolves.toEqual(["untrusted"]);
});
it("publishes the pinned sealed inode and preserves it only after commit EOF", async () => {
const {
decodeProviderGuardianPublished,
decodeProviderGuardianReady,
encodeProviderGuardianCommit,
encodeProviderGuardianGuard,
encodeProviderGuardianPublish,
} = await import("../../scripts/lib/provider-guardian-protocol.ts");
const workspace = await createWorkspace("provider-guardian-publish-");
const evidenceRoot = path.join(workspace, "provider-evidence");
const rawPath = path.join(evidenceRoot, "untrusted/vulnerability-report.json");
const sealedPath = path.join(evidenceRoot, "vulnerability-report.json");
const nonce = Buffer.alloc(32, 0x62);
const child = spawnGuardian(workspace, nonce);
const completion = waitForChild(child);
const readyPayload = readFrame(child.stdout!);
child.stdin!.write(encodeProviderGuardianGuard({
kind: "vulnerability",
nonce,
deadlineEpochMs: Date.now() + 3_000,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "guardian READY"),
nonce,
);
const sealedTempPath = path.join(evidenceRoot, ready.sealedTempLeaf);
const bytes = Buffer.from('{"validated":true}\n');
const handle = await open(
sealedTempPath,
constants.O_WRONLY | constants.O_NOFOLLOW,
);
try {
const before = await handle.stat();
expect({ dev: before.dev, ino: before.ino }).toEqual({
dev: ready.sealedDev,
ino: ready.sealedIno,
});
await handle.writeFile(bytes);
await handle.chmod(0o400);
await handle.sync();
} finally {
await handle.close();
}
const publishedPayload = readFrame(child.stdout!);
child.stdin!.write(encodeProviderGuardianPublish({
nonce,
sealedDev: ready.sealedDev,
sealedIno: ready.sealedIno,
size: bytes.byteLength,
sha256: createHash("sha256").update(bytes).digest("hex"),
}));
decodeProviderGuardianPublished(
await within(publishedPayload, 1_000, "guardian PUBLISHED"),
nonce,
{ dev: ready.sealedDev, ino: ready.sealedIno },
);
await expect(lstat(sealedTempPath)).rejects.toMatchObject({ code: "ENOENT" });
const published = await lstat(sealedPath);
expect({ dev: published.dev, ino: published.ino, mode: published.mode & 0o777 })
.toEqual({ dev: ready.sealedDev, ino: ready.sealedIno, mode: 0o400 });
await expect(readFile(sealedPath)).resolves.toEqual(bytes);
child.stdin!.write(encodeProviderGuardianCommit(nonce));
await new Promise<void>((resolve) => setTimeout(resolve, 50));
expect(child.exitCode).toBeNull();
child.stdin!.end();
await expect(within(completion, 1_000, "guardian commit EOF")).resolves.toEqual({
code: 0,
signal: null,
});
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(readFile(sealedPath)).resolves.toEqual(bytes);
});
it("owns raw and sealed publication through the authenticated client lease", async () => {
const client = await import("../../scripts/lib/provider-guardian-client.ts") as
Record<string, unknown>;
expect(client.startProviderGuardian).toBeTypeOf("function");
const start = client.startProviderGuardian as (input: Readonly<{
kind: "vulnerability";
workspaceRoot: string;
leaseMs: number;
guardianScript: string;
}>) => Promise<Readonly<{
pid: number;
rawPath: string;
rawIdentity: Readonly<{ dev: number; ino: number }>;
sealedPath: string;
sealedTempPath: string;
sealedIdentity: Readonly<{ dev: number; ino: number }>;
publish(bytes: Buffer): Promise<void>;
commit(): Promise<void>;
}>>;
const workspace = await createWorkspace("provider-guardian-client-v2-");
const guardianScript = path.resolve("scripts/lib/provider-raw-guardian.ts");
const lease = await start({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 3_000,
guardianScript,
});
expect((await readFile(`/proc/${lease.pid}/cmdline`)).toString("utf8")
.split("\0").filter(Boolean)).toEqual([process.execPath, guardianScript]);
await expect(readlink(`/proc/${lease.pid}/fd/3`)).resolves.toBe(
path.join(workspace, "provider-evidence/untrusted"),
);
await expect(readlink(`/proc/${lease.pid}/fd/4`)).resolves.toBe(
path.join(workspace, "provider-evidence"),
);
const rawMetadata = await lstat(lease.rawPath);
expect({ dev: rawMetadata.dev, ino: rawMetadata.ino }).toEqual(lease.rawIdentity);
const sealedTempMetadata = await lstat(lease.sealedTempPath);
expect({ dev: sealedTempMetadata.dev, ino: sealedTempMetadata.ino })
.toEqual(lease.sealedIdentity);
const bytes = Buffer.from('{"validated":"client"}\n');
await lease.publish(bytes);
await expect(lstat(lease.sealedTempPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(readFile(lease.sealedPath)).resolves.toEqual(bytes);
await lease.commit();
await expect(lstat(lease.rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(readFile(lease.sealedPath)).resolves.toEqual(bytes);
});
it("cleans published evidence on EOF before commit and permits a same-workspace retry", async () => {
const workspace = await createWorkspace("provider-guardian-published-eof-");
const transaction = await establishPublishedGuardian(workspace, Buffer.alloc(32, 0x31));
transaction.child.stdin!.end();
await expect(within(transaction.completion, 1_000, "published guardian EOF"))
.resolves.toEqual({ code: 125, signal: null });
await assertTransactionAbsent(transaction);
const { startProviderGuardian } = await import(
"../../scripts/lib/provider-guardian-client.ts"
);
const retry = await startProviderGuardian({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 2_000,
guardianScript: path.resolve("scripts/lib/provider-raw-guardian.ts"),
});
await retry.abort();
await expect(readdir(path.join(workspace, "provider-evidence/untrusted"))).resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence"))).resolves.toEqual(["untrusted"]);
});
it("fails closed when a separate trailing frame arrives after commit", async () => {
const { encodeProviderGuardianCommit } = await import(
"../../scripts/lib/provider-guardian-protocol.ts"
);
const workspace = await createWorkspace("provider-guardian-commit-trailing-");
const nonce = Buffer.alloc(32, 0x47);
const transaction = await establishPublishedGuardian(workspace, nonce);
transaction.child.stdin!.write(encodeProviderGuardianCommit(nonce));
await new Promise<void>((resolve) => setImmediate(resolve));
transaction.child.stdin!.write(Buffer.from([0, 0, 0, 1, 0x7b]));
transaction.child.stdin!.end();
await expect(within(transaction.completion, 1_000, "guardian trailing frame"))
.resolves.toEqual({ code: 126, signal: null });
await assertTransactionAbsent(transaction);
});
it("uses pinned fallback identities after a guardian hard death", async () => {
const { startProviderGuardian } = await import(
"../../scripts/lib/provider-guardian-client.ts"
);
const workspace = await createWorkspace("provider-guardian-hard-death-");
const lease = await startProviderGuardian({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 3_000,
guardianScript: path.resolve("scripts/lib/provider-raw-guardian.ts"),
});
process.kill(lease.pid, "SIGKILL");
await expect(within(lease.prematureExit, 1_000, "guardian hard death"))
.resolves.toEqual(expect.objectContaining({ message: expect.stringMatching(/SIGKILL/u) }));
await lease.abort();
await expect(lstat(lease.rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(lease.sealedTempPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(lease.sealedPath)).rejects.toMatchObject({ code: "ENOENT" });
});
it("cleans a linked raw inode when killed before READY and retries immediately", async () => {
const { startProviderGuardian } = await import(
"../../scripts/lib/provider-guardian-client.ts"
);
const workspace = await createWorkspace("provider-guardian-pre-ready-death-");
const evidenceRoot = path.join(workspace, "provider-evidence");
const rawDirectory = path.join(evidenceRoot, "untrusted");
const rawPath = path.join(rawDirectory, "vulnerability-report.json");
const guardianScript = await createPausedGuardianFixture(workspace);
const existingChildren = await directChildPids(process.pid);
const starting = startProviderGuardian({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 3_000,
guardianScript,
});
const guardianPid = await waitForNewDirectChild(existingChildren);
await waitForStoppedProcess(guardianPid);
const rawLink = waitForDirectoryEntry(rawDirectory, path.basename(rawPath), () => {
process.kill(guardianPid, "SIGKILL");
});
process.kill(guardianPid, "SIGCONT");
await within(rawLink, 1_000, "pre-READY canonical raw link");
await expect(within(starting, 2_000, "pre-READY guardian rejection"))
.rejects.toThrow(/provider guardian/u);
const possibleTempLeaves = (await readdir(evidenceRoot)).filter((leaf) =>
leaf.startsWith(".vulnerability-report.json.guardian-")
);
expect(possibleTempLeaves).toEqual([]);
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
const retry = await startProviderGuardian({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 3_000,
guardianScript: path.resolve("scripts/lib/provider-raw-guardian.ts"),
});
expect(path.basename(retry.sealedTempPath)).toMatch(
/^\.vulnerability-report\.json\.guardian-[0-9a-f]{32}\.tmp$/u,
);
await retry.abort();
await expect(readdir(rawDirectory)).resolves.toEqual([]);
await expect(readdir(evidenceRoot)).resolves.toEqual(["untrusted"]);
});
it("preserves an external raw canary created after startup checks", async () => {
const { startProviderGuardian } = await import(
"../../scripts/lib/provider-guardian-client.ts"
);
const workspace = await createWorkspace("provider-guardian-external-canary-");
const rawPath = path.join(
workspace,
"provider-evidence/untrusted/vulnerability-report.json",
);
const { guardianScript, markerPath } = await createStalledGuardianFixture(workspace);
const starting = startProviderGuardian({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 3_000,
guardianScript,
});
const guardianPid = Number(await waitForFile(markerPath));
const canaryBytes = Buffer.from("external-canary\n");
const canaryHandle = await open(
rawPath,
constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW,
0o600,
);
await canaryHandle.writeFile(canaryBytes);
const canaryIdentity = await canaryHandle.stat();
await canaryHandle.close();
process.kill(guardianPid, "SIGKILL");
await expect(within(starting, 2_000, "external-canary guardian rejection"))
.rejects.toThrow(/provider guardian/u);
expect(await readFile(rawPath)).toEqual(canaryBytes);
expect(await lstat(rawPath)).toMatchObject({
dev: canaryIdentity.dev,
ino: canaryIdentity.ino,
});
await rm(rawPath);
const retry = await startProviderGuardian({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 3_000,
guardianScript: path.resolve("scripts/lib/provider-raw-guardian.ts"),
});
await retry.abort();
await expect(readdir(path.join(workspace, "provider-evidence/untrusted")))
.resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence")))
.resolves.toEqual(["untrusted"]);
});
it("allows exactly one same-kind guardian without deleting the winner", async () => {
const { startProviderGuardian } = await import(
"../../scripts/lib/provider-guardian-client.ts"
);
const workspace = await createWorkspace("provider-guardian-concurrent-kind-");
const input = {
kind: "vulnerability" as const,
workspaceRoot: workspace,
leaseMs: 10_000,
guardianScript: path.resolve("scripts/lib/provider-raw-guardian.ts"),
};
const results = await Promise.allSettled([
startProviderGuardian(input),
startProviderGuardian(input),
]);
const winners = results.filter((result) => result.status === "fulfilled");
const losers = results.filter((result) => result.status === "rejected");
expect(winners).toHaveLength(1);
expect(losers).toHaveLength(1);
const winner = winners[0]!.value;
const rawMetadata = await lstat(winner.rawPath);
expect({ dev: rawMetadata.dev, ino: rawMetadata.ino }).toEqual(winner.rawIdentity);
await winner.abort();
await expect(readdir(path.join(workspace, "provider-evidence/untrusted")))
.resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence")))
.resolves.toEqual(["untrusted"]);
const retry = await startProviderGuardian(input);
await retry.abort();
await expect(readdir(path.join(workspace, "provider-evidence/untrusted")))
.resolves.toEqual([]);
await expect(readdir(path.join(workspace, "provider-evidence")))
.resolves.toEqual(["untrusted"]);
});
it("exits nonzero after cleanup when the diagnostic stderr pipe is closed", async () => {
const {
decodeProviderGuardianReady,
encodeProviderGuardianGuard,
} = await import("../../scripts/lib/provider-guardian-protocol.ts");
const workspace = await createWorkspace("provider-guardian-closed-stderr-");
const evidenceRoot = path.join(workspace, "provider-evidence");
const rawDirectory = path.join(evidenceRoot, "untrusted");
const nonce = Buffer.alloc(32, 0x63);
const child = spawnGuardian(workspace, nonce);
const completion = waitForChild(child);
const readyPayload = readFrame(child.stdout!);
child.stdin!.write(encodeProviderGuardianGuard({
kind: "vulnerability",
nonce,
deadlineEpochMs: Date.now() + 3_000,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "closed-stderr guardian READY"),
nonce,
);
child.stderr!.destroy();
await new Promise<void>((resolve) => child.stderr!.once("close", resolve));
child.stdin!.end();
await expect(within(completion, 1_000, "closed-stderr guardian exit"))
.resolves.toEqual({ code: 125, signal: null });
await expect(lstat(path.join(rawDirectory, "vulnerability-report.json")))
.rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(path.join(evidenceRoot, ready.sealedTempLeaf)))
.rejects.toMatchObject({ code: "ENOENT" });
});
it("expires an uncommitted lease only after cleaning every owned object", async () => {
const {
decodeProviderGuardianReady,
encodeProviderGuardianGuard,
} = await import("../../scripts/lib/provider-guardian-protocol.ts");
const workspace = await createWorkspace("provider-guardian-deadline-v2-");
const evidenceRoot = path.join(workspace, "provider-evidence");
const rawPath = path.join(evidenceRoot, "untrusted/vulnerability-report.json");
const nonce = Buffer.alloc(32, 0x58);
const child = spawnGuardian(workspace, nonce);
const completion = waitForChild(child);
const readyPayload = readFrame(child.stdout!);
child.stdin!.write(encodeProviderGuardianGuard({
kind: "vulnerability",
nonce,
deadlineEpochMs: Date.now() + 350,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "deadline guardian READY"),
nonce,
);
const sealedTempPath = path.join(evidenceRoot, ready.sealedTempLeaf);
await expect(within(completion, 1_000, "guardian deadline"))
.resolves.toEqual({ code: null, signal: "SIGKILL" });
await expect(lstat(rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(sealedTempPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(readdir(evidenceRoot)).resolves.toEqual(["untrusted"]);
}, PROCESS_HEAVY_TIMEOUT_MS);
it("still publishes near the lease deadline when post-processing completes in time", async () => {
const { startProviderGuardian } = await import(
"../../scripts/lib/provider-guardian-client.ts"
);
const workspace = await createWorkspace("provider-guardian-near-timeout-");
const lease = await startProviderGuardian({
kind: "vulnerability",
workspaceRoot: workspace,
leaseMs: 2_000,
guardianScript: path.resolve("scripts/lib/provider-raw-guardian.ts"),
});
await new Promise<void>((resolve) => setTimeout(resolve, 1_000));
const bytes = Buffer.from('{"validated":"near-timeout"}\n');
await lease.publish(bytes);
await lease.commit();
await expect(lstat(lease.rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(readFile(lease.sealedPath)).resolves.toEqual(bytes);
});
it("signals guardian exit only while the provider scope latch is active", async () => {
const lifecycle = await import("../../scripts/lib/provider-guardian-client.ts") as
Record<string, unknown>;
expect(lifecycle.createProviderScopeGuardianLatch).toBeTypeOf("function");
const createLatch = lifecycle.createProviderScopeGuardianLatch as (
guardianExit: Promise<Error>,
) => Readonly<{
activeFailure: Promise<Error>;
close(): Promise<void>;
failure(): Error | undefined;
}>;
let failAtBoundary!: (error: Error) => void;
const boundaryExit = new Promise<Error>((resolve) => { failAtBoundary = resolve; });
const boundary = createLatch(boundaryExit);
const boundaryError = new Error("guardian died at the collection boundary");
failAtBoundary(boundaryError);
await boundary.close();
await expect(boundary.activeFailure).resolves.toBe(boundaryError);
expect(boundary.failure()).toBe(boundaryError);
let failActive!: (error: Error) => void;
const activeExit = new Promise<Error>((resolve) => { failActive = resolve; });
const active = createLatch(activeExit);
const activeError = new Error("guardian died while scope active");
failActive(activeError);
await expect(active.activeFailure).resolves.toBe(activeError);
expect(active.failure()).toBe(activeError);
let failLate!: (error: Error) => void;
const lateExit = new Promise<Error>((resolve) => { failLate = resolve; });
const late = createLatch(lateExit);
await late.close();
const lateError = new Error("guardian died after scope collection");
failLate(lateError);
await Promise.resolve();
expect(late.failure()).toBe(lateError);
await expect(Promise.race([
late.activeFailure.then(() => "active"),
new Promise<string>((resolve) => setTimeout(() => resolve("inactive"), 25)),
])).resolves.toBe("inactive");
});
it("rejects a guardian lease whose derived sealed path differs from the configured target", async () => {
const lifecycle = await import("../../scripts/lib/provider-guardian-client.ts") as
Record<string, unknown>;
expect(lifecycle.assertProviderGuardianLeasePaths).toBeTypeOf("function");
const assertPaths = lifecycle.assertProviderGuardianLeasePaths as (
lease: Readonly<{ rawPath: string; sealedPath: string }>,
expected: Readonly<{ rawPath: string; sealedPath: string }>,
) => void;
const canonical = {
rawPath: "/workspace/provider-evidence/untrusted/vulnerability-report.json",
sealedPath: "/workspace/provider-evidence/vulnerability-report.json",
};
expect(() => assertPaths(canonical, canonical)).not.toThrow();
expect(() => assertPaths(canonical, {
...canonical,
sealedPath: "/workspace/provider-evidence/configured-alias.json",
})).toThrow(/noncanonical sealed path/u);
});
});
async function createWorkspace(prefix: string): Promise<string> {
const workspace = await mkdtemp(path.join(tmpdir(), prefix));
roots.push(workspace);
await mkdir(path.join(workspace, "provider-evidence/untrusted"), { recursive: true });
return workspace;
}
async function createStalledGuardianFixture(
workspace: string,
): Promise<Readonly<{ guardianScript: string; markerPath: string }>> {
const guardianScript = path.join(workspace, "stalled-guardian.mjs");
const markerPath = path.join(workspace, "guardian-spawned");
await writeFile(guardianScript, [
'import { writeFileSync } from "node:fs";',
'import path from "node:path";',
'writeFileSync(path.join(process.cwd(), "guardian-spawned"), String(process.pid));',
"setInterval(() => undefined, 1_000);",
"",
].join("\n"));
return { guardianScript, markerPath };
}
async function createPausedGuardianFixture(workspace: string): Promise<string> {
const guardianScript = path.join(workspace, "paused-guardian.mjs");
const realGuardian = pathToFileURL(
path.resolve("scripts/lib/provider-raw-guardian.ts"),
).href;
await writeFile(guardianScript, [
'process.kill(process.pid, "SIGSTOP");',
`await import(${JSON.stringify(realGuardian)});`,
"",
].join("\n"));
return guardianScript;
}
async function waitForFile(target: string): Promise<Buffer> {
const deadline = Date.now() + 1_000;
while (Date.now() <= deadline) {
try {
return await readFile(target);
} catch (error) {
if (!hasErrorCode(error, "ENOENT")) throw error;
}
await new Promise<void>((resolve) => setTimeout(resolve, 5));
}
throw new Error(`file did not appear: ${target}`);
}
type PublishedGuardian = Readonly<{
child: ReturnType<typeof spawn>;
completion: Promise<Readonly<{
code: number | null;
signal: NodeJS.Signals | null;
}>>;
rawPath: string;
sealedPath: string;
sealedTempPath: string;
}>;
async function establishPublishedGuardian(
workspace: string,
nonce: Buffer,
): Promise<PublishedGuardian> {
const {
decodeProviderGuardianPublished,
decodeProviderGuardianReady,
encodeProviderGuardianGuard,
encodeProviderGuardianPublish,
} = await import("../../scripts/lib/provider-guardian-protocol.ts");
const evidenceRoot = path.join(workspace, "provider-evidence");
const rawPath = path.join(evidenceRoot, "untrusted/vulnerability-report.json");
const sealedPath = path.join(evidenceRoot, "vulnerability-report.json");
const child = spawnGuardian(workspace, nonce);
const completion = waitForChild(child);
const readyPayload = readFrame(child.stdout!);
child.stdin!.write(encodeProviderGuardianGuard({
kind: "vulnerability",
nonce,
deadlineEpochMs: Date.now() + 3_000,
}));
const ready = decodeProviderGuardianReady(
await within(readyPayload, 1_000, "guardian READY"),
nonce,
);
const sealedTempPath = path.join(evidenceRoot, ready.sealedTempLeaf);
const bytes = Buffer.from('{"validated":"pending-commit"}\n');
const handle = await open(sealedTempPath, constants.O_WRONLY | constants.O_NOFOLLOW);
try {
await handle.writeFile(bytes);
await handle.chmod(0o400);
await handle.sync();
} finally {
await handle.close();
}
const publishedPayload = readFrame(child.stdout!);
child.stdin!.write(encodeProviderGuardianPublish({
nonce,
sealedDev: ready.sealedDev,
sealedIno: ready.sealedIno,
size: bytes.byteLength,
sha256: createHash("sha256").update(bytes).digest("hex"),
}));
decodeProviderGuardianPublished(
await within(publishedPayload, 1_000, "guardian PUBLISHED"),
nonce,
{ dev: ready.sealedDev, ino: ready.sealedIno },
);
return { child, completion, rawPath, sealedPath, sealedTempPath };
}
async function assertTransactionAbsent(transaction: PublishedGuardian): Promise<void> {
await expect(lstat(transaction.rawPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(transaction.sealedPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(lstat(transaction.sealedTempPath)).rejects.toMatchObject({ code: "ENOENT" });
}
async function readFrame(stream: NodeJS.ReadableStream): Promise<Buffer> {
return await new Promise((resolve, reject) => {
let pending = Buffer.alloc(0);
const onData = (chunk: Buffer | string): void => {
pending = Buffer.concat([pending, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
if (pending.byteLength < 4) return;
const payloadBytes = pending.readUInt32BE(0);
if (pending.byteLength < payloadBytes + 4) return;
cleanup();
if (pending.byteLength !== payloadBytes + 4) {
reject(new Error("guardian acknowledgement contained trailing bytes"));
return;
}
resolve(pending.subarray(4));
};
const onEnd = (): void => {
cleanup();
reject(new Error("guardian closed before acknowledgement"));
};
const cleanup = (): void => {
stream.removeListener("data", onData);
stream.removeListener("end", onEnd);
};
stream.on("data", onData);
stream.once("end", onEnd);
});
}
async function waitForChild(
child: ReturnType<typeof spawn>,
): Promise<Readonly<{ code: number | null; signal: NodeJS.Signals | null }>> {
return await new Promise((resolve, reject) => {
child.once("error", reject);
child.once("close", (code, signal) => resolve({ code, signal }));
});
}
function spawnGuardian(
workspace: string,
nonce: Buffer = Buffer.alloc(32, 0x7d),
): ReturnType<typeof spawn> {
const evidenceRoot = path.join(workspace, "provider-evidence");
const rawDirectory = path.join(evidenceRoot, "untrusted");
const noncePrefix = nonce.subarray(0, 16).toString("hex");
const openedFds: number[] = [];
try {
openedFds.push(openSync(
rawDirectory,
constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
));
openedFds.push(openSync(
evidenceRoot,
constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
));
openedFds.push(openSync(
path.join(
rawDirectory,
`.vulnerability-report.json.guardian-${noncePrefix}.raw.tmp`,
),
constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
0o600,
));
openedFds.push(openSync(
path.join(
evidenceRoot,
`.vulnerability-report.json.guardian-${noncePrefix}.tmp`,
),
constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
0o600,
));
return spawn(process.execPath, [path.resolve("scripts/lib/provider-raw-guardian.ts")], {
cwd: workspace,
env: {},
stdio: ["pipe", "pipe", "pipe", ...openedFds],
});
} finally {
for (const fd of openedFds) closeSync(fd);
}
}
function spawnGuardianWithPrivateModes(
workspace: string,
nonce: Buffer,
modes: Readonly<{ rawMode: number; sealedMode: number }>,
): Readonly<{
child: ReturnType<typeof spawn>;
rawStagingPath: string;
sealedTempPath: string;
}> {
const evidenceRoot = path.join(workspace, "provider-evidence");
const rawDirectory = path.join(evidenceRoot, "untrusted");
const noncePrefix = nonce.subarray(0, 16).toString("hex");
const rawStagingPath = path.join(
rawDirectory,
`.vulnerability-report.json.guardian-${noncePrefix}.raw.tmp`,
);
const sealedTempPath = path.join(
evidenceRoot,
`.vulnerability-report.json.guardian-${noncePrefix}.tmp`,
);
const openedFds: number[] = [];
try {
openedFds.push(openSync(
rawDirectory,
constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
));
openedFds.push(openSync(
evidenceRoot,
constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
));
const rawStagingFd = openSync(
rawStagingPath,
constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
0o600,
);
openedFds.push(rawStagingFd);
fchmodSync(rawStagingFd, modes.rawMode);
const sealedTempFd = openSync(
sealedTempPath,
constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
0o600,
);
openedFds.push(sealedTempFd);
fchmodSync(sealedTempFd, modes.sealedMode);
const child = spawn(process.execPath, [path.resolve("scripts/lib/provider-raw-guardian.ts")], {
cwd: workspace,
env: {},
stdio: ["pipe", "pipe", "pipe", ...openedFds],
});
return { child, rawStagingPath, sealedTempPath };
} finally {
for (const fd of openedFds) closeSync(fd);
}
}
async function waitForDirectoryEntry(
directory: string,
expectedLeaf: string,
onEntry: () => void,
): Promise<void> {
await new Promise<void>((resolve, reject) => {
const watcher = watch(directory, (_event, filename) => {
if (filename?.toString() !== expectedLeaf) return;
try {
onEntry();
resolve();
} catch (error) {
reject(error);
} finally {
watcher.close();
}
});
watcher.once("error", (error) => {
watcher.close();
reject(error);
});
});
}
async function within<T>(operation: Promise<T>, timeoutMs: number, label: string): Promise<T> {
let timer: NodeJS.Timeout | undefined;
try {
return await Promise.race([
operation,
new Promise<never>((_resolve, reject) => {
timer = setTimeout(() => reject(new Error(`${label} timed out`)), timeoutMs);
}),
]);
} finally {
if (timer) clearTimeout(timer);
}
}
async function directChildPids(parentPid: number): Promise<Set<number>> {
const value = await readFile(`/proc/${parentPid}/task/${parentPid}/children`, "utf8");
return new Set(value.trim().split(/\s+/u).filter(Boolean).map(Number));
}
async function waitForNewDirectChild(previous: ReadonlySet<number>): Promise<number> {
const deadline = Date.now() + 1_000;
while (Date.now() <= deadline) {
for (const pid of await directChildPids(process.pid)) {
if (!previous.has(pid)) return pid;
}
await new Promise<void>((resolve) => setTimeout(resolve, 5));
}
throw new Error("provider guardian child did not appear");
}
async function waitForStoppedProcess(pid: number): Promise<void> {
const deadline = Date.now() + 1_000;
while (Date.now() <= deadline) {
const status = await readFile(`/proc/${pid}/status`, "utf8");
if (/^State:\s+T/mu.test(status)) return;
await new Promise<void>((resolve) => setTimeout(resolve, 5));
}
throw new Error("provider guardian child did not stop before bootstrap");
}
function hasErrorCode(error: unknown, code: string): boolean {
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
}