From 65fa0c6a847bde1a32d24a26903b1ceb03f1cfb7 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 28 Oct 2026 11:51:42 -0500 Subject: [PATCH] fix(evals): withhold screenshots after original privacy findings --- tests/runner-e2e/SECURITY.md | 7 ++ tests/runner-e2e/abnormal-finalization.ts | 3 +- tests/runner-e2e/dashboard.ts | 5 +- tests/runner-e2e/history-publish.ts | 2 + tests/runner-e2e/history.test.ts | 14 ++++ tests/runner-e2e/launch.ts | 8 +- tests/runner-e2e/result-validation.ts | 2 + tests/runner-e2e/runner.spec.ts | 10 ++- tests/runner-e2e/screenshot-privacy.test.ts | 84 +++++++++++++++++++++ tests/runner-e2e/screenshot-privacy.ts | 38 ++++++++++ tests/runner-e2e/types.ts | 1 + 11 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 tests/runner-e2e/screenshot-privacy.test.ts create mode 100644 tests/runner-e2e/screenshot-privacy.ts diff --git a/tests/runner-e2e/SECURITY.md b/tests/runner-e2e/SECURITY.md index 3c015e9507f..c0f26962427 100644 --- a/tests/runner-e2e/SECURITY.md +++ b/tests/runner-e2e/SECURITY.md @@ -581,3 +581,10 @@ qualification prerequisite. This alternative does not qualify that path, create an instance administrator, bypass route authorization, or change retained failed outcomes. Passwords and cookies remain in the existing private session registry; the setup receipt contains only public identity, role, grant keys and statuses. + +A screenshot route marker is provisional. Any original secret finding, persisted +state secret finding, privacy-driven reporter redaction/omission, or packaged +evidence secret finding withholds all screenshots for that attempt. The launcher +retains a closed withholding reason and removes public markers while preserving +original private PNG bytes and hashes. Later cleanup labels and empty sanitized +export scans cannot clear this decision; the publisher and gallery enforce it. diff --git a/tests/runner-e2e/abnormal-finalization.ts b/tests/runner-e2e/abnormal-finalization.ts index af09442cf95..4a637d778a1 100644 --- a/tests/runner-e2e/abnormal-finalization.ts +++ b/tests/runner-e2e/abnormal-finalization.ts @@ -1,9 +1,10 @@ +import { withholdPrivateScreenshots } from "./screenshot-privacy.js"; import type { RunnerE2EResult } from "./types.js"; /** Evidence failure must retain the original startup/work verdict. A generated * fallback remains a failed harness result, never proof that a browser ran. */ export function abnormalFinalizationResult(original: RunnerE2EResult, error: unknown): RunnerE2EResult { const detail = error instanceof Error ? error.message : String(error); - return { ...original, status: "failed", failureClass: original.status === "failed" ? original.failureClass : "permanent_infrastructure", + return { ...withholdPrivateScreenshots(original), status: "failed", failureClass: original.status === "failed" ? original.failureClass : "permanent_infrastructure", error: `${original.error ? `${original.error}; ` : ""}Abnormal attempt finalization: ${detail}` }; } diff --git a/tests/runner-e2e/dashboard.ts b/tests/runner-e2e/dashboard.ts index 96a5eefad07..d4e9319ef16 100644 --- a/tests/runner-e2e/dashboard.ts +++ b/tests/runner-e2e/dashboard.ts @@ -1,3 +1,4 @@ +import { screenshotWithholdingNotice } from "./screenshot-privacy.js"; import { campaignAttemptTimeLabel, campaignTimingScope, renderWorkerPhaseTiming, summarizeCampaignTiming, summarizeWorkerPhaseTiming } from "./campaign-timing.js"; import { html, durationLabel, tokenLabel, llmCostLabel, usdLabel } from "./report-format.js"; import { renderReportShell, reportNavigationStyles, reportShellScript } from "./report-navigation.js"; @@ -83,6 +84,7 @@ function compactJson(value: unknown) { function resolveScreenshots( entry: RunnerDashboardEntry | undefined, ): ResolvedScreenshot[] { + if (entry && screenshotWithholdingNotice(entry.result)) return []; const declaredScreenshots = entry?.result.screenshots?.length ? entry.result.screenshots : entry @@ -244,7 +246,8 @@ function renderCase( ${STARTUP_REUSE_LAYERS.map(layer => `${html(reuse.layers[layer].state)} (${html(reuse.layers[layer].reason)})`).join("")} ${html(reuse.providerLaunchCount === null ? "unknown" : reuse.providerLaunchCount)} `).join(""); - const gallery = screenshots.length + const withholdingNotice = entry ? screenshotWithholdingNotice(entry.result) : null; + const gallery = withholdingNotice ? `

${html(withholdingNotice)}

` : screenshots.length ? `