mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-19 07:36:52 +00:00
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:
@@ -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]',
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user