shieldx/tests/unit/sanitization/CredentialRedactor.test.ts
Rene Fichtmueller 1c4c034483 feat: ShieldX v0.3.0 — UnicodeScanner (L5), DNS Covert Channel rules, ATLAS v5.4 mappings
- Layer 4 EntropyScanner: Shannon entropy, Base32/Base64 detection, CVE-2025-55284
  ping/nslookup exfil, EchoLeak markdown pattern, DNS tunneling (iodine/dnscat)
- Layer 5 UnicodeScanner: ASCII Smuggling (U+E0000 Tags Block), Variant Selectors,
  Zero-Width steganography, CamoLeak image-ordering (CVE-2025-53773), homoglyphs,
  BiDi override, high-entropy URL params
- 30 DNS covert channel rules (dns-001 to dns-030)
- ATLASMapper: 29 techniques (ATLAS v5.4.0 Feb 2026), added AML.T0062 (Agent Tool
  Invocation), AML.TA0015 (C2 tactic), memory poisoning, multi-agent trust,
  CamoLeak, Unicode steganography mappings
- Rule count: 72 → 102
- Build: tsup 316ms, zero TypeScript errors
2026-03-31 16:32:16 +02:00

190 lines
7.3 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest'
import { CredentialRedactor } from '../../../src/sanitization/CredentialRedactor.js'
import { defaultConfig } from '../../../src/core/config.js'
describe('CredentialRedactor', () => {
let redactor: CredentialRedactor
beforeEach(() => {
redactor = new CredentialRedactor(defaultConfig)
})
describe('API key detection', () => {
it('should redact OpenAI sk- prefixed keys', () => {
const input = 'My key is sk-proj-abc123def456ghi789jkl012mno345pqr678stu901vwx'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_')
expect(result.foundSecrets).toBeGreaterThan(0)
})
it('should redact GitHub ghp_ tokens', () => {
const input = 'Token: ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345678901'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_GITHUB_TOKEN]')
expect(result.secretTypes).toContain('github_token')
})
it('should redact GitHub gho_ tokens', () => {
const input = 'OAuth: gho_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345678901'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_GITHUB_TOKEN]')
})
it('should redact AWS AKIA access key IDs', () => {
const input = 'AWS key: AKIAIOSFODNN7EXAMPLE'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_AWS_KEY]')
expect(result.secretTypes).toContain('aws_access_key')
})
it('should redact Google API keys', () => {
// Pattern is AIza + exactly 35 chars of [A-Za-z0-9_-]
const input = 'Google key: AIzaSyAbcdefghijklmnopqrstuvwxyz1234567'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_GOOGLE_KEY]')
expect(result.secretTypes).toContain('google_api_key')
})
it('should redact Stripe keys', () => {
const input = 'Stripe: sk_live_abc123def456ghi789jklm'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_STRIPE_KEY]')
expect(result.secretTypes).toContain('stripe_key')
})
it('should redact Slack tokens', () => {
const input = 'Slack: xoxb-1234567890-abc-defghijklm'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_SLACK_TOKEN]')
expect(result.secretTypes).toContain('slack_token')
})
it('should redact Anthropic API keys', () => {
const input = 'Anthropic: sk-ant-abcdefghijklmnopqrstuvwxyz012345678901234567'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_ANTHROPIC_KEY]')
expect(result.secretTypes).toContain('anthropic_key')
})
it('should redact SendGrid keys', () => {
const input = 'SendGrid: SG.abcdefghijklmnopqrstuv.wxyz0123456789abcdefghij'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_SENDGRID_KEY]')
expect(result.secretTypes).toContain('sendgrid_key')
})
})
describe('JWT token detection', () => {
it('should redact JWT tokens', () => {
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
const input = `Bearer ${jwt}`
const result = redactor.redact(input)
expect(result.foundSecrets).toBeGreaterThan(0)
expect(result.redacted).toContain('[REDACTED_')
})
})
describe('password in URL detection', () => {
it('should redact passwords in URLs', () => {
const input = 'Connect to postgres://admin:mysecretpass@localhost:5432/db'
const result = redactor.redact(input)
expect(result.redacted).not.toContain('mysecretpass')
expect(result.foundSecrets).toBeGreaterThan(0)
})
})
describe('email detection', () => {
it('should redact email addresses', () => {
const input = 'Contact john.doe@example.com for info'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_EMAIL]')
expect(result.secretTypes).toContain('email_address')
})
it('should redact multiple emails', () => {
const input = 'Send to alice@test.org and bob@company.net'
const result = redactor.redact(input)
expect(result.foundSecrets).toBeGreaterThanOrEqual(2)
})
})
describe('database URL detection', () => {
it('should redact PostgreSQL connection strings', () => {
const input = 'DATABASE_URL=postgresql://user:pass@host:5432/dbname'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_')
expect(result.foundSecrets).toBeGreaterThan(0)
})
it('should redact MongoDB connection strings', () => {
const input = 'Use mongodb+srv://user:pass@cluster.example.net/mydb'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_')
})
})
describe('private key detection', () => {
it('should redact RSA private key blocks', () => {
const input = '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA0Z3...\n-----END RSA PRIVATE KEY-----'
const result = redactor.redact(input)
expect(result.redacted).toContain('[REDACTED_PRIVATE_KEY]')
expect(result.secretTypes).toContain('private_key')
})
})
describe('normal text passthrough', () => {
it('should not redact normal English text', () => {
const input = 'The quick brown fox jumps over the lazy dog.'
const result = redactor.redact(input)
expect(result.redacted).toBe(input)
expect(result.foundSecrets).toBe(0)
expect(result.secretTypes).toHaveLength(0)
})
it('should not redact technical discussion without secrets', () => {
const input = 'We need to implement a REST API using Express.js with TypeScript strict mode.'
const result = redactor.redact(input)
expect(result.redacted).toBe(input)
expect(result.foundSecrets).toBe(0)
})
it('should not redact short alphanumeric strings', () => {
const input = 'The key concept is "abstraction" and the value is 42.'
const result = redactor.redact(input)
expect(result.foundSecrets).toBe(0)
})
})
describe('redaction markers', () => {
it('should use [REDACTED_*] marker format', () => {
const input = 'Key: AKIAIOSFODNN7EXAMPLE and email user@test.com'
const result = redactor.redact(input)
const markers = result.redacted.match(/\[REDACTED_\w+\]/g) || []
expect(markers.length).toBeGreaterThan(0)
for (const marker of markers) {
expect(marker).toMatch(/^\[REDACTED_\w+\]$/)
}
})
})
describe('edge cases', () => {
it('should handle empty string', () => {
const result = redactor.redact('')
expect(result.redacted).toBe('')
expect(result.foundSecrets).toBe(0)
})
it('should handle multiple different secret types in one input', () => {
const input = 'AWS: AKIAIOSFODNN7EXAMPLE, Email: test@example.com, Stripe: sk_live_abcdefghijklmnopqrstu'
const result = redactor.redact(input)
expect(result.secretTypes.length).toBeGreaterThanOrEqual(2)
expect(result.foundSecrets).toBeGreaterThanOrEqual(2)
})
it('should return frozen result', () => {
const result = redactor.redact('test')
expect(Object.isFrozen(result)).toBe(true)
})
})
})