fix(e2e): fail closed on readable previews
This commit is contained in:
parent
e7a073778e
commit
e5857b602e
|
|
@ -1108,11 +1108,12 @@ jobs:
|
|||
if: always()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v convert >/dev/null 2>&1; then
|
||||
if ! command -v convert >/dev/null 2>&1 || ! command -v tesseract >/dev/null 2>&1; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install --no-install-recommends -y imagemagick
|
||||
sudo apt-get install --no-install-recommends -y imagemagick tesseract-ocr
|
||||
fi
|
||||
convert -version
|
||||
tesseract --version
|
||||
|
||||
- name: Prepare public history bundle with redacted layout previews
|
||||
id: prepare_public_history
|
||||
|
|
|
|||
|
|
@ -208,8 +208,9 @@ Permanent public history has a narrower boundary. A trusted job without
|
|||
provider or AWS credentials copies successful declared PNG screenshots into a
|
||||
separate publication tree. It validates the PNG container, reduces each image
|
||||
to at most 160 pixels on either edge, applies a strong blur, limits the image to
|
||||
24 colors, and removes metadata. These layout previews preserve coarse UI
|
||||
state while making rendered task and provider text unreadable. The job then
|
||||
24 colors, and removes metadata. It then runs OCR and rejects the whole public
|
||||
bundle if any letter or digit remains readable. These layout previews preserve
|
||||
coarse UI state while making rendered task and provider text unreadable. The job then
|
||||
removes the full-resolution raster files, failure screenshots, video, archives,
|
||||
SVG, and generated Playwright/blob/HTML report trees. Only the derived
|
||||
`public-visuals/*.png` previews and allowlisted inert evidence (`.json`, `.log`,
|
||||
|
|
@ -384,8 +385,9 @@ GitHub Pages remains the stable latest dashboard. Enable Pages with GitHub
|
|||
Actions as its source and set `RUNNER_FULL_STACK_E2E_PUBLISH_PAGES=true`.
|
||||
The trusted report job creates a separate public bundle before the AWS role is
|
||||
available. It publishes only blurred, low-resolution, metadata-free layout
|
||||
previews for successful declared screenshots and allowlisted inert structured
|
||||
text. The S3/CloudFront history and optional Pages mirror use this same bundle.
|
||||
previews with an empty OCR result for successful declared screenshots and
|
||||
allowlisted inert structured text. The S3/CloudFront history and optional Pages
|
||||
mirror use this same bundle.
|
||||
Full-resolution and failure screenshots, video, archives, generated reports,
|
||||
databases, Paperclip homes, workspaces, raw logs, and credentials are never
|
||||
published. Sanitized allowlisted `.log` copies may be public only after
|
||||
|
|
|
|||
|
|
@ -199,11 +199,13 @@ It accepts only declared screenshots from passing results. It validates a
|
|||
bounded, non-interlaced PNG container before invoking ImageMagick with strict
|
||||
memory, disk, thread, and time limits. It reduces each image to at most 160
|
||||
pixels on either edge, applies a strong blur, limits the palette, removes alpha
|
||||
and metadata, and validates the new PNG again. This is a layout preview, not
|
||||
diagnostic evidence. It then prunes full-resolution raster files, all failure
|
||||
images, video, archives, active SVG, and generated reports. This transformation
|
||||
runs before AWS credentials are available. The AWS job downloads only the
|
||||
prepared public tree.
|
||||
and metadata, and validates the new PNG again. Tesseract must then find no
|
||||
readable letter or digit. OCR output is never logged. Any readable text or OCR
|
||||
failure blocks the whole publication. This is a layout preview, not diagnostic
|
||||
evidence. The job then prunes full-resolution raster files, all failure images,
|
||||
video, archives, active SVG, and generated reports. This transformation runs
|
||||
before AWS credentials are available. The AWS job downloads only the prepared
|
||||
public tree.
|
||||
|
||||
The remaining allowlist contains `public-visuals/*.png`, `.json`, `.log`, `.md`,
|
||||
and `.txt` evidence, plus the generated dashboard, normalized
|
||||
|
|
|
|||
|
|
@ -263,6 +263,24 @@ async function validatePublicPreview(file: string) {
|
|||
});
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
@ -313,6 +331,7 @@ export async function createPublicLayoutPreview(
|
|||
{ 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 });
|
||||
|
|
|
|||
|
|
@ -690,6 +690,8 @@ describe("public repository paid workflow security", () => {
|
|||
expect(workflow).toContain("unexpected_png=");
|
||||
expect(workflow).toContain("passed_count=");
|
||||
expect(workflow).toContain("pnpm test:e2e:runner:history:prepare");
|
||||
expect(report).toContain("tesseract-ocr");
|
||||
expect(report).toContain("tesseract --version");
|
||||
expect(report).not.toContain("id-token: write");
|
||||
expect(report).not.toMatch(
|
||||
/(?:OPENAI|ANTHROPIC|OPENROUTER|DAYTONA)_API_KEY/,
|
||||
|
|
|
|||
Loading…
Reference in New Issue