import { execFile } from "node:child_process"; import { constants as fsConstants } from "node:fs"; import path from "node:path"; import { promisify } from "node:util"; import { cp, copyFile, lstat, mkdir, mkdtemp, readFile, readdir, rm, writeFile, } from "node:fs/promises"; import { regenerateRunnerDashboard } from "./dashboard-regenerate.js"; const execFileAsync = promisify(execFile); const PUBLIC_EVIDENCE_EXTENSIONS = new Set([".json", ".log", ".md", ".txt"]); const PRIVATE_EVIDENCE_DIRECTORIES = new Set([ "blob-report", "html-report", "playwright-output", ]); const PUBLIC_VISUAL_DIRECTORY = "public-visuals"; const PUBLIC_SCREENSHOT_NAME = /^(?:final-state|plan-[a-z0-9-]+|question-[a-z0-9-]+)\.png$/; const PNG_SIGNATURE = Buffer.from([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ]); const MAX_PRIVATE_PNG_BYTES = 32 * 1024 * 1024; const MAX_PRIVATE_PNG_PIXELS = 64 * 1024 * 1024; const MAX_PRIVATE_PNG_EDGE = 32 * 1024; const MAX_PUBLIC_PREVIEW_EDGE = 96; const MAX_PUBLIC_PREVIEW_BYTES = 256 * 1024; const PRIVATE_PNG_CHUNKS = new Set([ "IHDR", "IDAT", "IEND", "PLTE", "tRNS", "gAMA", "cHRM", "sRGB", "pHYs", ]); const PUBLIC_PNG_CHUNKS = new Set(["IHDR", "IDAT", "IEND", "PLTE"]); interface PublishedResult { executionId?: unknown; attempt?: unknown; status?: unknown; evidenceValid?: unknown; screenshots?: unknown; [key: string]: unknown; } interface PublishedCampaign { results?: unknown; [key: string]: unknown; } export type PublicPreviewTransformer = ( source: string, destination: string, ) => Promise; function publicVisualPath(evidencePath: string) { const segments = evidencePath.split("/"); return ( segments.length === 2 && segments[0] === PUBLIC_VISUAL_DIRECTORY && PUBLIC_SCREENSHOT_NAME.test(segments[1] ?? "") ); } export function isPublicHistoryEvidencePath(relative: string) { const match = relative.match( /^evidence\/[A-Za-z0-9._-]+\/attempt-[1-9][0-9]*\/(.+)$/, ); if (!match) return false; const evidencePath = match[1]!; const segments = evidencePath.split("/"); if ( segments.some( (segment) => !segment || segment === "." || segment === ".." || PRIVATE_EVIDENCE_DIRECTORIES.has(segment), ) ) { return false; } if (publicVisualPath(evidencePath)) return true; return PUBLIC_EVIDENCE_EXTENSIONS.has( path.posix.extname(evidencePath).toLowerCase(), ); } async function pruneEvidenceDirectory(root: string, current: string) { const entries = await readdir(current, { withFileTypes: true }); for (const entry of entries) { const absolute = path.join(current, entry.name); if (entry.isDirectory()) { await pruneEvidenceDirectory(root, absolute); if ((await readdir(absolute)).length === 0) { await rm(absolute, { recursive: true }); } continue; } const relative = path.relative(root, absolute).split(path.sep).join("/"); if (!entry.isFile() || !isPublicHistoryEvidencePath(relative)) { await rm(absolute, { force: true }); } else if (path.posix.extname(relative).toLowerCase() === ".png") { // Only derived public previews can pass the PNG path allowlist. Validate // the bounded inert container again when the AWS publisher prunes its // downloaded bundle. await validatePublicPreview(absolute); } } } export async function prunePrivateHistoryEvidence(root: string) { const evidenceRoot = path.join(root, "evidence"); const metadata = await lstat(evidenceRoot).catch(() => null); if (!metadata) return; if (!metadata.isDirectory()) { throw new Error("Historical evidence root must be a directory"); } await pruneEvidenceDirectory(root, evidenceRoot); } function crc32(buffer: Buffer) { let crc = 0xffffffff; for (const byte of buffer) { crc ^= byte; for (let bit = 0; bit < 8; bit += 1) { crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0); } } return (crc ^ 0xffffffff) >>> 0; } function inspectPng( value: Buffer, options: { label: string; maxBytes: number; maxEdge?: number; maxPixels?: number; chunks: ReadonlySet; }, ) { if (value.length > options.maxBytes) { throw new Error(`${options.label} exceeds the PNG byte limit`); } if ( value.length < PNG_SIGNATURE.length || !value.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE) ) { throw new Error(`${options.label} is not a PNG`); } let offset = PNG_SIGNATURE.length; let width = 0; let height = 0; let sawHeader = false; let sawData = false; let sawEnd = false; while (offset < value.length) { if (offset + 12 > value.length) { throw new Error(`${options.label} has a truncated PNG chunk`); } const length = value.readUInt32BE(offset); const dataStart = offset + 8; const dataEnd = dataStart + length; const chunkEnd = dataEnd + 4; if (chunkEnd > value.length) { throw new Error(`${options.label} has an invalid PNG chunk length`); } const type = value.toString("ascii", offset + 4, offset + 8); if (!options.chunks.has(type)) { throw new Error(`${options.label} contains forbidden PNG chunk ${type}`); } const expectedCrc = value.readUInt32BE(dataEnd); const actualCrc = crc32(value.subarray(offset + 4, dataEnd)); if (actualCrc !== expectedCrc) { throw new Error(`${options.label} contains a corrupt PNG chunk`); } if (type === "IHDR") { if (sawHeader || offset !== PNG_SIGNATURE.length || length !== 13) { throw new Error(`${options.label} has an invalid PNG header`); } sawHeader = true; width = value.readUInt32BE(dataStart); height = value.readUInt32BE(dataStart + 4); const bitDepth = value[dataStart + 8]; const colorType = value[dataStart + 9]; const compression = value[dataStart + 10]; const filter = value[dataStart + 11]; const interlace = value[dataStart + 12]; if ( width < 1 || height < 1 || bitDepth !== 8 || ![2, 3, 6].includes(colorType ?? -1) || compression !== 0 || filter !== 0 || interlace !== 0 ) { throw new Error(`${options.label} uses an unsupported PNG encoding`); } if ( options.maxEdge && (width > options.maxEdge || height > options.maxEdge) ) { throw new Error(`${options.label} exceeds the PNG dimensions`); } if (options.maxPixels && width * height > options.maxPixels) { throw new Error(`${options.label} exceeds the PNG pixel limit`); } } else if (!sawHeader) { throw new Error(`${options.label} has data before its PNG header`); } else if (type === "IDAT") { sawData = true; } else if (type === "IEND") { if (!sawData || length !== 0 || chunkEnd !== value.length) { throw new Error(`${options.label} has an invalid PNG end marker`); } sawEnd = true; } offset = chunkEnd; } if (!sawHeader || !sawData || !sawEnd) { throw new Error(`${options.label} is an incomplete PNG`); } } async function validatePrivateScreenshot(file: string) { const metadata = await lstat(file); if (!metadata.isFile() || metadata.isSymbolicLink()) { throw new Error("Public preview source must be a regular file"); } inspectPng(await readFile(file), { label: "Public preview source", maxBytes: MAX_PRIVATE_PNG_BYTES, maxEdge: MAX_PRIVATE_PNG_EDGE, maxPixels: MAX_PRIVATE_PNG_PIXELS, chunks: PRIVATE_PNG_CHUNKS, }); } async function validatePublicPreview(file: string) { const metadata = await lstat(file); if (!metadata.isFile() || metadata.isSymbolicLink()) { throw new Error("Public preview output must be a regular file"); } inspectPng(await readFile(file), { label: "Public preview output", maxBytes: MAX_PUBLIC_PREVIEW_BYTES, maxEdge: MAX_PUBLIC_PREVIEW_EDGE, chunks: PUBLIC_PNG_CHUNKS, }); } async function assertPreviewHasNoReadableText(file: string) { const { stdout } = await execFileAsync( process.env.RUNNER_E2E_TESSERACT_BINARY ?? "tesseract", [file, "stdout", "--psm", "11", "-l", "eng"], { timeout: 30_000, maxBuffer: 1024 * 1024, env: { ...process.env, OMP_THREAD_LIMIT: "1" }, }, ); // The report job no longer has provider secrets, so it cannot compare OCR // output with exact credential values. Reject every readable letter or digit // instead. Do not include OCR output in the error because it is untrusted. if (/[\p{L}\p{N}]/u.test(stdout)) { throw new Error("Public layout preview still contains OCR-readable text"); } } export async function createPublicLayoutPreview( source: string, destination: string, ) { await validatePrivateScreenshot(source); await mkdir(path.dirname(destination), { recursive: true }); const temporary = await mkdtemp( path.join(path.dirname(destination), ".preview-"), ); const output = path.join(temporary, "preview.png"); try { await execFileAsync( process.env.RUNNER_E2E_IMAGE_MAGICK_BINARY ?? "convert", [ "-limit", "memory", "128MiB", "-limit", "map", "256MiB", "-limit", "disk", "256MiB", "-limit", "thread", "1", "-limit", "time", "30", source, "-background", "#f3f4f6", "-alpha", "remove", "-alpha", "off", "-resize", `${MAX_PUBLIC_PREVIEW_EDGE}x${MAX_PUBLIC_PREVIEW_EDGE}>`, "-blur", "0x3.5", "-colors", "16", "-strip", "-define", "png:exclude-chunks=all", `PNG8:${output}`, ], { timeout: 45_000, maxBuffer: 1024 * 1024 }, ); await validatePublicPreview(output); await assertPreviewHasNoReadableText(output); await copyFile(output, destination, fsConstants.COPYFILE_EXCL); } finally { await rm(temporary, { recursive: true, force: true }); } } async function assertTreeContainsNoLinks(root: string, current = root) { const entries = await readdir(current, { withFileTypes: true }); for (const entry of entries) { const absolute = path.join(current, entry.name); const metadata = await lstat(absolute); if (metadata.isSymbolicLink()) { throw new Error("Public history source must not contain symbolic links"); } if (metadata.isDirectory()) await assertTreeContainsNoLinks(root, absolute); } } async function removeExistingPublicVisuals(current: string) { const entries = await readdir(current, { withFileTypes: true }).catch( () => [], ); for (const entry of entries) { const absolute = path.join(current, entry.name); if (!entry.isDirectory()) continue; if (entry.name === PUBLIC_VISUAL_DIRECTORY) { await rm(absolute, { recursive: true, force: true }); continue; } await removeExistingPublicVisuals(absolute); } } function safePassedScreenshot(input: unknown) { if (!input || typeof input !== "object") { throw new Error("Passing result has invalid screenshot metadata"); } const screenshot = input as Record; if ( typeof screenshot.id !== "string" || typeof screenshot.label !== "string" || typeof screenshot.file !== "string" || !PUBLIC_SCREENSHOT_NAME.test(screenshot.file) ) { throw new Error("Passing result has unsafe screenshot metadata"); } return { ...screenshot, label: `${screenshot.label} (redacted layout preview)`, file: `${PUBLIC_VISUAL_DIRECTORY}/${screenshot.file}`, privateFile: screenshot.file, }; } export async function preparePublicHistoryBundle(input: { source: string; destination: string; transform?: PublicPreviewTransformer; }) { const source = path.resolve(input.source); const destination = path.resolve(input.destination); if ( source === destination || source.startsWith(`${destination}${path.sep}`) || destination.startsWith(`${source}${path.sep}`) ) { throw new Error("Public history source and destination must not overlap"); } const sourceMetadata = await lstat(source); if (!sourceMetadata.isDirectory() || sourceMetadata.isSymbolicLink()) { throw new Error("Public history source must be a directory"); } await assertTreeContainsNoLinks(source); await rm(destination, { recursive: true, force: true }); await cp(source, destination, { recursive: true, force: false }); const normalizedFile = path.join(destination, "normalized-results.json"); const campaign = JSON.parse( await readFile(normalizedFile, "utf8"), ) as PublishedCampaign; if (!Array.isArray(campaign.results)) { throw new Error("Public history source has invalid campaign results"); } await removeExistingPublicVisuals(path.join(destination, "evidence")); const transform = input.transform ?? createPublicLayoutPreview; let previewCount = 0; const results: PublishedResult[] = []; for (const untrustedResult of campaign.results) { if (!untrustedResult || typeof untrustedResult !== "object") { throw new Error("Public history source has an invalid result"); } const result = untrustedResult as PublishedResult; if (result.status !== "passed" || result.evidenceValid === false) { results.push(result); continue; } if ( typeof result.executionId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,299}$/.test(result.executionId) || !Number.isSafeInteger(result.attempt) || Number(result.attempt) < 1 || !Array.isArray(result.screenshots) || result.screenshots.length === 0 ) { throw new Error("Passing result lacks safe screenshot provenance"); } const base = path.join( destination, "evidence", result.executionId, `attempt-${String(result.attempt)}`, ); const publicVisuals = path.join(base, PUBLIC_VISUAL_DIRECTORY); await rm(publicVisuals, { recursive: true, force: true }); await mkdir(publicVisuals, { recursive: true }); const screenshots = []; const seen = new Set(); for (const entry of result.screenshots) { const screenshot = safePassedScreenshot(entry); if (seen.has(screenshot.privateFile)) { throw new Error("Passing result has duplicate screenshot metadata"); } seen.add(screenshot.privateFile); const sourceScreenshot = path.join(base, screenshot.privateFile); const publicScreenshot = path.join(publicVisuals, screenshot.privateFile); await validatePrivateScreenshot(sourceScreenshot); await transform(sourceScreenshot, publicScreenshot); await validatePublicPreview(publicScreenshot); const { privateFile: _, ...publishedScreenshot } = screenshot; screenshots.push(publishedScreenshot); previewCount += 1; } results.push({ ...result, screenshots }); } await writeFile( normalizedFile, `${JSON.stringify({ ...campaign, results }, null, 2)}\n`, "utf8", ); await prunePrivateHistoryEvidence(destination); await regenerateRunnerDashboard({ bundle: destination, historyFile: null }); console.log( `Prepared ${previewCount} public low-resolution layout preview(s); full-resolution visual evidence remains private`, ); return { previewCount }; } async function main() { const [source, destination] = process.argv .slice(2) .filter((argument) => argument !== "--"); if (!source || !destination) { throw new Error( "Usage: history-public-bundle.ts ", ); } await preparePublicHistoryBundle({ source, destination }); } if ( process.argv[1] && path.resolve(process.argv[1]) === path.resolve(import.meta.filename) ) { await main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; }); }