Title
Built-in redaction ruleset and test corpus
Summary
Implement the eleven built-in redaction rules from DESIGN.md §8 with their
exact ids and patterns, plus the true-positive / false-positive regression
corpus that pins their behavior.
Context
The ruleset table in DESIGN §8 is the contract: rule ids are public API
(users reference them in allowlist docs), and the "commit hashes and
ordinary text are never masked" guarantee is what keeps reports readable.
This issue is deliberately separate from the engine (16) because the corpus
is large and the patterns need focused review.
Scope
internal/redact/builtin.go (func Builtin() []Rule),
builtin_test.go, testdata/corpus.json — a JSON array of cases:
{"name": "<unique>", "rule": "<rule-id or NONE>", "input": "<text>", "want": "<exact expected output>"}. JSON strings carry multiline inputs
(PEM blocks) losslessly and want pins the exact masking result.
Detailed Requirements
- Implement exactly the rules of DESIGN §8's table, in that order, with
those ids: private-key-block (Block rule), aws-access-key-id,
github-token, slack-token, google-api-key, openai-anthropic-key,
jwt, authorization-header (mask value group),
env-assignment (mask value group, keep name), cli-secret-flag
(mask value group), url-userinfo (mask password group).
- Pattern refinements (allowed to tighten, never loosen, relative to the
table; document any deviation in code comments AND update DESIGN §8 in
the same PR if a pattern must change):
env-assignment must handle export NAME=…, quoted values, and stop
at whitespace for unquoted values.
cli-secret-flag must handle --token=x, --token x, -p=x forms
listed in the pattern.
url-userinfo masks only the password span (user:****@host).
- Corpus — minimum content:
- Positives (each rule ≥ 5 lines): realistic fakes (e.g.
AKIA + 16 uppercase, ghp_ + 36 alnum, quoted/unquoted env
assignments, curl -H "Authorization: Bearer …", a 3-line PEM block,
JWT with three segments, https://user:pass@host/path,
--api-key=sk-… which must double-mask under two rules without
corruption).
- Negatives (must NOT mask, ≥ 20 lines): 12/40-char git hashes; commit
subject fix: rotate token handling; TOKEN_COUNT=5 (name not in the
secret-name list — verify the pattern's name list is anchored);
--tokenizer bert; URL without userinfo; skiing sk-lift schedule
(no, sk-lift — 7 chars — must not match sk-[A-Za-z0-9_-]{20,});
Japanese text; password: prose without assignment; base64 blob of 30
chars in plain prose (entropy is v2 — must pass through).
- Corpus runner test: for every case, assert
Redact(input) == want
byte-for-byte. Derived assertions: cases with rule != "NONE" must have
want containing [REDACTED:<rule>] and must differ from input
(self-checking corpus — a want equal to input on a positive case
fails the suite); cases with rule == "NONE" must have
want == input (asserted at load time so corpus typos are caught).
- A
doc.go table listing rule ids + one-line descriptions (renders in
pkg docs; README links here in 31).
Acceptance Criteria
Validation
go test -race -cover ./internal/redact/ in PR, plus paste 5 sample
positive→masked lines (fake secrets only) demonstrating output shape.
Dependencies
Non-goals
Entropy heuristics, cloud-provider-exhaustive token catalogs (the 11 rules
are v1 scope; additions are post-v1 issues driven by real misses),
locale-specific secrets.
Design References
docs/DESIGN.md §8 (normative rule table)
docs/decisions/ADR-004-redaction-default-on.md
Source of truth: docs/issues/17-redaction-ruleset.md (PR #1, branch docs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies: docs/ISSUE_PLAN.md (this is issue 17 of 33).
Title
Built-in redaction ruleset and test corpus
Summary
Implement the eleven built-in redaction rules from DESIGN.md §8 with their
exact ids and patterns, plus the true-positive / false-positive regression
corpus that pins their behavior.
Context
The ruleset table in DESIGN §8 is the contract: rule ids are public API
(users reference them in
allowlistdocs), and the "commit hashes andordinary text are never masked" guarantee is what keeps reports readable.
This issue is deliberately separate from the engine (16) because the corpus
is large and the patterns need focused review.
Scope
internal/redact/builtin.go(func Builtin() []Rule),builtin_test.go,testdata/corpus.json— a JSON array of cases:{"name": "<unique>", "rule": "<rule-id or NONE>", "input": "<text>", "want": "<exact expected output>"}. JSON strings carry multiline inputs(PEM blocks) losslessly and
wantpins the exact masking result.Detailed Requirements
those ids:
private-key-block(Block rule),aws-access-key-id,github-token,slack-token,google-api-key,openai-anthropic-key,jwt,authorization-header(mask value group),env-assignment(mask value group, keep name),cli-secret-flag(mask value group),
url-userinfo(mask password group).table; document any deviation in code comments AND update DESIGN §8 in
the same PR if a pattern must change):
env-assignmentmust handleexport NAME=…, quoted values, and stopat whitespace for unquoted values.
cli-secret-flagmust handle--token=x,--token x,-p=xformslisted in the pattern.
url-userinfomasks only the password span (user:****@host).AKIA+ 16 uppercase,ghp_+ 36 alnum, quoted/unquoted envassignments,
curl -H "Authorization: Bearer …", a 3-line PEM block,JWT with three segments,
https://user:pass@host/path,--api-key=sk-…which must double-mask under two rules withoutcorruption).
subject
fix: rotate token handling;TOKEN_COUNT=5(name not in thesecret-name list — verify the pattern's name list is anchored);
--tokenizer bert; URL without userinfo;skiing sk-lift schedule(no,
sk-lift— 7 chars — must not matchsk-[A-Za-z0-9_-]{20,});Japanese text;
password:prose without assignment; base64 blob of 30chars in plain prose (entropy is v2 — must pass through).
Redact(input) == wantbyte-for-byte. Derived assertions: cases with
rule != "NONE"must havewantcontaining[REDACTED:<rule>]and must differ frominput(self-checking corpus — a
wantequal toinputon a positive casefails the suite); cases with
rule == "NONE"must havewant == input(asserted at load time so corpus typos are caught).doc.gotable listing rule ids + one-line descriptions (renders inpkg docs; README links here in 31).
Acceptance Criteria
Redact(input) == wantfor all cases); negativesbyte-identical; the multiline PEM positive is present as a JSON case
and collapses to one
[REDACTED:private-key-block]token.--api-key=sk-…) yields a stable, parseable result(no nested/garbled tokens) — exact expected string in the test.
green (
[REDACTED:…]tokens match no builtin rule).(compile-time guarantee via
regexp.MustCompile— build failsotherwise).
Validation
go test -race -cover ./internal/redact/in PR, plus paste 5 samplepositive→masked lines (fake secrets only) demonstrating output shape.
Dependencies
Non-goals
Entropy heuristics, cloud-provider-exhaustive token catalogs (the 11 rules
are v1 scope; additions are post-v1 issues driven by real misses),
locale-specific secrets.
Design References
docs/DESIGN.md§8 (normative rule table)docs/decisions/ADR-004-redaction-default-on.mdSource of truth:
docs/issues/17-redaction-ruleset.md(PR #1, branchdocs/v1-design). If this issue and the repo docs disagree, the docs win. Execution order and dependencies:docs/ISSUE_PLAN.md(this is issue 17 of 33).