Skip to main content
← Back to list
01Issue
BugOpenSwamp ClubPublicTeam
AssigneesNone

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

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 \b at the very start of swamp (a real boundary: string-start to word-char). It matches swamp (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 \b immediately 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 catch swamp_....
  • And/or fix LONG_BASE64_RE/LONG_HEX_RE to 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.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

8/18/2026, 1:34:25 AM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.