Skip to content

feat(rules): detect Redis AUTH credentials across four leak shapes - #18

Closed
arr-wf wants to merge 1 commit into
mainfrom
feat/redis-auth-rules
Closed

arr-wf wants to merge 1 commit into
mainfrom
feat/redis-auth-rules

Conversation

@arr-wf

@arr-wf arr-wf commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes a structural detection gap for Redis credentials: a bare Redis
AUTH password has no token prefix and no key=value shape — the two forms
gitleaks-class rules can detect — so four common leak shapes passed through
unredacted (upstream betterleaks v1.2 + embedded pack as of v0.1.1):

  • the plain AUTH [user] <password> command line as error logs and pasted
    shell history show it
  • the node-redis structured error form (args: [ 'AUTH', … ] arrays and
    their JSON rendering, with or without an ACL username)
  • redis-cli -a / --pass invocations
  • RESP protocol dumps in all three encodings they appear in logs (raw CRLF,
    escaped CRLF, space-flattened)

The four embedded rules are ported rule-for-rule from
webflow/secrets-scrubber
(@santi-webflow — your project inspired this, and the four gates below are
yours; requesting your review specifically on the JS→RE2 fidelity).

RE2 + CEL adaptations from the JS originals (RE2 has no lookaheads, betterleaks
has no replace-callbacks):

  1. Reference-value exclusion ($VAR refs, process.env.* chains, template
    placeholders must pass) → per-rule CEL filter (matchesAny on
    finding["secret"]).
  2. Plain-AUTH "value must contain an uppercase letter or digit" gate →
    [a-z]*[A-Z0-9] prefix, keeping the secret-span start anchored.
  3. RESP tempered-dot lookahead → [^\s\\]+ (bulk strings can't contain CRLF;
    the escaped encoding reserves the backslash).
  4. The RESP rule additionally requires the *N array header — all of
    secrets-scrubber's RESP fixtures carry it; without the anchor, a
    dollar-amount next to the literal word "auth" in prose would redact an
    innocent trailing word. Recall on the leak shapes is unchanged.
  5. Port deviation (js has no equivalent hazard): every rule carries a
    minimum-length floor (16 plain-AUTH, 8 elsewhere) — a sub-8-char finding
    trips Splice's over-redact backstop against unrelated lines carrying the
    same short form (a stray / swept every slash-bearing line during
    development). Over-redacting a short password is safe; the cascade is not.

Span semantics follow the bearer-token rule's house style: the whole match is
redacted (AUTH verb, RESP framing, ACL username vanish with the password).

Test plan

  • internal/rules/redis_auth_rules_test.go: 21 table-driven cases through the
    real detector (race-clean) — all positive leak shapes, secrets-scrubber's
    own false-positive suite, and the port gates.
  • 43-assertion shell matrix (run against a locally built binary, embedded
    path, no CTXCOP_RULES): 19 positives (incl. via ctxcop run Bash-wrapper
    path), 14 negatives, idempotency ×2, span behavior ×3, self-scan ×5
    byte-identical (run-order nondeterminism guard).
  • go test -race -count=1 ./... — clean.
  • gofmt -w . clean; go vet ./... clean.
  • ctxcop scan < internal/rules/embedded/ctxcop.toml byte-identical ×5 (the
    file must never self-match).

Security checklist

  • This PR does not change detection or bypass surface.
    • It DOES change detection (adds coverage). THREATMODEL.md is updated in
      the same PR: new residual-risk bullet ("Bare Redis AUTH residuals" —
      shorter passwords, $0-bulk empty ACL usernames, and non-AUTH RESP
      commands pass; floors exist because of the Splice backstop cascade).
  • No new third-party Go dependency.
  • No new third-party GitHub Action.
  • No literal credential-shape strings in source outside fixture paths
    covered by the skip-list — test literals are split via the existing
    join() helper; tokens built at runtime.
  • Audit-log entries cover any new decision path — none: this only adds
    rules to the existing composed ruleset; existing redact paths log as before.

Reviewer notes

  • Files: internal/rules/embedded/ctxcop.toml (rules + provenance comments),
    internal/rules/redis_auth_rules_test.go (table cases), docs/detection.md
    (eight→twelve table), THREATMODEL.md, CHANGELOG.md.
  • Known residual (documented, both implementations): RESP dumps with an
    empty ($0-bulk) ACL username leak — needs a zero-length bulk alternative
    that would also weaken the header anchor; deferred until a real-world
    shape justifies it.
  • Pre-existing, out of scope for this PR: after a rule fires, later
    occurrences of the same secret value are swept whole-line but carry the
    first-fired rule's ID (stale label) — worth its own issue.
  • Provenance: ported from secrets-scrubber hooks/register.tsx + its test
    suite; leak-shape taxonomy matches Santi's tests one-for-one.

@arr-wf
arr-wf requested a review from santi-webflow October 16, 2026 00:18
@arr-wf

arr-wf commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

@santi-webflow I think you probably have a good idea of the leak shape that this hopes to cover so I'd love a bit of feedback!

@arr-wf
arr-wf force-pushed the feat/redis-auth-rules branch from e527710 to dbc2f21 Compare October 27, 2026 18:41
@arr-wf arr-wf mentioned this pull request Oct 27, 2026
@arr-wf

arr-wf commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Cherry-picked into the v0.1.2 release PR #22 so it ships with this release. Please keep the rule review here. I'll close this PR once #22 merges.

🤖 Generated with Claude Code

Upstream betterleaks has a structural gap for Redis: a bare AUTH
password carries no token prefix and no key=value shape, so the plain
AUTH command line, the node-redis structured error form, redis-cli
-a/--pass invocations, and RESP protocol dumps all passed through
unredacted. Port the four redis-auth rules from
webflow/secrets-scrubber (built around a real Redis key leak into an
AI session), adapted from JS to RE2 + CEL-filter semantics: its
reference-value heuristic becomes a per-rule filter, its tempered-dot
becomes a whitespace/backslash-excluding class, and the RESP rule
additionally requires the *N array header (all of its fixtures carry
it) to keep prose dollar-amounts from matching.

Every rule carries a minimum-length floor: a sub-8-char finding trips
Splices over-redact backstop against unrelated lines carrying the
same short form, which is a worse failure than over-redacting a short
password.

Co-authored-by: Pi <noreply@pi.dev>
Signed-off-by: Andrew Rose <andrew.rose@webflow.com>
@arr-wf
arr-wf force-pushed the feat/redis-auth-rules branch from dbc2f21 to c8bee52 Compare October 27, 2026 18:51
@arr-wf

arr-wf commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Shipped in #22 (v0.1.2). The commit was cherry-picked there and is now on main. Thanks @santi-webflow for the review context.

@arr-wf arr-wf closed this Oct 27, 2026
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