File size: 6,566 Bytes
b6ecafa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
 * 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)
  })
})