Skip to content

Add detection dispositions, typed role surrogates, and a sentinel - #12

Merged
greenseeing merged 1 commit into
mainfrom
feat/detection-disposition-and-typed-surrogates
Aug 25, 2026
Merged

Add detection dispositions, typed role surrogates, and a sentinel#12
greenseeing merged 1 commit into
mainfrom
feat/detection-disposition-and-typed-surrogates

Conversation

@greenseeing

Copy link
Copy Markdown
Owner

Lands three related pieces of the detection/redaction plan together (they share the Kind enum, the scrub sweep, and shapes.py, so they don't split cleanly).

What changed

  • D0 (D0 — Three-tier detection disposition model (SCRUB/WARN/REPORT-ONLY) + first rules #2) — three-tier disposition model (SCRUB / WARN / REPORT-ONLY) as a first-class attribute of every rule/Match/Finding. GitHub PAT → SCRUB (shape-preserving random look-alike); Twilio SK 32-hex → WARN row with a line number; Stripe pk_ public identifier → left unchanged. An explicit scrub decision (promotion, --also, or a review override) outranks WARN. --strict still gates on warn rows.
  • R2 (R2 — --also role hints → typed surrogates (host/person/project) #4)--also-host / --also-person / --also-project type a declared value's surrogate as host-a / person-b / project-c (new roles channel on LocalIdentity, new Kind.PERSON / Kind.PROJECT); casing collapses to one surrogate.
  • R3 (R3 — Self-advertising surrogate sentinel + bounded IPv4 alias pool #5) — minted secret surrogates carry a self-advertising sentinel; a second scrub pass is a fixed point (no re-scrub, no re-warn) for readable counters, secret randoms, emails and documentation IPs, including the redacted-a self-collision. The skip binds to the actual minted alias, so an ordinary compound that merely looks like a surrogate label (user-alice) still gets its declared value scrubbed. The IPv4 documentation pool (762 addresses) warns on overflow instead of silently wrapping.

Also

  • Fixes a pre-existing quadratic ReDoS in the email rule (unbounded local part rescanned O(n²) on a long run); bounded to RFC 5321's 64 octets. A long adversarial input now completes in <0.5s.
  • Known limitation (documented in CHANGELOG): shape-only surrogates (random MAC/UUID/hex) are still re-randomised on a re-scrub — an ASCII sentinel can't live in them without breaking their shape or risking a real value being kept.

Verification

  • 850 tests pass; ruff + mypy clean. Verified end-to-end via the scrubbr CLI and the scrub() seam. Reviewed by code-quality-reviewer; a CRITICAL (compound-word leak) and HIGH (WARN outranking explicit scrub) were fixed with regression tests.

Closes #2
Closes #4
Closes #5

🤖 Generated with Claude Code

Land three related pieces of the detection/redaction plan together; they
share the Kind enum, the scrub sweep, and shapes.py, so they don't split
cleanly.

D0 (#2) — three-tier disposition model. Every rule/Match/Finding now carries
a disposition. SCRUB rewrites (a GitHub PAT becomes a shape-preserving random
look-alike, never a readable or reversible alias); WARN flags but leaves in
place (a Twilio SK 32-hex key, too collision-prone to auto-scrub, surfaces as
a review warn row with a line number); a public identifier that only shares a
shape (Stripe pk_) gets no rule. An explicit scrub decision — a promotion, an
--also, or a review override — outranks WARN, so a keyword-confirmed secret is
never scrubbed in one place and left verbatim in another. --strict still gates
on the warn rows.

R2 (#4) — --also-host / --also-person / --also-project type a declared value's
surrogate as host-a / person-b / project-c via a new roles channel on
LocalIdentity and new Kind.PERSON / Kind.PROJECT; casing collapses to one.

R3 (#5) — minted secret surrogates carry a self-advertising sentinel and the
sweep keeps any value already exactly equal to its own alias, so a second scrub
pass is a fixed point (no re-scrub, no re-warn) for readable counters, secret
randoms, emails and documentation IPs — including the redacted-a self-collision.
The skip binds to the actual minted alias, so an ordinary compound that merely
looks like a surrogate label (user-alice) still gets its declared value scrubbed.
The IPv4 documentation pool (762 addresses) now warns on overflow instead of
silently wrapping two addresses onto one alias.

Also fix a pre-existing quadratic ReDoS in the email rule: an unbounded local
part rescanned the whole remaining run at every start position looking for an
@, O(n^2) on a large dump. Bounding it to RFC 5321's 64 octets makes the scan
linear; a long adversarial input now completes in well under a second.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@greenseeing
greenseeing merged commit 94f0584 into main Aug 25, 2026
1 check passed
@greenseeing
greenseeing deleted the feat/detection-disposition-and-typed-surrogates branch August 25, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant