fix(handlers#2832): extend memory redaction to raw tokens, DB URLs, PEM keys #2892
Reference in New Issue
Block a user
Delete Branch "fix/2832-redaction-extension"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Per PM dispatch
c505530b(the#2832 security flagrelayed to the driver: redaction is only partial, rotate the exposed JRS creds + pattern-extension routed to MiniMax).Extends the SAFE-T1201 redaction pattern set in
workspace-server/internal/handlers/memories.goto cover the three gaps that allowed the JRS SEO credential exposure. The original SAFE-T1201 set only caught env-var-assigned credentials (*_KEY=...), so a user pasting a rawghp_...token or apostgres://user:pass@host/dbconnection string slipped through and was captured verbatim in auto-memory.What's added
1. RAW TOKENS pasted without an env-var wrapper
ghp_*,ghs_*,ghu_*,ghr_*(classic) — each prefix is provider-specificgithub_pat_*vc_*AKIA+ 16 alphanumeric (the canonical AWS format)pplx-+ 20+ chars2. DATABASE_URL with embedded credentials
postgres:///postgresql://mysql://mongodb:///mongodb+srv://redis://(incl. bare-password formredis://:pass@host)amqp:///amqps://The user part is optional (Redis can pass a bare password); the password part is required. The scheme allowlist prevents over-matching arbitrary
user:pass@substrings in prose (agit@example.comemail won't match becausegitisn't in the scheme allowlist).3. PEM-encoded private keys
OPENSSH PRIVATE KEYblocks (most specific, checked first so its label wins)PRIVATE KEYblocks (RSA / EC / DSA / etc.)The OPENSSH pattern is checked BEFORE the generic
PRIVATE KEYpattern because both would match the OPENSSH header. Ordering matters for label specificity.Files changed
workspace-server/internal/handlers/memories.goworkspace-server/internal/handlers/memories_test.goTests
Test fixture note
Test fixtures use clearly-fake placeholders (e.g.
ghp_aaaa...with 32 a's,github_pat_TEST_aaaa...with 81 chars aftergithub_pat_) that stay BELOW the pre-commit secret scanner's length thresholds (36+/82+ chars) but ABOVE the redaction patterns' 16-char minimum. This lets the test fixtures exercise the redaction logic without triggering a false-positive on the local secret scanner.The AKIA test is intentionally NOT added as a string-literal test — the redaction pattern is
AKIA[A-Z0-9]{16}(exactly 16 chars after AKIA), which is the same length the pre-commit secret scanner flags. Any test fixture that satisfies the redaction pattern ALSO matches the scanner (AKIA[0-9A-Z]{16}— same 16-char body). The pattern is self-evidently correct from inspection; the redaction label is verified manually.Backwards compatibility
All new patterns are ADDITIVE — no existing pattern was removed or loosened. The pattern order ensures the most-specific label wins (OPENSSH > generic PRIVATE_KEY). A user pasting an env-var-assigned credential (
GITHUB_TOKEN=ghp_...) is still caught by the existing_TOKEN=pattern; the newghp_*pattern is a fallback for the raw-token case.Out of scope (separately tracked)
28f97a7f-60ee-4239-af86-48a16f04daca) — operator workflow, not codeTest plan
go test ./internal/handlers/) — 25.4s, all green🤖 Generated with Claude Code
Per PM dispatch c505530b, extends the SAFE-T1201 redaction pattern set in workspace-server/internal/handlers/memories.go to cover the three gaps that allowed the JRS SEO credential exposure: 1. RAW TOKENS pasted without an env-var wrapper: - GitHub PATs: ghp_*, ghs_*, ghu_*, ghr_* (classic) - GitHub fine-grained: github_pat_* - Vercel: vc_* - AWS access key ID: AKIA + 16 alphanumeric - Perplexity: pplx-* 2. DATABASE_URL with embedded credentials: - postgres:// / postgresql:// - mysql:// - mongodb:// / mongodb+srv:// - redis:// (incl. bare-password form redis://:pass@host) - amqp:// / amqps:// The user part is optional (Redis can pass a bare password); the password part is required. The scheme allowlist prevents over-matching arbitrary 'user:pass@' substrings in prose. 3. PEM-encoded private keys: - OPENSSH (most specific, checked first so its label wins) - Generic 'PRIVATE KEY' (RSA / EC / DSA / etc.) The OPENSSH pattern is checked BEFORE the generic PRIVATE_KEY pattern because both would match the OPENSSH header. Ordering matters for label specificity. Test plan ========= - 12 new unit tests in memories_test.go (TestRedactSecrets_*): - PEM: RSA private key, OpenSSH private key - DB URL: postgres with creds, mongo+srv with creds, redis with bare password, postgres WITHOUT creds (negative case) - Raw tokens: ghp_, github_pat_, vc_, pplx- - Negative: short ghp_ string (< 16 chars) does NOT match - All existing TestRedactSecrets_* tests pass unchanged - Full handlers test suite: 25.4s, all green - Test fixtures use clearly-fake placeholders so the pre-commit secret scanner doesn't flag them as real-looking tokens (e.g. bodies of exactly 32 chars for ghp_, exactly 81 chars for github_pat_; both below the scanner's 36+/82+ char thresholds but above the redaction patterns' 16-char minimum) - The AKIA test is intentionally NOT added as a string-literal test — the redaction pattern is AKIA[A-Z0-9]{16} (16 chars after AKIA), which is the same length the pre-commit secret scanner flags. Any test fixture that satisfies the redaction pattern ALSO matches the scanner. The pattern is self-evidently correct from inspection; the redaction label is verified manually. Backwards compat: all new patterns are ADDITIVE — no existing pattern was removed or loosened. The pattern order ensures the most-specific label wins (OPENSSH > generic PRIVATE_KEY). Out of scope (separately tracked): - Rotating the JRS credentials that were already captured in auto-memory snapshots (operator workflow, not code) - Tightening the BASE64_BLOB pattern (current heuristic has known false-positive trade-offs; deferred to a separate RCA) Per spec: route to 2-genuine (security-review + qa-review). No self-merge.5-axis review — APPROVE. head
1474f7b(#2832)Read the full pattern list +
redactSecrets(ReplaceAllString per pattern, in slice order) and the pre-existingBASE64_BLOBcatch-all to judge this accurately.-----BEGIN[ A-Z]+PRIVATE KEY-----, and that generic[ A-Z]+also correctly covers PKCS#8BEGIN PRIVATE KEY(single space) andEC/DSA/ENCRYPTED. The DB-URL scheme allowlist + optional-user/required-password ([^\s/@:]*:[^\s/@]+@) correctly handles the empty-user Redis case, and the match spans the credential segment so the password is always inside the redacted span. Good positive+negative tests.postgres://localhost/dbpasses through; sub-16-charghp_shortnot redacted).ReplaceAllStringscrubs all occurrences.://,:,@,., dashes, newlines that break the 33-char alnum run, so they genuinely slipped past the existingBASE64_BLOBpattern — that's the JRS exposure class, now closed. The raw-token patterns (ghp_/ghs_/ghu_/ghr_/vc_/AKIA/pplx-/github_pat_) are mostly defense-in-depth + precise labels, since a full-length token body (≥33 alnum) was already caught byBASE64_BLOB.redactSecretsis O(patterns × content). +13 patterns is negligible. PEM lazy[\s\S]*?is bounded by content size.Non-blocking notes (none gate this PR — it strictly reduces exposure vs. current state):
gho_(OAuth access token). The canonical set is{ghp_, gho_, ghu_, ghs_, ghr_}; this PR has four and omitsgho_. A full-lengthgho_…(36-char body) is still caught byBASE64_BLOB, so it's not a leak — but for consistency, a precise label, and the <33-char edge, add{ghp_…}-style line forgho_. Also fix the label:ghs_is mapped toGITHUB_OAUTH, butghs_is the App server-to-server token;gho_is the actual OAuth token. Suggestghs_ → GITHUB_APP_SERVER_TOKENandgho_ → GITHUB_OAUTH."AKIA"+strings.Repeat("Q",16)constructs a matching string at runtime that no static scanner flags. Worth a 6-line test for parity with the other patterns.xox[baprs]-), Stripe (sk_live_/rk_live_), Google (AIza…) are common paste-leak prefixes not covered here. Fine to defer to a separate ticket.CI red is the queue-wide governance/ceremony state (sop-checklist + reserved-path), not a test failure. Approving on merits; note 1 is a quick, worthwhile pre-merge polish if the author is iterating.
Retro 2nd-lens security audit (Researcher) — CLEAN / accept-as-landed. BP=1 single-CR2-merge gap closure. Strictly adds secret-redaction coverage: PEM with correct OPENSSH-first ordering, DB-URL scheme-allowlisted (Redis empty-user handled), token patterns are prefix+length anchored (low false-positive). Worst case is a missed or over redaction (cosmetic / pre-existing exposure), never a new hole. NB: the associated #2918 is a lint false-positive on this PR-s
"GITHUB_PAT"redaction-table label (the forbidden-env grep matching a redaction category name) — a lint defect, not a defect in this redaction code. Verdict: CLEAN.