Reuse attempt-private domain probe credentials until cleanup

This commit is contained in:
Dotta committed 2026-10-26 18:00:54 -05:00
1 parent 865a191bce
commit a55eaa86e6
3 files changed
+76 -45

No files matched your search

@@ -7,19 +7,29 @@ interface Probe { status: number; body: unknown }
interface InboxObservation extends InternalObservation {
personalItems: Entity[]; policy: { mode: string; allowedAgentIds: string[] }; personalProbe: Probe; responsibleUserProbe: Probe; unreadProbe: Probe; archiveProbe?: Probe;
}
async function agentRequest(ctx: ChatCatalogContext, path: string, method = "GET", body?: unknown): Promise<Probe> {
const id = ctx.input.fixtures.agent.id;
const key = await ctx.input.api.postSensitive<{ id: string; token: string }>(`/api/agents/${id}/keys`, { name: `inbox-scope-control-${ctx.input.nonce}` });
const inboxProbeKeys = new WeakMap<ChatCatalogContext, Promise<{ id: string; token: string }>>();
async function inboxProbeKey(ctx: ChatCatalogContext) {
if (!ctx.input.privateSecrets) throw new Error("Agent probe requires an attempt-private secret redactor");
ctx.input.privateSecrets.push(key.token);
try {
const url = new URL(path, ctx.input.api.baseURL);
if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Inbox probe escaped fixture API");
const response = await fetch(url, { method, redirect: "error", signal: AbortSignal.timeout(15_000),
headers: { Authorization: `Bearer ${key.token}`, ...(body === undefined ? {} : { "content-type": "application/json" }) },
...(body === undefined ? {} : { body: JSON.stringify(body) }) });
return { status: response.status, body: await response.json() };
} finally { await ctx.input.api.delete(`/api/agents/${id}/keys/${key.id}`); }
let pending = inboxProbeKeys.get(ctx);
if (!pending) {
pending = ctx.input.api.postSensitive<{ id: string; token: string }>(`/api/agents/${ctx.input.fixtures.agent.id}/keys`, { name: `inbox-scope-control-${ctx.input.nonce}` })
.then(key => { ctx.input.privateSecrets!.push(key.token); return key; });
inboxProbeKeys.set(ctx, pending);
}
return pending;
}
async function cleanupInboxProbe(ctx: ChatCatalogContext) {
const pending = inboxProbeKeys.get(ctx); inboxProbeKeys.delete(ctx);
if (pending) { const key = await pending; await ctx.input.api.delete(`/api/agents/${ctx.input.fixtures.agent.id}/keys/${key.id}`, { allowNotFound: true }); }
}
async function agentRequest(ctx: ChatCatalogContext, path: string, method = "GET", body?: unknown): Promise<Probe> {
const key = await inboxProbeKey(ctx);
const url = new URL(path, ctx.input.api.baseURL);
if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Inbox probe escaped fixture API");
const response = await fetch(url, { method, redirect: "error", signal: AbortSignal.timeout(15_000),
headers: { Authorization: `Bearer ${key.token}`, ...(body === undefined ? {} : { "content-type": "application/json" }) },
...(body === undefined ? {} : { body: JSON.stringify(body) }) });
return { status: response.status, body: await response.json() };
}
function minePath(ctx: ChatCatalogContext) {
return `/api/companies/${ctx.input.fixtures.company.id}/issues?touchedByUserId=me&inboxArchivedByUserId=me&includeAgentCreatedTasks=true&limit=500`;
@@ -74,6 +84,7 @@ export function gradeInbox(value: unknown) {
}
function inboxBoundary(scenarioId: string): ChatCatalogCase {
const base = makeDefinition(scenarioId, async ctx => {
try {
const session = await ctx.input.api.get<{ user?: { id: string } }>("/api/auth/get-session");
if (!session.user?.id) throw new Error("Personal inbox fixture requires a real signed-in person");
const p = await project(ctx); const tasks: Entity[] = [];
@@ -94,6 +105,7 @@ function inboxBoundary(scenarioId: string): ChatCatalogCase {
throw new Error("Production Mine fixture does not reflect human participation independently of automated work");
return { project: p, task: tasks[0], doneTask: tasks[2], personId: session.user.id, originalTasks: [...tasks, automatedTask],
originalPersonalIds: personalItems.map(row => row.id), automatedTask };
} catch (error) { await cleanupInboxProbe(ctx); throw error; }
}, gradeInbox);
return { ...base, authentication: "human-session",
async observe(ctx, state): Promise<InboxObservation> {
@@ -124,6 +136,7 @@ function inboxBoundary(scenarioId: string): ChatCatalogCase {
await ctx.input.page.goto(source, { waitUntil: "domcontentloaded" });
await expect(ctx.input.page.getByTestId("task-chat-composer-input")).toBeVisible();
},
async cleanup(ctx) { await cleanupInboxProbe(ctx); },
};
}
export const inboxSearchCases: readonly ChatCatalogCase[] = [inboxBoundary("inbox-search.show-my-work"), inboxBoundary("inbox-search.archive-without-grant")];
@@ -30,15 +30,30 @@ async function personRead(person: Person, path: string) {
await response.body?.cancel();
return { status: response.status, body };
}
const privacyProbeKeys = new WeakMap<ChatCatalogContext, Map<string, Promise<{ id: string; token: string }>>>();
async function privacyProbeKey(ctx: ChatCatalogContext, agentId: string) {
if (!ctx.input.privateSecrets) throw new Error("Privacy probes require attempt-private credential redaction");
let keys = privacyProbeKeys.get(ctx); if (!keys) { keys = new Map(); privacyProbeKeys.set(ctx, keys); }
let pending = keys.get(agentId);
if (!pending) {
pending = ctx.input.api.postSensitive<{ id: string; token: string }>(`/api/agents/${agentId}/keys`, { name: `privacy-probe-${ctx.input.nonce}` })
.then(key => { ctx.input.privateSecrets!.push(key.token); return key; });
keys.set(agentId, pending);
}
return pending;
}
async function cleanupPrivacyProbes(ctx: ChatCatalogContext) {
const keys = privacyProbeKeys.get(ctx); privacyProbeKeys.delete(ctx);
for (const [agentId, pending] of keys ?? []) {
const key = await pending; await ctx.input.api.delete(`/api/agents/${agentId}/keys/${key.id}`, { allowNotFound: true });
}
}
async function agentStatus(ctx: ChatCatalogContext, agentId: string, path: string) {
const key = await ctx.input.api.postSensitive<{ id: string; token: string }>(`/api/agents/${agentId}/keys`, { name: `privacy-probe-${ctx.input.nonce}` });
ctx.input.privateSecrets?.push(key.token);
try {
const url = new URL(path, ctx.input.api.baseURL);
if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Privacy probe escaped its isolated API");
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(15_000), headers: { Authorization: `Bearer ${key.token}` } });
await response.body?.cancel(); return response.status;
} finally { await ctx.input.api.delete(`/api/agents/${agentId}/keys/${key.id}`); }
const key = await privacyProbeKey(ctx, agentId);
const url = new URL(path, ctx.input.api.baseURL);
if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Privacy probe escaped its isolated API");
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(15_000), headers: { Authorization: `Bearer ${key.token}` } });
await response.body?.cancel(); return response.status;
}
function privacyObservation(value: unknown): PrivacyObservation | undefined {
const o = observation(value) as PrivacyObservation | undefined;
@@ -111,7 +126,7 @@ function privateProject(): ChatCatalogCase {
denied: true, outcome: "The copied project link presents an access error, without the protected project heading." });
} finally { await observerPage.close(); }
},
async cleanup() { await observer?.dispose(); observer = undefined; },
async cleanup(ctx) { try { await cleanupPrivacyProbes(ctx); } finally { await observer?.dispose(); observer = undefined; } },
};
}
export const projectPrivacyCases: readonly ChatCatalogCase[] = [privateProject(), shareProject()];
@@ -144,15 +159,12 @@ export function gradeShareProject(value: unknown) {
}
async function agentMutationStatus(ctx: ChatCatalogContext, path: string, data: unknown) {
const id = ctx.input.fixtures.agent.id;
const key = await ctx.input.api.postSensitive<{ id: string; token: string }>(`/api/agents/${id}/keys`, { name: `privacy-write-control-${ctx.input.nonce}` });
ctx.input.privateSecrets?.push(key.token);
try {
const url = new URL(path, ctx.input.api.baseURL);
if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Privacy mutation probe escaped fixture API");
const response = await fetch(url, { method: "POST", redirect: "error", signal: AbortSignal.timeout(15_000),
headers: { Authorization: `Bearer ${key.token}`, "content-type": "application/json" }, body: JSON.stringify(data) });
await response.body?.cancel(); return response.status;
} finally { await ctx.input.api.delete(`/api/agents/${id}/keys/${key.id}`); }
const key = await privacyProbeKey(ctx, id);
const url = new URL(path, ctx.input.api.baseURL);
if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Privacy mutation probe escaped fixture API");
const response = await fetch(url, { method: "POST", redirect: "error", signal: AbortSignal.timeout(15_000),
headers: { Authorization: `Bearer ${key.token}`, "content-type": "application/json" }, body: JSON.stringify(data) });
await response.body?.cancel(); return response.status;
}
function shareProject(): ChatCatalogCase {
let observer: Person | undefined;
@@ -217,7 +229,7 @@ function shareProject(): ChatCatalogCase {
await ctx.input.evidence("chat-catalog-owner-project-sharing-opened.json", { observerId: observer.id, projectId: p.id, url: observerPage.url(), visible: true });
} finally { await observerPage.close(); }
},
async cleanup() { await observer?.dispose(); observer = undefined; },
async cleanup(ctx) { try { await cleanupPrivacyProbes(ctx); } finally { await observer?.dispose(); observer = undefined; } },
};
}
@@ -272,7 +284,7 @@ function sharePrivateTask(): ChatCatalogCase {
await ctx.input.evidence("chat-catalog-task-sharing-denied-browser.json", { taskId: t.id, observerId: observer.id, denied: true });
} finally { await page.close(); }
},
async cleanup() { await observer?.dispose(); observer = undefined; },
async cleanup(ctx) { try { await cleanupPrivacyProbes(ctx); } finally { await observer?.dispose(); observer = undefined; } },
};
}
export const taskPrivacyCases: readonly ChatCatalogCase[] = [sharePrivateTask()];
@@ -13,6 +13,7 @@ interface BriefObservation extends InternalObservation {
workerTaskStatus?: number; workerDownloadStatus?: number; workerDownloadedHash?: string; durablePath?: string;
}
const hash = (bytes: Uint8Array) => createHash("sha256").update(bytes).digest("hex");
const workerProbeKeys = new WeakMap<ChatCatalogContext, { id: string; token: string; workerId: string }>();
export function gradeAttachmentBrief(value: unknown) {
const c = checks(); const o = observation(value) as BriefObservation | undefined;
c.add("independent-upload-and-worker-evidence", !!o && Array.isArray(o.sourceAttachments) && Array.isArray(o.workAttachments) && Array.isArray(o.workDocuments),
@@ -41,23 +42,20 @@ export function gradeAttachmentBrief(value: unknown) {
return c.result();
}
async function workerReads(ctx: ChatCatalogContext, workerId: string, taskId: string, path: string | undefined) {
if (!ctx.input.privateSecrets) throw new Error("Worker brief probe requires private secret redaction");
const key = await ctx.input.api.postSensitive<{ id: string; token: string }>(`/api/agents/${workerId}/keys`, { name: "delegated-brief-read-control" });
ctx.input.privateSecrets.push(key.token);
const key = workerProbeKeys.get(ctx);
if (!key || key.workerId !== workerId) throw new Error("Attempt-private Morgan probe credential is unavailable");
const get = (value: string) => {
const url = new URL(value, ctx.input.api.baseURL);
if (url.origin !== ctx.input.api.baseURL || !/^\/api\/(?:issues|attachments)\//.test(url.pathname)) throw new Error("Worker brief read escaped isolated resources");
return fetch(url, { redirect: "error", signal: AbortSignal.timeout(15_000), headers: { Authorization: `Bearer ${key.token}` } });
};
try {
const taskRead = await get(`/api/issues/${taskId}`); const workerTaskStatus = taskRead.status; await taskRead.body?.cancel();
if (!path) return { workerTaskStatus };
const response = await get(path); const workerDownloadStatus = response.status;
if (!response.ok) { await response.body?.cancel(); return { workerTaskStatus, workerDownloadStatus }; }
const bytes = new Uint8Array(await response.arrayBuffer());
if (bytes.byteLength > 65_536) throw new Error("Delegated brief unexpectedly exceeds its fixture size bound");
return { workerTaskStatus, workerDownloadStatus, workerDownloadedHash: hash(bytes) };
} finally { await ctx.input.api.delete(`/api/agents/${workerId}/keys/${key.id}`); }
const taskRead = await get(`/api/issues/${taskId}`); const workerTaskStatus = taskRead.status; await taskRead.body?.cancel();
if (!path) return { workerTaskStatus };
const response = await get(path); const workerDownloadStatus = response.status;
if (!response.ok) { await response.body?.cancel(); return { workerTaskStatus, workerDownloadStatus }; }
const bytes = new Uint8Array(await response.arrayBuffer());
if (bytes.byteLength > 65_536) throw new Error("Delegated brief unexpectedly exceeds its fixture size bound");
return { workerTaskStatus, workerDownloadStatus, workerDownloadedHash: hash(bytes) };
}
const base = makeDefinition("tasks.attachment-brief", async ctx => {
const worker = await fixtureWorker(ctx); const source = await openStandingChat(ctx);
@@ -70,7 +68,10 @@ const base = makeDefinition("tasks.attachment-brief", async ctx => {
const receipt = uploaded.find(row => row.originalFilename === filename)!;
await expect(ctx.input.page.getByTestId("task-chat-composer-attachments")).toContainText(filename);
await expect(ctx.input.page.getByTestId("task-chat-composer-attachments")).not.toContainText("Uploading");
return { worker, sourceId: source.id, briefAttachment: receipt, briefHash: hash(Buffer.from(briefBody)) };
if (!ctx.input.privateSecrets) throw new Error("Worker brief probe requires private secret redaction");
const key = await ctx.input.api.postSensitive<{ id: string; token: string }>(`/api/agents/${worker.id}/keys`, { name: "delegated-brief-read-control" });
ctx.input.privateSecrets.push(key.token); workerProbeKeys.set(ctx, { ...key, workerId: worker.id });
return { worker, sourceId: source.id, briefAttachment: receipt, briefHash: hash(Buffer.from(briefBody)), workerProbeKeyId: key.id };
}, gradeAttachmentBrief);
export const taskAttachmentCases: readonly ChatCatalogCase[] = [{ ...base, authentication: "human-session",
queryAttachments: [{ stateKey: "briefAttachment", filename: "website-brief.md" }], expectedRunCount: 3, minimumRunCount: 2,
@@ -93,4 +94,9 @@ export const taskAttachmentCases: readonly ChatCatalogCase[] = [{ ...base, authe
const reads = saved ? await workerReads(ctx, entity(state, "worker").id, saved.id, durablePath) : {};
return { ...o, sourceId: source?.id, sourceAttachments, originalBytesHash, workAttachments, workDocuments, durablePath, ...reads };
},
async cleanup(ctx, state) {
const key = workerProbeKeys.get(ctx); workerProbeKeys.delete(ctx);
const id = key?.id ?? state.workerProbeKeyId;
if (id) await ctx.input.api.delete(`/api/agents/${entity(state, "worker").id}/keys/${id}`, { allowNotFound: true });
},
}];