test(evals): identify vault-reference's credential consumer from server state, not shell identity headers

The vault deployment helper sent x-paperclip-agent/x-paperclip-run from
PAPERCLIP_AGENT_ID and PAPERCLIP_RUN_ID, which the native runner reserves
and strips from the worker shell, so the endpoint always returned 403 even
for the correctly bound reference. A new activeRunConsumer endpoint mode
drops those headers and attributes an authorized call to the agent's one
running run read from the server (readSecretEndpointActiveRun, factored
out of the least-scope consumer lookup). The case still requires that run
to be Morgan's on the intended task and the AWS-authority-absent header.

Versions: vault-reference -> external-vault-reference.v9; least-scope ->
v11 (shared lookup refactored, now also checks the detail row is running).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DottaandClaude Opus 5.5 committed 2026-11-09 01:13:12 -06:00
1 parent 80ab0ca512
commit d477873af8
5 files changed
+50 -9

No files matched your search

@@ -4971,7 +4971,7 @@
"../../packages/plugins/sdk/dist/index.js",
"chat-scenarios/secrets.yaml"
],
"oracleVersion": "paperclip.chat-catalog.external-least-scope-delivery.v10",
"oracleVersion": "paperclip.chat-catalog.external-least-scope-delivery.v11",
"expectedRunCount": 4,
"expectedHumanMessageCount": 1,
"minimumRunCount": 3,
@@ -5096,7 +5096,7 @@
"../../ui/src/pages/secrets/ImportFromVaultDialog.tsx",
"chat-scenarios/secrets.yaml"
],
"oracleVersion": "paperclip.chat-catalog.external.v8",
"oracleVersion": "paperclip.chat-catalog.external-vault-reference.v9",
"expectedRunCount": 3,
"expectedHumanMessageCount": 2,
"minimumRunCount": 2,
@@ -268,7 +268,7 @@ export const secretDeliveryExternalCases = [
},
gradeSecretDelivery,
{
oracleVersion: "paperclip.chat-catalog.external-least-scope-delivery.v10", authentication: "human-session",
oracleVersion: "paperclip.chat-catalog.external-least-scope-delivery.v11", authentication: "human-session",
// CEO turn, Morgan's check (plus at most one wake from a handoff
// comment on the created task), and the CEO's confirmation.
expectedRunCount: 4,
@@ -78,12 +78,20 @@ export function validSecretScopeReceipt(
* own execution workspace inside the attempt root. Nothing here depends on
* what the model copied into the command. */
export async function readSecretEndpointConsumerRun(ctx: ChatCatalogContext, agentId: string) {
const run = await readSecretEndpointActiveRun(ctx, agentId);
return { run, ...(await readSecretEndpointTaskBinding(ctx, agentId, run.id, String(run.contextSnapshot?.issueId ?? ""))) };
}
/** The agent's one running run in this company, read from the server. */
export async function readSecretEndpointActiveRun(ctx: ChatCatalogContext, agentId: string) {
const companyId = ctx.input.fixtures.company.id;
const rows = await ctx.input.api.get<Array<{ id: string; agentId: string; status: string }>>(`/api/companies/${companyId}/heartbeat-runs?agentId=${encodeURIComponent(agentId)}&limit=20`, { timeout: 4000 });
const running = rows.filter(row => row.agentId === agentId && row.status === "running");
if (running.length !== 1) throw new Error("Secret consumer has no single active run");
const run = await ctx.input.api.get<{ id: string; companyId: string; agentId: string; status: string; startedAt: string | null; responsibleUserId?: string | null; contextSnapshot: { issueId?: string } | null }>(`/api/heartbeat-runs/${running[0]!.id}`, { timeout: 4000 });
return { run, ...(await readSecretEndpointTaskBinding(ctx, agentId, run.id, String(run.contextSnapshot?.issueId ?? ""))) };
if (run.id !== running[0]!.id || run.companyId !== companyId || run.agentId !== agentId || run.status !== "running")
throw new Error("Secret consumer is not the actual active run");
return run;
}
export async function readSecretEndpointTaskBinding(ctx: ChatCatalogContext, agentId: string, runId: string, taskId: string) {
@@ -111,7 +119,7 @@ export async function secretTestEndpoint(
value: string,
envKey: "PUBLISHING_API_KEY" | "DEPLOYMENT_API_KEY",
scopeProbe?: SecretScopeProbe,
options?: { forbidAwsRuntimeAuthority?: true; taskBound?: true; personalWorkerReceipt?: true },
options?: { forbidAwsRuntimeAuthority?: true; taskBound?: true; personalWorkerReceipt?: true; activeRunConsumer?: true },
) {
if (options?.personalWorkerReceipt && (ctx.scenario.id !== "secrets.personal-secret" || envKey !== "PUBLISHING_API_KEY" || scopeProbe || options.taskBound || options.forbidAwsRuntimeAuthority)) throw new Error("Personal worker receipts require the dedicated personal-secret endpoint");
if (ctx.input.execution.environment.id !== "local")
@@ -168,9 +176,20 @@ export async function secretTestEndpoint(
calls.push(record);
let ok =
authorized &&
(options?.personalWorkerReceipt || options?.taskBound || (record.agentId === agentId && /^[a-f0-9-]{36}$/i.test(record.runId))) &&
(options?.personalWorkerReceipt || options?.taskBound || options?.activeRunConsumer || (record.agentId === agentId && /^[a-f0-9-]{36}$/i.test(record.runId))) &&
record.method === "GET" &&
record.pathname === "/publishing-check";
if (options?.activeRunConsumer && ok) {
// Native runner shells carry no Paperclip run identity; attribute the
// call to the agent's one running run from saved server state.
try {
const run = await readSecretEndpointActiveRun(ctx, agentId);
record.agentId = run.agentId;
record.runId = run.id;
} catch {
ok = false;
}
}
if (options?.forbidAwsRuntimeAuthority)
ok = ok && record.awsAuthorityAbsent === true;
if (scopeProbe && ok) {
@@ -290,7 +309,7 @@ export async function secretTestEndpoint(
const port = await listenOnFetchAllowedPort(server),
scriptPath = home ? path.join(home, `secret-endpoint-check-${ctx.input.nonce}.mjs`) : "",
url = `http://127.0.0.1:${port}/publishing-check`;
const script = `const credential=process.env[${JSON.stringify(envKey)}];if(!credential){process.stdout.write(JSON.stringify({ok:false,reason:'credential unavailable'}));process.exitCode=1;}else{try{const response=await fetch(${JSON.stringify(url)},{method:'GET',redirect:'error',signal:AbortSignal.timeout(20000),headers:{authorization:'Bearer '+credential${options?.personalWorkerReceipt || options?.taskBound ? "" : ",...{'x-paperclip-agent':process.env.PAPERCLIP_AGENT_ID??'','x-paperclip-run':process.env.PAPERCLIP_RUN_ID??''}"},...${options?.forbidAwsRuntimeAuthority ? "{'x-paperclip-no-aws':String(!Object.keys(process.env).some(key=>key.startsWith('AWS_')||key.startsWith('PAPERCLIP_SECRETS_AWS_')))}" : "{}"}}});const result=await response.json();process.stdout.write(JSON.stringify({ok:result.ok===true,status:response.status,check:'publishing endpoint',...${options?.personalWorkerReceipt ? "(/^[a-f0-9]{48}$/.test(result.receiptId??''))?{receiptId:result.receiptId}:{}" : "{}"}}));if(!response.ok)process.exitCode=1;}catch{process.stdout.write(JSON.stringify({ok:false,reason:'endpoint check failed'}));process.exitCode=1;}}\n`;
const script = `const credential=process.env[${JSON.stringify(envKey)}];if(!credential){process.stdout.write(JSON.stringify({ok:false,reason:'credential unavailable'}));process.exitCode=1;}else{try{const response=await fetch(${JSON.stringify(url)},{method:'GET',redirect:'error',signal:AbortSignal.timeout(20000),headers:{authorization:'Bearer '+credential${options?.personalWorkerReceipt || options?.taskBound || options?.activeRunConsumer ? "" : ",...{'x-paperclip-agent':process.env.PAPERCLIP_AGENT_ID??'','x-paperclip-run':process.env.PAPERCLIP_RUN_ID??''}"},...${options?.forbidAwsRuntimeAuthority ? "{'x-paperclip-no-aws':String(!Object.keys(process.env).some(key=>key.startsWith('AWS_')||key.startsWith('PAPERCLIP_SECRETS_AWS_')))}" : "{}"}}});const result=await response.json();process.stdout.write(JSON.stringify({ok:result.ok===true,status:response.status,check:'publishing endpoint',...${options?.personalWorkerReceipt ? "(/^[a-f0-9]{48}$/.test(result.receiptId??''))?{receiptId:result.receiptId}:{}" : "{}"}}));if(!response.ok)process.exitCode=1;}catch{process.stdout.write(JSON.stringify({ok:false,reason:'endpoint check failed'}));process.exitCode=1;}}\n`;
if (scriptPath) await writeFile(scriptPath, script, { mode: 0o600 });
const command = scriptPath ? `node ${JSON.stringify(scriptPath)}` : `node --input-type=module -e '${script.replace(/'/g, "'\\''")}'`;
return {
@@ -85,3 +85,25 @@ it("actual endpoint binds the server's running Morgan run, probes with its own a
expect(JSON.stringify({ calls: endpoint.calls, receipts: endpoint.scopeReceipts, output })).not.toContain("synthetic-deployment-value");
} finally { await endpoint.close(); }
});
it("active-run consumer mode attributes the bound credential to the server's one running run, without shell identity headers", async () => {
const f = await fixture(); vi.stubEnv("PAPERCLIP_HOME", path.join(await realpath(f.root), "home"));
f.ctx.scenario.id = "secrets.vault-reference";
const endpoint = await secretTestEndpoint(f.ctx, "morgan", "synthetic-deployment-value", "DEPLOYMENT_API_KEY", undefined, { forbidAwsRuntimeAuthority: true, activeRunConsumer: true });
const statuses: number[] = [];
const invoke = (authorization: string, headers: Record<string, string> = {}) => handlers[0]!({ headers: { authorization, "x-paperclip-no-aws": "true", ...headers }, method: "GET", url: "/publishing-check" }, { writeHead: (status: number) => statuses.push(status), end: () => {} });
try {
expect(endpoint.command).not.toContain("synthetic-deployment-value");
const { readFile } = await import("node:fs/promises");
expect(await readFile(endpoint.scriptPath, "utf8")).not.toMatch(/PAPERCLIP_AGENT_ID|PAPERCLIP_RUN_ID|x-paperclip-agent|x-paperclip-run/);
await invoke("Bearer synthetic-deployment-value"); expect(statuses.at(-1)).toBe(200);
expect(endpoint.calls.at(-1)).toMatchObject({ authorized: true, agentId: "morgan", runId: f.run.id, awsAuthorityAbsent: true });
// Forged shell headers are ignored: identity comes from the server.
await invoke("Bearer synthetic-deployment-value", { "x-paperclip-agent": "ceo", "x-paperclip-run": "33333333-3333-3333-3333-333333333333" });
expect(endpoint.calls.at(-1)).toMatchObject({ agentId: "morgan", runId: f.run.id });
await invoke("Bearer wrong-value"); expect(statuses.at(-1)).toBe(403);
await invoke("Bearer synthetic-deployment-value", { "x-paperclip-no-aws": "false" }); expect(statuses.at(-1)).toBe(403);
f.runs[0]!.status = "succeeded"; await invoke("Bearer synthetic-deployment-value"); expect(statuses.at(-1)).toBe(403);
f.runs[0]!.status = "running"; f.runs[1]!.status = "running"; await invoke("Bearer synthetic-deployment-value"); expect(statuses.at(-1)).toBe(403);
f.runs[1]!.status = "succeeded"; f.run.agentId = "ceo"; await invoke("Bearer synthetic-deployment-value"); expect(statuses.at(-1)).toBe(403);
} finally { await endpoint.close(); }
});
@@ -368,7 +368,7 @@ export const vaultReferenceExternalCases = [
value,
"DEPLOYMENT_API_KEY",
undefined,
{ forbidAwsRuntimeAuthority: true },
{ forbidAwsRuntimeAuthority: true, activeRunConsumer: true },
);
const task = await ctx.input.api.post<Entity>(`${root(ctx)}/issues`, {
title: "Vault deployment check",
@@ -426,7 +426,7 @@ export const vaultReferenceExternalCases = [
},
gradeVaultReference,
{
oracleVersion: "paperclip.chat-catalog.external.v8", authentication: "human-session",
oracleVersion: "paperclip.chat-catalog.external-vault-reference.v9", authentication: "human-session",
expectedRunCount: 3,
minimumRunCount: 2,
expectedHumanMessageCount: 2,