Add finite owned public HTTPS origin handoff for chat evals

This commit is contained in:
Dotta committed 2026-10-27 01:31:43 -05:00
1 parent a0c5ad9805
commit ea6dc42d2b
6 files changed
+264 -5

No files matched your search

+5 -2
View File
@@ -1,3 +1,4 @@
import { publicOriginFingerprintIdentity } from "./chat-public-origin.js";
import { databaseFingerprintIdentity } from "./docker-postgres.js";
import { verifyRetainedChatOracleIdentity } from "./chat-private-inputs.js";
import { installedChatFingerprintIdentity, installedChatScenarioIds } from "./chat-installed-lane.js";
@@ -112,11 +113,13 @@ export function chatCatalogCaseFingerprint(descriptor: ChatCatalogDescriptor, se
const attachmentFormatter = descriptor.queryAttachments?.length ? ["ui/src/components/IssueChatThread.tsx", "ui/src/components/task-chat/TaskChatComposer.tsx"].map(file => ({
file, sha256: hash(readFileSync(new URL(`../../${file}`, import.meta.url), "utf8")),
})) : null;
const originIdentity = publicOriginFingerprintIdentity((selectedExecution as { executionId: string }).executionId, descriptor.scenarioId);
const originReaders = originIdentity.selected ? ["server/src/config.ts", "server/src/auth/better-auth.ts", "server/src/routes/tool-access.ts", "server/src/services/chat-task-url.ts", "server/src/services/muse-setup.ts", "server/src/services/workspace-runtime.ts"] : [];
const result = fingerprintChatDependencyClosure({ root,
entries: [selectedModule, "tests/runner-e2e/chat-catalog-flow.ts", "tests/runner-e2e/chat-catalog-fingerprint.ts", "tests/runner-e2e/chat-catalog-dynamic-resolutions.json",
"tests/runner-e2e/chat-feature-flow.ts", "tests/runner-e2e/runner.spec.ts", "tests/runner-e2e/launch.ts",
"tests/runner-e2e/chat-scenarios/defaults.yaml", ...descriptor.sourceFiles.map(file => `tests/runner-e2e/${file}`)],
selected: { descriptor, selectedExecution, lane, attachmentFormatter, privateInputRequirements: chatPrivateInputRequirements(descriptor), privateInputNameIdentity, installedIdentity, scenario: { id, capabilities, context, query, expected_outcome } },
"tests/runner-e2e/chat-scenarios/defaults.yaml", ...originReaders, ...descriptor.sourceFiles.map(file => `tests/runner-e2e/${file}`)],
selected: { descriptor, selectedExecution, lane, publicOriginIdentity: originIdentity, attachmentFormatter, privateInputRequirements: chatPrivateInputRequirements(descriptor), privateInputNameIdentity, installedIdentity, scenario: { id, capabilities, context, query, expected_outcome } },
reviewedDynamicResolutions,
selectedDispatchFile: "tests/runner-e2e/chat-catalog-registry.ts",
normalizedMetadata: { "tests/runner-e2e/chat-catalog-descriptors.json": descriptor,
@@ -0,0 +1,62 @@
import { mkdtemp, writeFile, chmod, rm, symlink, realpath } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { publicHttpsOrigin, publicOriginReceiptMatches, publicProxySocketMatches, publicOriginReleaseMatches, samePublicProxyIdentity, publicOriginServerOverrides, publicOriginFingerprintIdentity, prepareOwnedPublicOrigin, readValidatedPublicOriginReceipt, type PublicOriginRequest, type ValidatedPublicOriginReceipt } from "./chat-public-origin.js";
const request: PublicOriginRequest = { schema: "paperclip.chat-public-origin-request.v1", executionId: "chat-catalog.agents.existing-grok-bot.codex", scenarioId: "agents.existing-grok-bot", attemptId: "a".repeat(32), backendOrigin: "http://127.0.0.1:42123", nonce: "b".repeat(48) };
const receipt: ValidatedPublicOriginReceipt = { ...request, schema: "paperclip.chat-public-origin.v1", publicOrigin: "https://attempt.example.test", routeVerifiedAt: "2026-10-27T12:00:00.000Z", proxy: { pid: 999999, uid: process.getuid!(), startedAt: "2026-10-27T11:59:00.000Z", executable: "/owned/proxy", executableSha256: "c".repeat(64), commandSha256: "d".repeat(64), workingDirectory: "/owned/private/control" } };
describe("finite owned public-origin handoff", () => {
it("accepts exact independently recorded attempt binding, rejects stale attempt/nonce/source", () => {
expect(publicOriginReceiptMatches(receipt, request)).toBe(true);
for (const mutation of [{ attemptId: "e".repeat(32) }, { nonce: "e".repeat(48) }, { backendOrigin: "http://127.0.0.1:42124" }, { executionId: "another.agents.existing-grok-bot" }, { scenarioId: "goals.list" }]) expect(publicOriginReceiptMatches({ ...receipt, ...mutation }, request)).toBe(false);
expect(publicOriginReceiptMatches({ ...receipt, undeclared: "input" } as typeof receipt, request)).toBe(false);
});
it("forbids HTTP, embedded credentials, alternate paths and loopback public origins", () => {
expect(publicHttpsOrigin(receipt.publicOrigin)).toBe(true);
for (const origin of ["http://attempt.example.test", "https://user:password@attempt.example.test", "https://attempt.example.test/path", "https://127.0.0.1", "https://localhost", "https://sub.localhost", "not a URL"]) expect(publicHttpsOrigin(origin)).toBe(false);
});
it("pins process start, owner, executable, command and owned cwd rather than PID alone", () => {
expect(samePublicProxyIdentity({ ...receipt.proxy }, receipt.proxy)).toBe(true);
for (const mutation of [{ startedAt: "2026-10-27T12:00:01.000Z" }, { uid: receipt.proxy.uid + 1 }, { executableSha256: "e".repeat(64) }, { commandSha256: "f".repeat(64) }, { workingDirectory: "/other" }]) expect(samePublicProxyIdentity({ ...receipt.proxy, ...mutation }, receipt.proxy)).toBe(false);
expect(samePublicProxyIdentity(undefined, receipt.proxy)).toBe(false);
expect(publicOriginReceiptMatches({ ...receipt, proxy: { ...receipt.proxy, pid: process.pid } }, request)).toBe(false);
});
it("requires the actual proxy's exact established loopback socket", () => {
expect(publicProxySocketMatches("p9123\nn127.0.0.1:53555->127.0.0.1:42123\n", 53555, 42123)).toBe(true);
for (const line of ["n127.0.0.1:53556->127.0.0.1:42123", "n127.0.0.1:53555->127.0.0.1:42124", "n10.0.0.1:53555->127.0.0.1:42123", "n127.0.0.1:42123->127.0.0.1:53555"]) expect(publicProxySocketMatches(line, 53555, 42123)).toBe(false);
});
it("rejects release without exact route and proxy teardown attestation", () => {
const release = { schema: "paperclip.chat-public-origin-release.v1", executionId: request.executionId, attemptId: request.attemptId, nonce: request.nonce, publicRouteReleased: true, proxyReleased: true };
expect(publicOriginReleaseMatches(release, request)).toBe(true);
for (const mutation of [{ attemptId: "f".repeat(32) }, { nonce: "f".repeat(48) }, { publicRouteReleased: false }, { proxyReleased: false }, { extra: true }]) expect(publicOriginReleaseMatches({ ...release, ...mutation }, request)).toBe(false);
});
it("refuses forged/stale private receipts before inspecting an unrelated process", async () => {
const directory = await realpath(await mkdtemp(path.join(tmpdir(), "origin-control-")));
try {
await chmod(directory, 0o700);
const filename = path.join(directory, "public-origin.json");
await writeFile(filename, JSON.stringify({ ...receipt, attemptId: "f".repeat(32) }), { mode: 0o600 });
await expect(readValidatedPublicOriginReceipt({ ...request, receiptPath: filename })).rejects.toThrow("no longer belongs");
await chmod(filename, 0o644);
await expect(readValidatedPublicOriginReceipt({ ...request, receiptPath: filename })).rejects.toThrow("Invalid private");
const alias = path.join(directory, "alias.json"); await symlink(filename, alias);
await expect(readValidatedPublicOriginReceipt({ ...request, receiptPath: alias })).rejects.toThrow();
} finally { await rm(directory, { recursive: true, force: true }); }
});
it("refuses a configured public route on a nonallowlisted case before writing a request", async () => {
const directory = await realpath(await mkdtemp(path.join(tmpdir(), "origin-control-")));
try {
await chmod(directory, 0o700);
await expect(prepareOwnedPublicOrigin({ ...request, executionId: "goals.list", scenarioId: "goals.list", controlDirectory: directory, temporaryRoot: directory, privateSecrets: [] })).rejects.toThrow("forbidden outside");
} finally { await rm(directory, { recursive: true, force: true }); }
});
it("preserves default source isolation and changes only finite public advertisements", async () => {
expect(await prepareOwnedPublicOrigin({ ...request, temporaryRoot: "/never-used", privateSecrets: [] })).toBeUndefined();
expect(publicOriginServerOverrides(undefined)).toEqual({});
expect(publicOriginServerOverrides(receipt)).toEqual({ PAPERCLIP_PUBLIC_URL: receipt.publicOrigin, PAPERCLIP_AUTH_PUBLIC_BASE_URL: receipt.publicOrigin, PAPERCLIP_MANAGED_RUNTIME_PUBLIC_URL: receipt.publicOrigin, PAPERCLIP_ALLOWED_HOSTNAMES: "127.0.0.1" });
expect(publicOriginFingerprintIdentity("goals.list", "goals.list", {}).selected).toBe(false);
const identity = publicOriginFingerprintIdentity(request.executionId, request.scenarioId, { PAPERCLIP_CHAT_EVAL_PUBLIC_ORIGIN_CONTROL_DIR: "/private/never-retained" });
expect(identity.selected && identity.configured).toBe(true);
expect(JSON.stringify(identity)).not.toContain("/private/never-retained");
});
});
+148
View File
@@ -0,0 +1,148 @@
import { constants } from "node:fs";
import { lstat, open, realpath, readFile, writeFile } from "node:fs/promises";
import { createHash, randomBytes } from "node:crypto";
import { createServer } from "node:http";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import path from "node:path";
export class PublicOriginCleanupError extends Error {}
export const PUBLIC_ORIGIN_CONTROL_ENV = "PAPERCLIP_CHAT_EVAL_PUBLIC_ORIGIN_CONTROL_DIR";
export const PUBLIC_ORIGIN_RECEIPT_ENV = "PAPERCLIP_RUNNER_E2E_OWNED_PUBLIC_ORIGIN_RECEIPT";
export const ownedPublicOriginScenarios = ["agents.existing-grok-bot", "assistant-connections.connect-external-assistant", "connection-setup.notion-missing", "connection-setup.notion-wrong-account", "connection-setup.notion-expired", "connection-setup.return-from-login", "connection-setup.save-and-resume"] as const;
export interface PublicOriginIdentity { pid: number; uid: number; startedAt: string; executable: string; executableSha256: string; commandSha256: string; workingDirectory: string }
export interface PublicOriginRequest { schema: "paperclip.chat-public-origin-request.v1"; executionId: string; scenarioId: string; attemptId: string; backendOrigin: string; nonce: string }
export interface ValidatedPublicOriginReceipt { schema: "paperclip.chat-public-origin.v1"; executionId: string; scenarioId: string; attemptId: string; backendOrigin: string; publicOrigin: string; nonce: string; proxy: PublicOriginIdentity; routeVerifiedAt: string }
const hash = (value: string | Buffer) => createHash("sha256").update(value).digest("hex");
const exactKeys = (value: unknown, keys: readonly string[]) => !!value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).sort().join() === [...keys].sort().join();
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
export function publicHttpsOrigin(value: string) { try { const u = new URL(value); return u.protocol === "https:" && u.origin === value && !u.username && !u.password && !["localhost", "127.0.0.1", "[::1]"].includes(u.hostname) && !u.hostname.endsWith(".localhost"); } catch { return false; } }
function validRequest(r: PublicOriginRequest) { try { const url = new URL(r.backendOrigin); return exactKeys(r, ["schema", "executionId", "scenarioId", "attemptId", "backendOrigin", "nonce"]) && r.schema === "paperclip.chat-public-origin-request.v1" && ownedPublicOriginScenarios.includes(r.scenarioId as typeof ownedPublicOriginScenarios[number]) && r.executionId.includes(r.scenarioId) && /^[a-f0-9]{32}$/.test(r.attemptId) && /^[a-f0-9]{48}$/.test(r.nonce) && url.protocol === "http:" && url.hostname === "127.0.0.1" && url.origin === r.backendOrigin && Number(url.port) > 1024; } catch { return false; } }
export function publicOriginReceiptMatches(r: ValidatedPublicOriginReceipt, expected: Pick<PublicOriginRequest, "executionId" | "attemptId" | "backendOrigin"> & Partial<Pick<PublicOriginRequest, "scenarioId" | "nonce">>) {
const p = r?.proxy;
return exactKeys(r, ["schema", "executionId", "scenarioId", "attemptId", "backendOrigin", "publicOrigin", "nonce", "proxy", "routeVerifiedAt"]) && r.schema === "paperclip.chat-public-origin.v1" && validRequest({ schema: "paperclip.chat-public-origin-request.v1", executionId: r.executionId, scenarioId: r.scenarioId, attemptId: r.attemptId, backendOrigin: r.backendOrigin, nonce: r.nonce })
&& r.executionId === expected.executionId && r.attemptId === expected.attemptId && r.backendOrigin === expected.backendOrigin && (!expected.scenarioId || r.scenarioId === expected.scenarioId) && (!expected.nonce || r.nonce === expected.nonce)
&& publicHttpsOrigin(r.publicOrigin) && Number.isFinite(Date.parse(r.routeVerifiedAt)) && exactKeys(p, ["pid", "uid", "startedAt", "executable", "executableSha256", "commandSha256", "workingDirectory"])
&& Number.isSafeInteger(p.pid) && p.pid > 1 && p.pid !== process.pid && p.uid === process.getuid?.() && Number.isFinite(Date.parse(p.startedAt)) && path.isAbsolute(p.executable) && path.isAbsolute(p.workingDirectory) && /^[a-f0-9]{64}$/.test(p.executableSha256) && /^[a-f0-9]{64}$/.test(p.commandSha256);
}
async function privateDirectory(directory: string) { const info = await lstat(directory); if (!info.isDirectory() || info.isSymbolicLink() || info.mode & 0o077 || info.uid !== process.getuid?.() || await realpath(directory) !== directory) throw new Error("Public origin control requires an owned canonical private0700 directory"); }
async function privateJson<T>(filename: string): Promise<T> {
await privateDirectory(path.dirname(filename));
const fd = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW);
try { const before = await fd.stat(); if (!before.isFile() || before.nlink !== 1 || before.mode & 0o077 || before.uid !== process.getuid?.() || before.size > 32768 || await realpath(filename) !== filename) throw new Error("Invalid private public-origin receipt file");
const bytes = await fd.readFile(), after = await fd.stat(), named = await lstat(filename);
if (before.ino !== after.ino || before.dev !== after.dev || before.size !== after.size || before.mtimeMs !== after.mtimeMs || named.ino !== before.ino || named.dev !== before.dev) throw new Error("Public-origin receipt changed during read");
return JSON.parse(bytes.toString("utf8")) as T;
} finally { await fd.close(); }
}
/** No raw process arguments or provider credentials leave this local reader. */
export async function readPublicProxyIdentity(pid: number): Promise<PublicOriginIdentity | undefined> {
if (!Number.isSafeInteger(pid) || pid <= 1 || pid === process.pid || !process.getuid) throw new Error("Invalid public proxy process identity");
try { process.kill(pid, 0); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ESRCH") return undefined; throw new Error("Public proxy ownership could not be independently read"); }
const field = async (name: string) => (await promisify(execFile)("/bin/ps", ["-p", String(pid), "-o", `${name}=`], { timeout: 5000, maxBuffer: 65536 })).stdout.trim();
try {
const [uid, started, command, executableName] = await Promise.all([field("uid"), field("lstart"), field("args"), field("comm")]);
if (!command || !Number.isFinite(Date.parse(started))) throw new Error("Missing process identity");
const executable = await realpath(executableName), stat = await lstat(executable); if (!stat.isFile() || stat.size > 134217728) throw new Error("Unbounded public proxy executable");
const workingDirectory = process.platform === "linux" ? await realpath(`/proc/${pid}/cwd`) : (await promisify(execFile)("lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], { timeout: 5000, maxBuffer: 16384 })).stdout.split("\n").find(line => line.startsWith("n"))?.slice(1);
if (!workingDirectory) throw new Error("Missing actual proxy working directory");
return { pid, uid: Number(uid), startedAt: new Date(started).toISOString(), executable, executableSha256: hash(await readFile(executable)), commandSha256: hash(command), workingDirectory: await realpath(workingDirectory) };
} catch { throw new Error("Public proxy identity inspection failed; private process diagnostics withheld"); }
}
export const samePublicProxyIdentity = (a: PublicOriginIdentity | undefined, b: PublicOriginIdentity) => !!a && Object.keys(b).every(key => a[key as keyof PublicOriginIdentity] === b[key as keyof PublicOriginIdentity]);
/** Finite coordinator handoff. Caller owns configuring/releasing the actual
* already-authorized route/process; this helper never creates a public tunnel. */
export async function publishPublicOriginRoute(input: { controlDirectory: string; publicOrigin: string; proxyPid: number }) {
const request = await privateJson<PublicOriginRequest>(path.join(input.controlDirectory, "request.json"));
if (!validRequest(request) || !publicHttpsOrigin(input.publicOrigin)) throw new Error("Invalid selected owned public route request");
const proxy = await readPublicProxyIdentity(input.proxyPid);
if (!proxy || proxy.uid !== process.getuid?.() || !proxy.workingDirectory.startsWith(`${input.controlDirectory}${path.sep}`) && proxy.workingDirectory !== input.controlDirectory || !samePublicProxyIdentity(await readPublicProxyIdentity(input.proxyPid), proxy)) throw new Error("Public proxy must be the exact coordinator-owned process rooted in the private handoff directory");
await writeFile(path.join(input.controlDirectory, "route.json"), JSON.stringify({ ...request, publicOrigin: input.publicOrigin, proxy }), { mode: 0o600, flag: "wx" });
return { published: true, executionId: request.executionId, attemptId: request.attemptId };
}
/** After actual separately owned proxy/route teardown, publish its closed
* completion. This helper never signals a process. The launcher also rechecks. */
export async function publishPublicOriginRelease(input: { controlDirectory: string; publicRouteReleased: true }) {
const request = await privateJson<PublicOriginRequest>(path.join(input.controlDirectory, "request.json"));
const release = await privateJson<PublicOriginRequest & { backendShutdown: boolean }>(path.join(input.controlDirectory, "release-request.json"));
const route = await privateJson<PublicOriginRequest & { publicOrigin: string; proxy: PublicOriginIdentity }>(path.join(input.controlDirectory, "route.json"));
if (!validRequest(request) || release.backendShutdown !== true || !exactKeys(release, [...Object.keys(request), "backendShutdown"]) || Object.entries(request).some(([key, value]) => release[key as keyof typeof release] !== value || route[key as keyof typeof route] !== value) || !exactKeys(route, [...Object.keys(request), "publicOrigin", "proxy"]) || !publicHttpsOrigin(route.publicOrigin) || input.publicRouteReleased !== true) throw new Error("Public-origin release does not belong to the completed backend attempt");
const live = await readPublicProxyIdentity(route.proxy.pid);
if (live?.startedAt === route.proxy.startedAt) throw new Error("The actual owned public proxy has not exited");
const backend = await fetch(`${request.backendOrigin}/api/health`, { signal: AbortSignal.timeout(2000), redirect: "error" }).catch(() => undefined);
if (backend) { await backend.body?.cancel(); throw new Error("Refusing a public route release receipt before backend shutdown"); }
await writeFile(path.join(input.controlDirectory, "released.json"), JSON.stringify({ schema: "paperclip.chat-public-origin-release.v1", executionId: request.executionId, attemptId: request.attemptId, nonce: request.nonce, publicRouteReleased: true, proxyReleased: true }), { mode: 0o600, flag: "wx" });
return { released: true, executionId: request.executionId, attemptId: request.attemptId };
}
export async function readValidatedPublicOriginReceipt(input: { receiptPath: string; executionId: string; attemptId: string; backendOrigin: string }): Promise<ValidatedPublicOriginReceipt> {
const receipt = await privateJson<ValidatedPublicOriginReceipt>(input.receiptPath);
if (!publicOriginReceiptMatches(receipt, input) || !samePublicProxyIdentity(await readPublicProxyIdentity(receipt.proxy.pid), receipt.proxy)) throw new Error("Validated public origin no longer belongs to this exact live attempt/proxy");
return receipt;
}
async function responseFile<T>(filename: string, deadline: number): Promise<T> { while (Date.now() < deadline) { try { return await privateJson<T>(filename); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } await sleep(250); } throw new Error("Owned public-origin coordinator handoff timed out before backend exposure"); }
export function publicProxySocketMatches(text: string, remotePort: number, backendPort: number) {
return Number.isSafeInteger(remotePort) && remotePort > 0 && Number.isSafeInteger(backendPort) && backendPort > 1024 && text.split("\n").includes(`n127.0.0.1:${remotePort}->127.0.0.1:${backendPort}`);
}
export function publicOriginReleaseMatches(release: unknown, request: PublicOriginRequest) {
const r = release as Record<string, unknown> | undefined;
return exactKeys(r, ["schema", "executionId", "attemptId", "nonce", "publicRouteReleased", "proxyReleased"]) && r?.schema === "paperclip.chat-public-origin-release.v1" && r.executionId === request.executionId && r.attemptId === request.attemptId && r.nonce === request.nonce && r.publicRouteReleased === true && r.proxyReleased === true;
}
async function boundedResponse(response: Response) {
const reader = response.body?.getReader(); if (!reader) return "";
const chunks: Uint8Array[] = []; let size = 0;
try { while (true) { const next = await reader.read(); if (next.done) break; size += next.value.byteLength; if (size > 256) throw new Error("Public-origin challenge response exceeded its finite bound"); chunks.push(next.value); } return Buffer.concat(chunks).toString("utf8"); }
finally { await reader.cancel(); }
}
async function confirmProxySocket(proxy: PublicOriginIdentity, remotePort: number, backendPort: number) {
const text = (await promisify(execFile)("lsof", ["-nP", "-a", "-p", String(proxy.pid), "-iTCP", "-sTCP:ESTABLISHED", "-Fn"], { timeout: 5000, maxBuffer: 65536 })).stdout;
if (!publicProxySocketMatches(text, remotePort, backendPort) || !samePublicProxyIdentity(await readPublicProxyIdentity(proxy.pid), proxy)) throw new Error("Actual public request did not traverse the exact owned proxy process/socket");
}
export async function prepareOwnedPublicOrigin(input: { controlDirectory?: string; executionId: string; scenarioId: string; attemptId: string; backendOrigin: string; temporaryRoot: string; privateSecrets: string[]; timeoutMs?: number }) {
if (!input.controlDirectory) return undefined;
const root = path.resolve(input.controlDirectory); await privateDirectory(root);
const request: PublicOriginRequest = { schema: "paperclip.chat-public-origin-request.v1", executionId: input.executionId, scenarioId: input.scenarioId, attemptId: input.attemptId, backendOrigin: input.backendOrigin, nonce: randomBytes(24).toString("hex") };
if (!validRequest(request)) throw new Error("Public origin handoff is forbidden outside its finite selected-case execution");
input.privateSecrets.push(request.nonce);
await writeFile(path.join(root, "request.json"), JSON.stringify(request), { mode: 0o600, flag: "wx" });
let receipt: ValidatedPublicOriginReceipt | undefined, released = false;
const close = async () => {
if (released) return;
const backend = await fetch(`${request.backendOrigin}/api/health`, { signal: AbortSignal.timeout(2000), redirect: "error" }).catch(() => undefined);
if (backend) { await backend.body?.cancel(); throw new Error("Public route cleanup requires independently observed backend shutdown"); }
await writeFile(path.join(root, "release-request.json"), JSON.stringify({ ...request, backendShutdown: true }), { mode: 0o600, flag: "wx" });
const release = await responseFile<{ schema: string; executionId: string; attemptId: string; nonce: string; publicRouteReleased: boolean; proxyReleased: boolean }>(path.join(root, "released.json"), Date.now() + (input.timeoutMs ?? 90000));
if (!publicOriginReleaseMatches(release, request) || !receipt) throw new Error("Owned public proxy release was not confirmed for this exact attempt");
const live = await readPublicProxyIdentity(receipt.proxy.pid); if (live && live.startedAt === receipt.proxy.startedAt) throw new Error("Owned per-attempt public proxy remains alive after reported release");
const response = await fetch(`${receipt.publicOrigin}/.well-known/paperclip-eval-route/${request.nonce}`, { redirect: "error", signal: AbortSignal.timeout(5000) }).catch(() => undefined);
if (response) { const body = await boundedResponse(response); if (body === request.nonce) throw new Error("Old public route remains reachable after reported release"); }
released = true;
};
try {
const route = await responseFile<PublicOriginRequest & { publicOrigin: string; proxy: PublicOriginIdentity }>(path.join(root, "route.json"), Date.now() + (input.timeoutMs ?? 90000));
if (!exactKeys(route, ["schema", "executionId", "scenarioId", "attemptId", "backendOrigin", "nonce", "publicOrigin", "proxy"]) || route.schema !== request.schema) throw new Error("Public proxy route receipt exceeds its exact request contract");
receipt = { schema: "paperclip.chat-public-origin.v1", executionId: route.executionId, scenarioId: route.scenarioId, attemptId: route.attemptId, backendOrigin: route.backendOrigin, nonce: route.nonce, publicOrigin: route.publicOrigin, proxy: route.proxy, routeVerifiedAt: new Date().toISOString() };
if (!publicOriginReceiptMatches(receipt, request) || !samePublicProxyIdentity(await readPublicProxyIdentity(receipt.proxy.pid), receipt.proxy) || !(receipt.proxy.workingDirectory === root || receipt.proxy.workingDirectory.startsWith(`${root}${path.sep}`))) throw new Error("Owned public proxy receipt has stale execution, route or process identity");
input.privateSecrets.push(receipt.publicOrigin);
const port = Number(new URL(input.backendOrigin).port); let routeObserved = false, requestClaimed = false;
const responder = createServer((req, res) => {
const expected = `/.well-known/paperclip-eval-route/${request.nonce}`;
if (requestClaimed || req.method !== "GET" || req.url !== expected || !["127.0.0.1", "::ffff:127.0.0.1"].includes(req.socket.remoteAddress ?? "") || !req.socket.remotePort) { res.writeHead(404); res.end(); return; }
requestClaimed = true;
void confirmProxySocket(receipt!.proxy, req.socket.remotePort, port).then(() => { routeObserved = true; res.writeHead(200, { "content-type": "text/plain", "cache-control": "no-store" }); res.end(request.nonce); }).catch(() => { res.writeHead(403); res.end(); });
});
responder.requestTimeout = 10000; responder.headersTimeout = 10000;
try {
await new Promise<void>((resolve, reject) => { responder.once("error", reject); responder.listen(port, "127.0.0.1", resolve); });
const response = await fetch(`${receipt.publicOrigin}/.well-known/paperclip-eval-route/${request.nonce}`, { redirect: "error", signal: AbortSignal.timeout(15000) });
if (!response.ok || await boundedResponse(response) !== request.nonce || !routeObserved) throw new Error("Actual HTTPS route failed its independently observed exact-process loopback challenge");
} finally { responder.closeAllConnections(); await new Promise<void>(resolve => responder.close(() => resolve())); }
if (!samePublicProxyIdentity(await readPublicProxyIdentity(receipt.proxy.pid), receipt.proxy)) throw new Error("Public proxy changed identity after route verification");
receipt.routeVerifiedAt = new Date().toISOString(); const filename = path.join(input.temporaryRoot, "public-origin.json"); await writeFile(filename, JSON.stringify(receipt), { mode: 0o600, flag: "wx" });
return { filename, receipt, close };
} catch (error) { try { await close(); } catch { throw new PublicOriginCleanupError("Public-origin preparation failed and owned proxy/route cleanup is unconfirmed; private control state retained"); } throw error; }
}
export function publicOriginServerOverrides(receipt: ValidatedPublicOriginReceipt | undefined) {
return receipt ? { PAPERCLIP_PUBLIC_URL: receipt.publicOrigin, PAPERCLIP_AUTH_PUBLIC_BASE_URL: receipt.publicOrigin, PAPERCLIP_MANAGED_RUNTIME_PUBLIC_URL: receipt.publicOrigin, PAPERCLIP_ALLOWED_HOSTNAMES: "127.0.0.1" } : {};
}
export function publicOriginFingerprintIdentity(executionId: string, scenarioId: string, env: NodeJS.ProcessEnv = process.env) {
return { selected: ownedPublicOriginScenarios.includes(scenarioId as typeof ownedPublicOriginScenarios[number]), configured: !!env[PUBLIC_ORIGIN_RECEIPT_ENV] || !!env[PUBLIC_ORIGIN_CONTROL_ENV], referenceNames: [PUBLIC_ORIGIN_CONTROL_ENV, PUBLIC_ORIGIN_RECEIPT_ENV], executionId, protocol: "private-attempt-request-exact-proxy-route-and-release-v1" };
}
+9 -1
View File
@@ -1,3 +1,4 @@
import { prepareOwnedPublicOrigin, PublicOriginCleanupError, PUBLIC_ORIGIN_CONTROL_ENV, PUBLIC_ORIGIN_RECEIPT_ENV } from "./chat-public-origin.js";
import { DATABASE_LANE_ENV, DOCKER_DATABASE_ENV, dockerDatabaseLaunchState, publicDockerDatabaseIdentity, selectedDatabaseLane, startOwnedDockerDatabase } from "./docker-postgres.js";
import { ARCHIVE_CONTROL_ENV, ARCHIVE_SCENARIO, startArchiveFaultControl } from "./chat-archive-fault-control.js";
import { abnormalFinalizationResult } from "./abnormal-finalization.js";
@@ -515,6 +516,7 @@ async function runAttempt(input: {
let processCleanupFailed = false;
let startupReceiptPath: string | undefined;
let dockerDatabase: Awaited<ReturnType<typeof startOwnedDockerDatabase>> | undefined;
let ownedPublicOrigin: Awaited<ReturnType<typeof prepareOwnedPublicOrigin>>;
let archiveControl: Awaited<ReturnType<typeof startArchiveFaultControl>> | undefined;
let gateway: Awaited<ReturnType<EvalOperations["start"]>> | undefined;
const rawCleanupResults: Array<{ cleanup: string; synthetic: boolean; status: string }> = [];
@@ -568,6 +570,8 @@ async function runAttempt(input: {
if (needsPrivateInputs && executions.length !== 1) throw new Error("Private case inputs require one explicitly selected execution");
await mkdir(selectedPrivateInputsDirectory, { mode: 0o700 });
const privateInputs = needsPrivateInputs ? stageChatPrivateInputs({ execution, descriptor: selectedDescriptor, privateDir: selectedPrivateInputsDirectory, env: process.env, secrets: credentials }) : undefined;
ownedPublicOrigin = await prepareOwnedPublicOrigin({ controlDirectory: process.env[PUBLIC_ORIGIN_CONTROL_ENV], executionId: execution.id, scenarioId: selectedDescriptor?.scenarioId ?? "", attemptId: accountingAttemptId, backendOrigin: `http://127.0.0.1:${port}`, temporaryRoot, privateSecrets: credentials });
delete sourceEnvironment[PUBLIC_ORIGIN_CONTROL_ENV]; delete sourceEnvironment[PUBLIC_ORIGIN_RECEIPT_ENV];
if (selectedDescriptor?.scenarioId === ARCHIVE_SCENARIO) archiveControl = await startArchiveFaultControl({ selected: executions.map(row => row.id), temporaryRoot, backendOrigin: `http://127.0.0.1:${port}`, privateSecrets: credentials });
if (databaseLane === "owned-docker-postgres") dockerDatabase = await startOwnedDockerDatabase({ temporaryRoot, privateSecrets: credentials });
const diagnosticRegistry = stagePrivateDiagnostics(temporaryRoot, executions.map(row => row.id), credentials);
@@ -580,6 +584,7 @@ async function runAttempt(input: {
const childEnv: NodeJS.ProcessEnv = {
...buildRunnerE2EProcessEnvironment(sourceEnvironment, executions),
[CHAT_PRIVATE_INPUTS_ENV]: privateInputs?.filename,
[PUBLIC_ORIGIN_RECEIPT_ENV]: ownedPublicOrigin?.filename,
[ARCHIVE_CONTROL_ENV]: archiveControl?.filename,
[DATABASE_LANE_ENV]: dockerDatabase ? "owned-docker-postgres" : undefined,
[DOCKER_DATABASE_ENV]: dockerDatabase?.filename,
@@ -913,6 +918,7 @@ async function runAttempt(input: {
}
return [...publishedResults];
} catch (error) {
if (error instanceof PublicOriginCleanupError) processCleanupFailed = true;
// Packaging/diagnostic failures must not erase an actually launched attempt
// or its primary startup failure. Retain only closed sanitized metadata;
// raw reports stay private when their normal packaging path failed.
@@ -939,6 +945,8 @@ async function runAttempt(input: {
return [...publishedResults];
} finally {
// runProcess has completed its owned Playwright/server process shutdown.
let publicOriginCleanupError: unknown;
try { await ownedPublicOrigin?.close(); } catch (error) { publicOriginCleanupError = error; processCleanupFailed = true; }
let archiveCleanupError: unknown;
try { await archiveControl?.close(); } catch (error) { archiveCleanupError = error; processCleanupFailed = true; }
let dockerCleanupError: unknown;
@@ -992,7 +1000,7 @@ async function runAttempt(input: {
`Refusing to remove unexpected temporary path ${temporaryRoot}`,
);
}
if (gatewayCleanupError || archiveCleanupError || dockerCleanupError) cleanupError = new AggregateError([cleanupError, gatewayCleanupError, archiveCleanupError, dockerCleanupError].filter(Boolean), "Eval owned gateway/transport cleanup failed");
if (gatewayCleanupError || archiveCleanupError || dockerCleanupError || publicOriginCleanupError) cleanupError = new AggregateError([cleanupError, gatewayCleanupError, archiveCleanupError, dockerCleanupError, publicOriginCleanupError].filter(Boolean), "Eval owned gateway/transport cleanup failed");
if (startupReceiptPath) {
const receipt = JSON.parse(await readFile(startupReceiptPath, "utf8"));
receipt.status = cleanupError ? "installed_launch_chain_cleanup_failed" : "installed_launch_chain_passed";
+30 -2
View File
@@ -1,3 +1,8 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import type { BrowserContext } from "@playwright/test";
import { readValidatedPublicOriginReceipt, PUBLIC_ORIGIN_RECEIPT_ENV } from "./chat-public-origin.js";
import { privatePublicSignIn } from "./chat-catalog-domains/external-public-signin.js";
import { redactTransferEvidence } from "./public-mcp-transfer-evidence.js";
import { runExpandedMcpScenario } from "./public-mcp-expanded-flow.js";
import { type AssistantTool } from "./public-mcp-model.js";
@@ -11,13 +16,35 @@ import { isReadOnlyMcpCall, gradeEventFollowUp, gradeDelegation, gradePausedAgen
import { eventReceiver, mcpEventRpc, connect, mcp, oauthPost, api as browserApi, type Team, type Task, type Run, type Document, type Comment } from "./public-mcp-client.js";
import { runPublicMcpInvitationFlow } from "./public-mcp-invitation-flow.js";
const publicFixtureCredentials = new WeakMap<RunnerApi, { userId: string; email: string; password: string; secrets: string[] }>();
/** The same API object is created once in runner.spec, bootstrapped before fixture
* setup, and passed unchanged into ChatFlowInput. A replacement client has no
* authority to retrieve or submit this person's private credentials. */
export async function signInPublicFixturePerson(api: RunnerApi, target: BrowserContext) {
const person = publicFixtureCredentials.get(api);
if (!person) throw new Error("Public sign-in requires the exact registered fixture API/person object");
const receiptPath = process.env[PUBLIC_ORIGIN_RECEIPT_ENV], attemptId = process.env.PAPERCLIP_E2E_ACCOUNTING_ATTEMPT_ID;
const executions = JSON.parse(process.env.PAPERCLIP_RUNNER_E2E_EXECUTION_IDS ?? "[]") as string[];
if (!receiptPath || !attemptId || executions.length !== 1) throw new Error("Public fixture sign-in requires its exact selected attempt's validated origin receipt");
const receipt = await readValidatedPublicOriginReceipt({ receiptPath, attemptId, executionId: executions[0]!, backendOrigin: api.baseURL });
return privatePublicSignIn({ receiptPath, attemptId, executionId: receipt.executionId, backendOrigin: api.baseURL, publicOrigin: receipt.publicOrigin, userId: person.userId, email: person.email, password: person.password }, person.secrets, async file => {
const incoming = JSON.parse(await readFile(file, "utf8")) as Awaited<ReturnType<BrowserContext["storageState"]>>;
const existing = await target.storageState(), hostname = new URL(receipt.publicOrigin).hostname;
// Preserve existing jars as-is. Only a REAL public sign-in contributes
// public-host cookies; no loopback value is renamed/copied to HTTPS.
const merged = { cookies: [...existing.cookies.filter(cookie => cookie.domain.replace(/^\./, "") !== hostname), ...incoming.cookies], origins: [...existing.origins.filter(origin => origin.origin !== receipt.publicOrigin), ...incoming.origins] };
const mergedFile = path.join(path.dirname(file), "merged-public-session.json"); await writeFile(mergedFile, JSON.stringify(merged), { mode: 0o600, flag: "wx" });
await target.setStorageState(mergedFile);
});
}
/** Real first-admin browser bootstrap, no direct fixture DB writes. */
export async function establishPublicMcpSession(api: RunnerApi, page: Page, secrets: string[]) {
const password = randomBytes(32).toString("base64url");
const password = randomBytes(32).toString("base64url"), email = `mcp-${randomUUID()}@example.test`;
secrets.push(password);
const response = await fetch(api.baseURL + "/api/auth/sign-up/email", {
method: "POST", headers: { "Content-Type": "application/json", Origin: api.baseURL },
body: JSON.stringify({ name: "MCP evaluation person", email: `mcp-${randomUUID()}@example.test`, password }),
body: JSON.stringify({ name: "MCP evaluation person", email, password }),
});
if (!response.ok) throw new Error(`Browser signup failed with HTTP ${response.status}; body withheld`);
const user = await response.json() as { user: { id: string } };
@@ -28,6 +55,7 @@ export async function establishPublicMcpSession(api: RunnerApi, page: Page, secr
await page.context().addCookies(cookies.map(cookie => ({ name: cookie.slice(0, cookie.indexOf("=")), value: cookie.slice(cookie.indexOf("=") + 1), url: api.baseURL, httpOnly: true, sameSite: "Lax" })));
await api.post("/api/bootstrap/claim");
await api.patch("/api/instance/settings/experimental", { enablePublicMcp: true });
publicFixtureCredentials.set(api, { userId: user.user.id, email, password, secrets });
return user.user.id;
}
+10
View File
@@ -1,3 +1,5 @@
import { readSelectedVaultServerEnvironment } from "./chat-vault-private-server.js";
import { readValidatedPublicOriginReceipt, publicOriginServerOverrides, PUBLIC_ORIGIN_RECEIPT_ENV, PUBLIC_ORIGIN_CONTROL_ENV } from "./chat-public-origin.js";
import { dockerDatabaseSupervisorEnvironment } from "./docker-postgres.js";
import { archiveServerEnvironment } from "./chat-archive-fault-control.js";
import { CHAT_INSTALL_PLAN, refreshInstalledChatPins, publishInstalledChatReceipt } from "./chat-installed-lane.js";
@@ -55,6 +57,12 @@ const installedRelease = process.env.PAPERCLIP_RUNNER_E2E_INSTALLED_CLI
? installedReleaseLaunch(process.env.PAPERCLIP_RUNNER_E2E_INSTALLED_CLI) : null;
const dockerEnvironment = await dockerDatabaseSupervisorEnvironment({ temporaryRoot, env: process.env });
const archiveEnvironment = await archiveServerEnvironment({ selected: executionIds, temporaryRoot, backendOrigin: `http://127.0.0.1:${port}`, env: dockerEnvironment.source });
const publicOriginReceipt = process.env[PUBLIC_ORIGIN_RECEIPT_ENV] ? await readValidatedPublicOriginReceipt({ receiptPath: required(PUBLIC_ORIGIN_RECEIPT_ENV), executionId: executionIds.length === 1 ? executionIds[0]! : "", attemptId: required("PAPERCLIP_E2E_ACCOUNTING_ATTEMPT_ID"), backendOrigin: `http://127.0.0.1:${port}` }) : undefined;
if (publicOriginReceipt && process.env.PAPERCLIP_RUNNER_E2E_PUBLIC_MCP !== "1") throw new Error("Owned public origin requires the exact authenticated selected case");
// Only the validated handoff can advertise a public origin. Neither private
// receipt paths nor ambient public/auth configuration reach the product child.
for (const key of [PUBLIC_ORIGIN_RECEIPT_ENV, PUBLIC_ORIGIN_CONTROL_ENV, "PAPERCLIP_PUBLIC_URL", "PAPERCLIP_AUTH_PUBLIC_BASE_URL", "PAPERCLIP_MANAGED_RUNTIME_PUBLIC_URL", "PAPERCLIP_ALLOWED_HOSTNAMES"]) delete archiveEnvironment.source[key];
const vaultEnvironment = await readSelectedVaultServerEnvironment({ executionIds, temporaryRoot, backendOrigin: `http://127.0.0.1:${port}`, env: process.env });
const serverEnvironment = buildPaperclipServerEnvironment(installedRelease
? installedReleaseEnvironment(archiveEnvironment.source, repositoryRoot, path.join(temporaryRoot, "provider-bin"))
: archiveEnvironment.source, {
@@ -82,6 +90,7 @@ const serverEnvironment = buildPaperclipServerEnvironment(installedRelease
PAPERCLIP_PUBLIC_URL: `http://127.0.0.1:${port}`,
PAPERCLIP_AUTH_PUBLIC_BASE_URL: `http://127.0.0.1:${port}`,
PAPERCLIP_AUTH_BASE_URL_MODE: "explicit",
...publicOriginServerOverrides(publicOriginReceipt),
// Use empty provider configuration as the managed homes' seed. A local
// operator's installed plugins, MCP connections and auth are not fixtures.
CODEX_HOME: path.join(temporaryRoot, "provider-config", "codex"),
@@ -98,6 +107,7 @@ const serverEnvironment = buildPaperclipServerEnvironment(installedRelease
// Onboarding normally opens the app after listen. Browser ownership belongs
// to Playwright in this harness, so never create a developer desktop tab.
PAPERCLIP_OPEN_ON_LISTEN: "false",
...vaultEnvironment,
});
assertIsolatedServerEnvironment(serverEnvironment, {
temporaryRoot,