Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/lib/redact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ const SECRET_VALUE_PATTERNS: Array<[RegExp, string]> = [
// scheme (Basic, Digest, …) carries its credential as the payload, so those
// are masked whole by this rule.
//
// The rules run in order, so by the time this one fires the Bearer rule has
// already replaced `Bearer <tok>` with `Bearer [REDACTED]`. Skipping a value
// that is already redacted keeps this rule from eating that result — and from
// eating the trailing diagnostics after it.
[/\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:)(?![^\S\r\n]*(?:Bearer\b|\[REDACTED\]|\r?\n|$))([^\S\r\n]*)[^\r\n]+/gi, `$1$2${REDACTED_SECRET}`],
// The exemption is for the SANITIZED result only — `Bearer [REDACTED]` —
// never a raw `Bearer …` value. Exempting the bare scheme word let a
// credential be smuggled past this rule simply by prefixing it: the Bearer
// rule above only matches an opaque `[A-Za-z0-9._~+/=-]{8,}` token, so
// `x-api-key: Bearer "quoted…"`, `Authorization: Bearer custom:cred…`, and
// a short token all slipped through untouched. Anything the Bearer rule
// could not sanitize is therefore masked whole here.
[/\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:)(?![^\S\r\n]*(?:Bearer[^\S\r\n]+\[REDACTED\]|\[REDACTED\])(?![^\s.,;)\]]))(?![^\S\r\n]*(?:\r?\n|$))([^\S\r\n]*)[^\r\n]+/gi, `$1$2${REDACTED_SECRET}`],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve quoted Bearer diagnostics

When an upstream error quotes the echoed header, e.g. error: "Authorization: Bearer abcdefgh12345678" at /path/file.json, the Bearer rule first produces Bearer [REDACTED], but this new boundary check does not treat the closing " as a delimiter. The colon rule then redacts from Authorization: through the end of the line, yielding error: "Authorization: [REDACTED] and dropping the closing quote plus the trailing path diagnostic that this exception is intended to preserve; include quote delimiters in the boundary set or add coverage for quoted prose.

Useful? React with 👍 / 👎.

[/((?:"(?:api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|client[_-]?secret|refreshToken|accessToken|clientSecret|apiKey)"\s*:\s*"))([^"]+)(")/gi, `$1${REDACTED_SECRET}$3`],
// Raw JSON "token" field values (Copilot token exchange bodies echo the credential here).
[/(("token"\s*:\s*"))([^"]+)(")/gi, `$1${REDACTED_SECRET}$4`],
Expand Down
13 changes: 13 additions & 0 deletions tests/redact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ describe("redactSecretString", () => {
.toBe(`Authorization: Bearer ${REDACTED_SECRET}`);
});

test("the Bearer carve-out cannot be used to smuggle a credential", () => {
// Re-review: exempting the bare scheme word meant anything the Bearer rule
// could not parse (quoted, punctuation-bearing, or under 8 chars) passed
// through untouched — a credential just had to be prefixed with "Bearer".
// Only the SANITIZED result is exempt now.
expect(redactSecretString('x-api-key: Bearer "smuggledcredential123456"'))
.toBe(`x-api-key: ${REDACTED_SECRET}`);
expect(redactSecretString("Authorization: Bearer custom:credential123456"))
.toBe(`Authorization: ${REDACTED_SECRET}`);
expect(redactSecretString("x-api-key: Bearer short"))
.toBe(`x-api-key: ${REDACTED_SECRET}`);
});

test("masks each credential line independently without eating the next", () => {
// End-of-line, not end-of-string: a multi-line error body must not collapse.
expect(redactSecretString("x-api-key: one-secret\nmodel: gpt-5.5\ncookie: two=secret"))
Expand Down
Loading