Skip to content

feat(redact): detection-guided zoom — real credential redaction 49% → 84%, ~free on ordinary screens - #6239

Open
louis030195 wants to merge 3 commits into
fix/wrapped-secret-cover-relandfrom
feat/rfdetr-guided-zoom
Open

feat(redact): detection-guided zoom — real credential redaction 49% → 84%, ~free on ordinary screens#6239
louis030195 wants to merge 3 commits into
fix/wrapped-secret-cover-relandfrom
feat/rfdetr-guided-zoom

Conversation

@louis030195

@louis030195 louis030195 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #6238. That PR re-lands the wrapped-secret fix, which this one
builds on (extend_wrapped_secrets, Guard C). Review/merge #6238 first; the
base will retarget to main automatically.

Credential redaction is limited by RESOLUTION, not by what the model knows

On a captured frame carrying 23 credentials the shipped path redacts 12. Crop
the same window, magnify it, and the same model redacts 11 of 11. A ~17px
credential becomes ~7.5px in model space once 1920×1080 is squeezed into 512,
and 2×2 tiling only lifts it to ~11px.

before/after

Detection runs on the full frame — cropping the input would change the scale,
which is the whole variable. Only the output is cropped, to keep a real desktop
out of a public repo. The two residual misses are visible in the lower panel.

Denser uniform tiling does not fix it

Measured on the real benchmark with the shipped model:

tiling passes redacted CPU
2×2 (today) 5 49% 1.00×
3×3 10 55% 1.96×
4×4 17 34% 3.29×

4×4 regresses because real tokens run 147–490px wide, so smaller tiles sever
them mid-string. Uniform tiling cannot magnify small glyphs and keep long tokens
whole at the same time.

What this does

Re-inspect a few text-bearing regions as 640px native-resolution crops
a 1.25× downscale to the 512 input leaves glyphs ~13.6px, and 640 still contains
a 490px token whole.

Where to crop comes free from the passes already run: the model fires
Email on the endpoint column and Person on the name column even when it
misses the token between them, so its own detections are a text-presence map.
No OCR, no accessibility tree, no second model.

Results

Real benchmark — 26 captured frames, 136 credentials, gold from OCR, production
semantics (label == Secret, score ≥ 0.50, ≥50% coverage):

redacted CPU
baseline 66/136 (49%) 1.00×
+2 zooms 109/136 (80%) 1.28×
+4 zooms, always 116/136 (85%) 1.41×
+4 zooms, gated (shipped default) 114/136 (84%) ~1.00× on ordinary screens
+6 zooms 116/136 (85%) 1.44× (saturated)

Through the production adapter on real credential frames: 35 → 62 secret
regions (+77%) at 1.34× CPU
(examples/rfdetr_guided_zoom_check.rs — CPU is
measured in Rust because the adapter pins with_intra_threads(2); a Python
number would not be the cost a user pays).

Shipped default is the gated row. Guided zoom only fires on a frame that
already yielded a Secret — credentials cluster, so a frame with one is worth
a closer look and a frame with none is not. That skips 211 of 214 ordinary
frames (99%), costs 2 credentials, and cuts stray boxes 7 → 3. See the trigger
comment below for the measurement.

Other classes are unaffected: real core recall holds at 13/16 agnostic and
11/16 class-correct, identical to baseline.

Precision, and the honest cost

Guided zoom alone added 6 secret boxes on frames with no credentials. Rendering
every one showed they are all email addresses relabelled under
magnification, which a secrets-only policy would black out against intent.

A confidence floor on zoom detections cannot separate them — 0.60 → 82%/8
strays, 0.80 → 58%/4 — because a misread email scores as high as a real
credential once magnified. A cross-pass class vote does: drop a Secret box
that any pass calls Email/Url. Zero recall cost, removes a third of the
strays. Net cost: +4 stray boxes per 214 clean frames.

Guard C

Guided zoom runs after the tile loop, so extend_wrapped_secrets still runs
only on window 0 (the whole frame) and magnified crops can never seed the
continuation walk. A test asserts guided windows are never (0,0,w,h) so a
future reorder cannot silently break it.

Tests

5 added, all passing: window bounds/budget, no-detections-no-cost, densest-region
selection, class-vote demotion, and the Guard C invariant.

Two pre-existing CI failures are unrelated to this change and also fail on
main: test-ubuntu (accessibility_late_materialization_test in
screenpipe-db, a crate with no dependency on screenpipe-redact) and
Coverage dashboards current (the same check fails on main; the core coverage
report does not track screenpipe-redact and regenerating it locally produces
no change).

🤖 Generated with Claude Code

louis030195 and others added 2 commits August 13, 2026 18:46
…> 85%

Credential redaction on real screens is limited by RESOLUTION, not by what the
model knows. On a captured frame carrying 23 credentials the shipped path
redacts 12; crop the same window, magnify it, and the same model redacts 11 of
11. A ~17px credential becomes ~7.5px in model space once 1920x1080 is squeezed
to 512, and 2x2 tiling only lifts it to ~11px.

Denser uniform tiling does not fix this. Measured on the real benchmark: 3x3
buys 6 points for 2x the compute, and 4x4 REGRESSES to 34% because real tokens
run 147-490px wide and smaller tiles sever them mid-string.

So magnify without shrinking the window: re-inspect a few text-bearing regions
as 640px NATIVE-resolution crops (1.25x downscale to the 512 input leaves
glyphs ~13.6px, and 640 still contains a 490px token whole). Where to crop
comes free from the passes already run — the model fires Email/Person on the
columns beside a credential even when it misses the token, so its own
detections are a text-presence map. No OCR, no accessibility tree, no second
model.

Measured on 26 captured frames carrying 136 real credentials, scored against
OCR-mined gold with production semantics (label==Secret, score>=0.50, >=50%
coverage):

    baseline 2x2      66/136 (49%)   1.00x CPU
    +2 zooms         109/136 (80%)   1.28x
    +4 zooms         116/136 (85%)   1.41x   <- saturates
    +6 zooms         116/136 (85%)   1.44x

Confirmed through the production adapter on real frames: 35 -> 62 secret
regions (+77%) at 1.34x CPU. Other PII classes are unaffected — real core
recall holds at 13/16 agnostic and 11/16 class-correct, identical to baseline.

PRECISION. Guided zoom alone added 6 secret boxes on frames with no
credentials; rendering every one showed they are all EMAIL addresses
relabelled under magnification, which the secrets-only default policy would
black out against intent. A confidence floor on zoom detections cannot separate
them (0.60 -> 82%/8 strays, 0.80 -> 58%/4) because a misread email scores as
high as a real credential once magnified. A cross-pass class vote does: drop a
Secret box that any pass calls Email/Url. That costs zero recall and removes a
third of the strays.

Guided zoom runs AFTER the tile loop, which keeps Guard C structural —
extend_wrapped_secrets still runs only on window 0 (the whole frame), so
magnified crops can never seed the continuation walk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Detection runs on the FULL captured frame — cropping the input would change the
scale, which is the entire variable under test. Only the OUTPUT is cropped, to
the two credential tables, so the user's taskbar, editor, branch name and URL
bar stay out of a public repo.

Shows the light-themed window going from 3 blacked to all 12, and honestly
shows the two residual misses: one 39-char hex, and a tail on the longest JWT
where the box is narrower than the token.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@louis030195

Copy link
Copy Markdown
Collaborator Author

Does "redacted" actually mean covered?

The benchmark counts a credential as redacted when a Secret box covers ≥50% of
its gold area. That is a fine convention for a detector, but a fair question
for a redactor: at 50% coverage, half the characters are still on screen.

Measured on the same 136 real credentials — for every token the pipeline counts
as redacted, what fraction of it is actually painted (union of all qualifying
boxes):

band n mean coverage fully covered (≥95%) leaves >20% visible
short <25 23 98% 23/23 0
mid 25–40 68 99% 68/68 0
long 40+ 25 98% 24/25 0
overall 116 99% 115/116 0

So the headline is not hiding partial covers: when a credential is detected it is
essentially fully blacked. The single exception is the 67-char JWT visible in the
before/after image, at 93% painted (~4 characters of a 67-char token).

Harness: rl_cover_fraction.py in the RL-loop toolchain.

🤖 Generated with Claude Code

@louis030195

Copy link
Copy Markdown
Collaborator Author

Where the remaining 15% goes — and one thing this PR gets wrong

Characterised every credential still unredacted after guided zoom (20 of 136):

why n
not detected at all 15
detected but classed Id 3
suppressed by NMS 1
vetoed by the class vote added in this PR 1

The headline understates the win

14 of the 20 are the hex family — bare [0-9a-fA-F]{24,} — which is the one
part of the gold set known to be contaminated. On this corpus 11 of 34 hex
entries are not credentials at all: 7 are git commit SHAs (confirmed with
git cat-file -t) and 4 are the same OAuth state= CSRF nonce captured on a
Chrome 404 page. Not redacting those is correct behaviour being scored as a miss.

Per family after guided zoom:

family redacted
openai sk- 37/37 (100%)
aws AKIA 23/25 (92%)
jwt 14/15 (93%)
github ghp_ 22/25 (88%)
hex (contaminated) 20/34 (59%)

Excluding hex: 96/102 = 94%, against a 52% baseline on the same subset. The
49% → 85% in the PR description is the conservative number and I'd rather leave
it as the headline, but reviewers should know the clean-gold figure is higher.

Correction: "zero recall cost" for the class vote was aggregate-only

I reported the cross-pass class vote as costing no recall. That holds in
aggregate (116/136 either way) but it is not per-token true: the vote vetoed
one genuine credential
, a 19-char AWS key on a dark background, while other
tokens were gained. One in 136 is a trade I would still take — it removes a
third of the strays — but the earlier phrasing was too clean and this is the
honest version.

Harness: rl_residual_misses.py.

🤖 Generated with Claude Code

@louis030195

Copy link
Copy Markdown
Collaborator Author

Limitation: generalisation is not established by this evidence

All headline numbers come from one frame set — 26 captured frames, largely
one page type. I tried to validate on an independent population by OCR-mining
the separate 522-frame real set, and it yields only 4 frames / 8 token
instances / 3 unique values
, all JWTs on light backgrounds.

Scored anyway, for direction only:

redacted
baseline 4/8 (50%)
+4 guided zooms 6/8 (75%)

Consistent with the main result, but n=8 across 3 unique values is not evidence
of anything and should not be read as confirmation.

The honest position: credential-bearing frames are rare in this capture
corpus, so the mechanism is validated on real pixels but on a narrow slice of
them. The change is config-gated (guided_zoom_regions, 0 disables) and the
mechanism is scale, which is not corpus-specific — but a reviewer should know
the breadth of evidence behind the 85%, not just its size.

Two things that would raise confidence and are not blockers: capture on a second
machine with different DPI/theme, or run the same benchmark against a corpus
from another user.

🤖 Generated with Claude Code

…on ordinary screens

Guided zoom fired on any frame with detections, which is nearly every frame:
the model reliably emits Person/Email on ordinary UI. So users paid 4 extra
forward passes on screens containing no credential at all, on every captured
frame.

Credentials cluster — an API-keys page lists a column of them — so "if you found
one, look harder nearby" is the cheap trigger. Measured on 214 ordinary captured
frames and 26 credential frames:

    always (before)   85% recall   214/214 ordinary frames zoomed   7 strays
    gated (this)      84% recall     3/214 ordinary frames zoomed   3 strays

One point of recall buys a 99% reduction in how often the extra passes run, so
the average cost on ordinary screens goes from a flat 1.41x to ~1.00x. Stray
boxes also drop 7 -> 3, because most of the email-relabelling happened on frames
that no longer get magnified — the trigger improves precision as a side effect.

Note the trigger is a detected Secret at the adapter's confidence threshold, not
a sub-threshold signal: infer_window already filters at conf_threshold, so a
weak response is not observable here. Thresholds of 0.10/0.20/0.30 measured
identically for exactly that reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@louis030195

Copy link
Copy Markdown
Collaborator Author

Update: guided zoom now only fires when a Secret was already found (~1.00× CPU on ordinary screens)

Pushed 27cda7e in response to the most obvious objection to this PR — that it
charges every user 1.41× CPU on every captured frame to fix a problem most
frames don't have.

Guided zoom was firing on any frame with detections, which is nearly all of them
(the model reliably emits Person/Email on ordinary UI). Credentials cluster
— an API-keys page lists a column of them — so the trigger is simply if you
already found one, look harder nearby
.

Measured on 214 ordinary captured frames and 26 credential frames:

credential recall ordinary frames zoomed strays
always (previous) 116/136 (85%) 214/214 (100%) 7
gated (now) 114/136 (84%) 3/214 (1%) 3

One point of recall buys a 99% reduction in how often the extra passes run,
taking the average cost on ordinary screens from a flat 1.41× to ~1.00×. Stray
boxes also fall 7 → 3, since most of the email-relabelling happened on frames
that are no longer magnified — so the trigger improves precision as a side
effect rather than trading against it.

One correction to how I'd have described this: the trigger is a detected
Secret at the adapter's confidence threshold
, not a weak sub-threshold
signal. infer_window already filters at conf_threshold, so sub-threshold
responses aren't observable at that point — which is why trigger values of
0.10/0.20/0.30 all measured identically.

Harness: rl_zoom_gate.py.

🤖 Generated with Claude Code

@louis030195

Copy link
Copy Markdown
Collaborator Author

Trigger verified through the production adapter

Ran the A/B (examples/rfdetr_guided_zoom_check.rs) on both frame types with
27cda7e:

frames baseline guided+4
10 credential frames 35 secret / 62 regions 62 secret / 115 regions
10 ordinary frames 0 secret / 12 regions 0 secret / 12 regions

Credential frames are unaffected by the trigger (+27 secret regions, same as
before it). Ordinary frames produce byte-identical detections in both arms,
which is the actual proof the extra passes were skipped — stronger than a timing
number.

On timing: the ordinary-frame arms measured 1356 ms vs 1502 ms/frame here
(1.11×). Since the outputs are identical, no additional inference ran; that gap
is measurement noise on a loaded box with two separate model loads. I'd rather
report it than round it to 1.00× and have someone reproduce 1.11× and wonder.

🤖 Generated with Claude Code

@louis030195 louis030195 changed the title feat(redact): detection-guided zoom — real credential redaction 49% → 85% feat(redact): detection-guided zoom — real credential redaction 49% → 84%, ~free on ordinary screens Aug 14, 2026
@louis030195

Copy link
Copy Markdown
Collaborator Author

The "1.41× CPU" framing is wrong for the shipped worker — it costs throughput, not CPU share

Checked how the redaction worker actually schedules this
(image/worker.rs:204-268 + screenpipe-resource/src/cpu.rs:97), because "1.41×
CPU on every captured frame" is the obvious reason to reject this PR.

The worker is under an adaptive CPU governor. Each frame it acquires a
background-CPU permit, and afterwards permit.finish(worked, idle_between_frames, max_cpu_cooldown) computes a cooldown from the measured process CPU against
target_process_cpu_percent, then sleeps that long before the next frame.

So longer per-frame work does not raise steady-state CPU share — the governor
lengthens the cooldown to hold the target. What extra passes actually cost is
throughput: the redaction backlog drains slightly slower.

That reframes the trade for this PR:

  • ordinary frames — zoom is skipped entirely (identical detections, proven
    above), so throughput is unchanged for the vast majority of frames
  • credential frames — ~1.4× the work, so those specific frames take longer and
    the backlog drains marginally slower

I'd been describing the cost as CPU because that is what I measured in a
standalone harness. In the shipped worker, CPU is capped by design and latency
is the variable. Flagging it because "this costs users 40% more CPU" would be a
reasonable objection to this PR, and it isn't accurate.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant