/** * Tests for src/lib/secret-scanner.ts — scanForSecrets and redactSecrets */ import { describe, it, expect } from 'vitest' import { scanForSecrets, redactSecrets } from '@/lib/secret-scanner' describe('scanForSecrets', () => { it('detects AWS access key IDs', () => { const hits = scanForSecrets('My key is AKIAIOSFODNN7EXAMPLE') expect(hits.length).toBeGreaterThanOrEqual(1) expect(hits.some(h => h.type === 'aws_access_key')).toBe(true) expect(hits[0].severity).toBe('critical') }) it('detects AWS secret access keys', () => { const hits = scanForSecrets('aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYaa') expect(hits.some(h => h.type === 'aws_secret_key')).toBe(true) }) it('detects GitHub personal access tokens (ghp_)', () => { const token = 'ghp_' + 'A'.repeat(36) const hits = scanForSecrets(`token: ${token}`) expect(hits.some(h => h.type === 'github_token')).toBe(true) expect(hits[0].severity).toBe('critical') }) it('detects GitHub OAuth tokens (gho_)', () => { const token = 'gho_' + 'B'.repeat(36) const hits = scanForSecrets(token) expect(hits.some(h => h.type === 'github_oauth_token')).toBe(true) }) it('detects GitHub fine-grained PATs (github_pat_)', () => { const token = 'github_pat_' + 'C'.repeat(22) const hits = scanForSecrets(token) expect(hits.some(h => h.type === 'github_pat')).toBe(true) }) it('detects Stripe live keys', () => { const key = 'sk_live_' + 'D'.repeat(24) const hits = scanForSecrets(key) expect(hits.some(h => h.type === 'stripe_secret_key')).toBe(true) expect(hits[0].severity).toBe('critical') }) it('detects Stripe test keys with warning severity', () => { const key = 'sk_test_' + 'E'.repeat(24) const hits = scanForSecrets(key) expect(hits.some(h => h.type === 'stripe_test_key')).toBe(true) expect(hits.find(h => h.type === 'stripe_test_key')!.severity).toBe('warning') }) it('detects JWTs', () => { const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' const hits = scanForSecrets(jwt) expect(hits.some(h => h.type === 'jwt')).toBe(true) }) it('detects private keys (PEM)', () => { const hits = scanForSecrets('-----BEGIN RSA PRIVATE KEY-----\nMIIEow...') expect(hits.some(h => h.type === 'private_key')).toBe(true) expect(hits[0].severity).toBe('critical') }) it('detects database connection strings', () => { const hits = scanForSecrets('postgres://user:pass@host:5432/mydb?sslmode=require') expect(hits.some(h => h.type === 'db_connection_string')).toBe(true) expect(hits[0].severity).toBe('critical') }) it('detects mongodb+srv connection strings', () => { const hits = scanForSecrets('mongodb+srv://admin:pw123@cluster0.mongodb.net/db') expect(hits.some(h => h.type === 'db_connection_string')).toBe(true) }) it('returns no false positives on normal text', () => { const hits = scanForSecrets('Hello, this is a normal message about deploying our application to production.') expect(hits).toHaveLength(0) }) it('returns no false positives on code snippets', () => { const hits = scanForSecrets('const x = 42; function hello() { return "world"; }') expect(hits).toHaveLength(0) }) it('returns redactedPreview for each match', () => { const hits = scanForSecrets('AKIAIOSFODNN7EXAMPLE') expect(hits[0].redactedPreview).toContain('***') expect(hits[0].redactedPreview).not.toBe('AKIAIOSFODNN7EXAMPLE') }) it('includes position in match', () => { const text = 'prefix AKIAIOSFODNN7EXAMPLE suffix' const hits = scanForSecrets(text) expect(hits[0].position).toBe(7) }) }) describe('redactSecrets', () => { it('masks AWS keys in text', () => { const text = 'Key is AKIAIOSFODNN7EXAMPLE here' const result = redactSecrets(text) expect(result).toContain('***REDACTED***') expect(result).not.toContain('AKIAIOSFODNN7EXAMPLE') }) it('masks GitHub tokens', () => { const token = 'ghp_' + 'A'.repeat(36) const result = redactSecrets(`Use ${token} for auth`) expect(result).toContain('***REDACTED***') expect(result).not.toContain(token) }) it('preserves text without credentials', () => { const text = 'Just a normal message with nothing sensitive.' expect(redactSecrets(text)).toBe(text) }) it('masks multiple credentials in one string', () => { const token = 'ghp_' + 'X'.repeat(36) const text = `AWS: AKIAIOSFODNN7EXAMPLE GitHub: ${token}` const result = redactSecrets(text) expect(result).not.toContain('AKIAIOSFODNN7EXAMPLE') expect(result).not.toContain(token) }) }) describe('scanForSecrets - new patterns', () => { it('detects Slack webhook URLs', () => { const url = 'https://hooks.slack.com/services/T' + '0'.repeat(8) + '/B' + '0'.repeat(8) + '/' + 'X'.repeat(24) const hits = scanForSecrets(url) expect(hits.some(h => h.type === 'slack_webhook')).toBe(true) expect(hits.find(h => h.type === 'slack_webhook')!.severity).toBe('critical') }) it('detects Discord webhook URLs', () => { const url = 'https://discord.com/api/webhooks/12345678901234567/' + 'a'.repeat(68) const hits = scanForSecrets(url) expect(hits.some(h => h.type === 'discord_webhook')).toBe(true) }) it('detects Anthropic API keys', () => { const key = 'sk-ant-api' + 'A'.repeat(30) const hits = scanForSecrets(key) expect(hits.some(h => h.type === 'anthropic_api_key')).toBe(true) expect(hits[0].severity).toBe('critical') }) it('detects SendGrid API keys', () => { const key = 'SG.' + 'A'.repeat(22) + '.' + 'B'.repeat(43) const hits = scanForSecrets(key) expect(hits.some(h => h.type === 'sendgrid_api_key')).toBe(true) }) it('detects Mailgun API keys', () => { const key = 'key-' + 'a'.repeat(32) const hits = scanForSecrets(key) expect(hits.some(h => h.type === 'mailgun_api_key')).toBe(true) }) it('detects Azure storage connection strings', () => { const conn = 'DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=' + 'A'.repeat(30) const hits = scanForSecrets(conn) expect(hits.some(h => h.type === 'azure_storage')).toBe(true) }) it('detects Twilio API keys', () => { const key = 'SK' + 'ab12cd34ef56ab12cd34ef56ab12cd34' const hits = scanForSecrets(key) expect(hits.some(h => h.type === 'twilio_api_key')).toBe(true) }) })