feat(core): Expand secret redaction patterns (JWT, PEM keys, vendor tokens, URL creds) (#32440)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaakko Husso
2026-06-17 13:38:06 +02:00
committed by GitHub
parent 734af045c5
commit f815269228
2 changed files with 35 additions and 0 deletions
@@ -43,6 +43,28 @@ describe('scrubSecretsInText', () => {
expect(scrubSecretsInText('AKIAIOSFODNN7EXAMPLE is the key')).toBe('[REDACTED] is the key');
});
it('redacts a PEM private-key block', () => {
const pem = `-----BEGIN PRIVATE KEY-----\n${'FAKEKEYMATERIAL'}\n-----END PRIVATE KEY-----`;
expect(scrubSecretsInText(`key:\n${pem}\ndone`)).toBe('key:\n[REDACTED]\ndone');
});
it('redacts a JWT', () => {
const jwt = `${join('eyJ', 'hbGciOiJIUzI1NiJ9')}.${join('eyJ', 'zdWIiOiIxMjMifQ')}.${'c2lnbmF0dXJl'}`;
expect(scrubSecretsInText(`token ${jwt} end`)).toBe('token [REDACTED] end');
});
it('redacts Stripe, Google, and GitHub fine-grained tokens', () => {
expect(scrubSecretsInText(join('sk', '_live_abcdefghijklmnop1234'))).toBe('[REDACTED]');
expect(scrubSecretsInText(join('AIza', 'B'.repeat(35)))).toBe('[REDACTED]');
expect(scrubSecretsInText(join('github_pat_', 'A'.repeat(30)))).toBe('[REDACTED]');
});
it('redacts credentials embedded in a URL, keeping scheme and host', () => {
const out = scrubSecretsInText('fetch https://alice:s3cretPass@db.example.com/items');
expect(out).not.toContain('s3cretPass');
expect(out).toBe('fetch https://[REDACTED]@db.example.com/items');
});
it('redacts generic key=value assignments regardless of separator', () => {
expect(scrubSecretsInText('password=hunter2 and api_key:abc')).toBe(
'[REDACTED] and [REDACTED]',
+13
View File
@@ -16,16 +16,29 @@ export const SECRET_KEYS =
'password|passwd|secret|credentials?|api[_-]?key|authorization|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?token|auth[_-]?token';
export const SECRET_VALUE_PATTERNS: readonly RegExp[] = [
// PEM private-key blocks (RSA/EC/DSA/OpenSSH/PGP). Whole block, multiline.
/-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g,
// JWTs: `eyJ<header>.eyJ<payload>.<signature>` (both leading segments are
// base64url of a `{"` object, which makes this highly distinctive).
/\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
// Authorization-header substrings: `Bearer <token>`, `Basic <token>`, `Token <token>`
/\b(?:Bearer|Basic|Token)\s+[A-Za-z0-9._~+/=-]{12,}/gi,
// OpenAI / Anthropic API keys
/\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,
// Stripe secret/restricted/publishable keys (`sk_live_…`, `rk_test_…`, …)
/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,
// Google API keys
/\bAIza[0-9A-Za-z_-]{35}\b/g,
// Slack tokens (xoxb, xoxp, xoxa, xoxr, xoxs, xoxo)
/\bxox[abprso]-[A-Za-z0-9-]{10,}/g,
// GitHub tokens (ghp, ghs, gho, ghr, ghu)
/\bgh[psoru]_[A-Za-z0-9]{20,}/g,
// GitHub fine-grained personal access tokens
/\bgithub_pat_[A-Za-z0-9_]{22,}/g,
// AWS access key id
/\bAKIA[0-9A-Z]{16}\b/g,
// Credentials embedded in a URL: `scheme://user:password@` — redact the userinfo.
/(?<=:\/\/)[^\s:/@]+:[^\s:/@]+(?=@)/g,
// JSON-shaped `"key": "value"` — matches the quoted field as a whole.
// Run before the loose pattern so nested objects like
// `{"credentials": {"apiKey": "..."}}` don't have the outer key consume