diff --git a/BACKLOG-shot.md b/BACKLOG-shot.md index 4302b8d..84b51e0 100644 --- a/BACKLOG-shot.md +++ b/BACKLOG-shot.md @@ -75,4 +75,19 @@ PNG-size note: 4801/4802 B at 1280×800 is a byte-stable blank-frame fingerprint ## Adversary findings -(Adversary-owned section.) +### [adversary] A1 — blank-retry can REGRESS a larger frame to a worse one (LOW, non-blocking) — OPEN +**Where:** `runner/harness/screenshot.py` `_snap_with_blank_retry` (ce50f64). +**What:** the retry overwrites `out_path` *unconditionally* with the second screenshot. The code/comment +claim "the retry only ever replaces a tiny frame with a later one" — but *later ≠ better*. If the first +frame is e.g. 9999 B (a partial render, just under `BLANK_SIZE_BYTES=10000`) and the page regresses in the +extra 4 s settle (redirect, session-timeout splash, error overlay), the retry can yield a 4801 B blank that +**overwrites the better 9999 B frame**. The Builder's unit test only covers blank→blank (4801→4802); the +bigger→smaller regression is untested. +**Repro (cold, my independent probe, not the Builder's test file):** fake page returning sizes +`[9999, 4801]` → `_snap_with_blank_retry` keeps **4801** (the worse frame). +**Severity:** LOW. R7 holds (cosmetic only, never affects verdict); my M2 per-PNG visual check is the +backstop — any actually-blank final PNG will FAIL that recipe regardless. Filed for hardening, not a veto. +**Suggested guard (trivial, strictly safer):** keep the larger frame — only overwrite if +`getsize(retry) >= getsize(first)` (or snap retry to a temp path and pick `max`). Then extend the unit +test with a bigger→smaller case asserting the larger frame survives. +**Closes:** only I close this, after re-test. Non-blocking for an M2 claim, but I will re-check at M2.