Relationships
#1702 Redaction regex boundary bug: label_base64 secrets with an unrecognized label pass through unredacted (e.g. swamp_ API keys)
Opened by webframp · 8/18/2026· Shipped 9/23/2026
Description
src/domain/issues/content_redactor.ts's automatic secret redaction (wired
into swamp issue bug/swamp issue submit by default, opt-out via
--no-redact) misses a whole class of API-key-shaped tokens: any value of
the form <label>_<base64-ish-payload> where <label> is not one of the
six literal prefixes recognized by PREFIXED_KEY_RE
(sk|pk|rk|ak|key|token|secret).
The intended fallback for exactly this case is LONG_BASE64_RE:
const LONG_BASE64_RE = /\b[A-Za-z0-9+/]{32,}={0,2}(?=\s|$)/g;This requires a \b word boundary immediately before the 32+ char payload
run. But \b is a transition between a word character (\w = letters,
digits, underscore) and a non-word character (or string edge). In a token
like swamp_FHPzTExTw..., the character immediately before the payload is
_, and _ is itself a word character — same class as the letters on
either side of it. There is no word/non-word transition at that position,
so \b never fires there, and the regex has no legal place to start
matching the payload run.
What actually happens on a string like swamp_FHPzTExTw...KJp:
- The scan tries to match
[A-Za-z0-9+/]{32,}starting from a\bat the very start ofswamp(a real boundary: string-start to word-char). It matchesswamp(5 chars, all in-class) and then hits_, which is not in[A-Za-z0-9+/], so the run terminates at length 5 — under the{32,}minimum. No match. - No other position in the string satisfies
\bimmediately before a 32+-char in-class run, because the only other word/non-word transition is at the very end of the token (after the last payload character) — that's a valid boundary for the end of a match, not somewhere a new match can start from that skips the prefix. - Net result: nothing matches, the whole token — prefix and payload — passes through completely unredacted.
The same gap applies to LONG_HEX_RE for any <label>_<40+ hex chars>
shape, for the identical reason (\b can't anchor immediately after an
underscore-joined prefix).
This is exactly the shape of swamp-club's own API key format
(swamp_<random>), so the redactor's blind spot covers its own platform's
credentials — the most likely secret a swamp user would accidentally paste
into a bug report.
Steps to Reproduce
import { redactIssueContent } from "./src/domain/issues/content_redactor.ts";
const text = "leaked key: swamp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789AB";
console.log(redactIssueContent(text).text);
// Expected: "leaked key: [REDACTED-SECRET-1]" (or similar)
// Actual: "leaked key: swamp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789AB"
// (unchanged -- no redaction applied)Or via the CLI path that actually triggers this in practice:
swamp issue bug --title "test" \
--body "key: swamp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789AB"
# Redaction notice reports 0 redactions; the token reaches the submitted
# issue body verbatim.(Use a fabricated token when testing -- do not reuse a real key, since this is precisely the bug that lets it leak.)
Impact
Discovered after a real swamp_... API key (not shown above; a synthetic
equivalent is used in the repro) survived swamp issue bug's default
redaction pass and was posted to a Lab issue body verbatim, requiring a
follow-up edit to scrub it. The redactor gave a false sense of safety --
it's the mechanism most likely to be relied on right when a user is
reporting a leaked-credential-adjacent bug.
Suggested Fix
PREFIXED_KEY_RE and the boundary logic in LONG_BASE64_RE/LONG_HEX_RE
both need to handle underscore/hyphen-joined prefixes generically instead
of (a) enumerating specific label strings or (b) relying on \b to anchor
past a joining character that is itself a word character. Options:
- Broaden
PREFIXED_KEY_RE's prefix alternation to a short generic alphanumeric label pattern followed by[-_]and a long payload, rather than a fixed enum -- this alone would catchswamp_.... - And/or fix
LONG_BASE64_RE/LONG_HEX_REto anchor on a lookbehind for "not preceded by another payload-class character" rather than\b, so a run beginning right after_or-is still eligible to start a match.
Add a test case to content_redactor_test.ts for <label>_<payload>
shapes where <label> is not in the current enum (e.g. swamp_,
stripe_, any future first-party or third-party prefix), asserting the
payload is redacted.
Shipped
Click a lifecycle step above to view its details.
skunk-ape commented 9/23/2026, 4:48:05 PM
Thanks @webframp for reporting this! We shipped: Repair the issue redactor in both directions: gate the enumerated tier so word-structured identifiers survive, and add a two-tier labelled-key matcher plus the boundary fix so label-prefixed secrets stop escaping.. The fix has been merged and a release is on its way. We appreciate your contribution to swamp.
Sign in to post a ripple.