ship-safe 6.1.1 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +735 -641
- package/cli/agents/api-fuzzer.js +345 -345
- package/cli/agents/auth-bypass-agent.js +348 -348
- package/cli/agents/base-agent.js +272 -272
- package/cli/agents/cicd-scanner.js +236 -201
- package/cli/agents/config-auditor.js +521 -521
- package/cli/agents/deep-analyzer.js +6 -2
- package/cli/agents/git-history-scanner.js +170 -170
- package/cli/agents/html-reporter.js +568 -568
- package/cli/agents/index.js +84 -84
- package/cli/agents/injection-tester.js +500 -500
- package/cli/agents/llm-redteam.js +251 -251
- package/cli/agents/mobile-scanner.js +231 -231
- package/cli/agents/orchestrator.js +322 -322
- package/cli/agents/pii-compliance-agent.js +301 -301
- package/cli/agents/scoring-engine.js +248 -248
- package/cli/agents/supabase-rls-agent.js +154 -154
- package/cli/agents/supply-chain-agent.js +650 -507
- package/cli/bin/ship-safe.js +452 -426
- package/cli/commands/agent.js +608 -608
- package/cli/commands/audit.js +986 -980
- package/cli/commands/baseline.js +193 -193
- package/cli/commands/ci.js +342 -342
- package/cli/commands/deps.js +516 -516
- package/cli/commands/doctor.js +159 -159
- package/cli/commands/fix.js +218 -218
- package/cli/commands/hooks.js +268 -0
- package/cli/commands/init.js +407 -407
- package/cli/commands/mcp.js +304 -304
- package/cli/commands/red-team.js +7 -1
- package/cli/commands/remediate.js +798 -798
- package/cli/commands/rotate.js +571 -571
- package/cli/commands/scan.js +569 -569
- package/cli/commands/score.js +449 -449
- package/cli/commands/watch.js +281 -281
- package/cli/hooks/patterns.js +313 -0
- package/cli/hooks/post-tool-use.js +140 -0
- package/cli/hooks/pre-tool-use.js +186 -0
- package/cli/index.js +73 -69
- package/cli/providers/llm-provider.js +397 -287
- package/cli/utils/autofix-rules.js +74 -74
- package/cli/utils/cache-manager.js +311 -311
- package/cli/utils/output.js +230 -230
- package/cli/utils/patterns.js +1121 -1121
- package/cli/utils/pdf-generator.js +94 -94
- package/package.json +69 -69
- package/configs/supabase/rls-templates.sql +0 -242
package/cli/utils/patterns.js
CHANGED
|
@@ -1,1121 +1,1121 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Secret Detection Patterns
|
|
6
|
-
* =========================
|
|
7
|
-
*
|
|
8
|
-
* These regex patterns detect common secret formats.
|
|
9
|
-
* Each pattern includes:
|
|
10
|
-
* - name: Human-readable identifier
|
|
11
|
-
* - pattern: Regular expression
|
|
12
|
-
* - severity: 'critical' | 'high' | 'medium'
|
|
13
|
-
* - description: Why this matters
|
|
14
|
-
*
|
|
15
|
-
* MAINTENANCE NOTES:
|
|
16
|
-
* - Patterns should have low false-positive rates
|
|
17
|
-
* - Only include patterns with SPECIFIC PREFIXES to avoid noise
|
|
18
|
-
* - Test new patterns against real codebases before adding
|
|
19
|
-
* - Order doesn't matter (all patterns are checked)
|
|
20
|
-
*
|
|
21
|
-
* v1.2.0 - Added 40+ new patterns for 2025-2026 services
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
export const SECRET_PATTERNS = [
|
|
25
|
-
// =========================================================================
|
|
26
|
-
// CRITICAL: These are almost always real secrets
|
|
27
|
-
// =========================================================================
|
|
28
|
-
{
|
|
29
|
-
name: 'AWS Access Key ID',
|
|
30
|
-
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
31
|
-
severity: 'critical',
|
|
32
|
-
description: 'AWS Access Keys can access your entire AWS account. Rotate immediately if exposed.'
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: 'AWS Secret Access Key',
|
|
36
|
-
pattern: /(?:aws_secret_access_key|aws_secret_key)[\s]*[=:][\s]*["']?([A-Za-z0-9/+=]{40})["']?/gi,
|
|
37
|
-
severity: 'critical',
|
|
38
|
-
description: 'AWS Secret Keys paired with Access Keys grant full AWS access.'
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: 'GitHub Personal Access Token',
|
|
42
|
-
pattern: /ghp_[a-zA-Z0-9]{36}/g,
|
|
43
|
-
severity: 'critical',
|
|
44
|
-
description: 'GitHub PATs can access repositories, create commits, and manage settings.'
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
name: 'GitHub OAuth Token',
|
|
48
|
-
pattern: /gho_[a-zA-Z0-9]{36}/g,
|
|
49
|
-
severity: 'critical',
|
|
50
|
-
description: 'GitHub OAuth tokens grant authorized application access.'
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: 'GitHub App Token',
|
|
54
|
-
pattern: /ghu_[a-zA-Z0-9]{36}|ghs_[a-zA-Z0-9]{36}/g,
|
|
55
|
-
severity: 'critical',
|
|
56
|
-
description: 'GitHub App tokens have installation-level access to repositories.'
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'GitHub Fine-Grained PAT',
|
|
60
|
-
pattern: /github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/g,
|
|
61
|
-
severity: 'critical',
|
|
62
|
-
description: 'GitHub fine-grained PATs can access repositories with scoped permissions.'
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
name: 'Stripe Live Secret Key',
|
|
66
|
-
pattern: /sk_live_[a-zA-Z0-9]{24,}/g,
|
|
67
|
-
severity: 'critical',
|
|
68
|
-
description: 'Stripe live keys can process real payments and access customer data.'
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
name: 'Private Key Block',
|
|
72
|
-
pattern: /-----BEGIN\s+(RSA\s+|EC\s+|DSA\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/g,
|
|
73
|
-
severity: 'critical',
|
|
74
|
-
description: 'Private keys enable impersonation and decryption. Never commit these.'
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: 'PlanetScale Password',
|
|
78
|
-
pattern: /pscale_pw_[a-zA-Z0-9_-]{32,}/g,
|
|
79
|
-
severity: 'critical',
|
|
80
|
-
description: 'PlanetScale passwords grant database access. Keep in environment variables.'
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
name: 'PlanetScale OAuth Token',
|
|
84
|
-
pattern: /pscale_oauth_[a-zA-Z0-9_-]{32,}/g,
|
|
85
|
-
severity: 'critical',
|
|
86
|
-
description: 'PlanetScale OAuth tokens can manage your database branches and schema.'
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
name: 'Clerk Secret Key',
|
|
90
|
-
pattern: /sk_live_[a-zA-Z0-9]{27,}/g,
|
|
91
|
-
severity: 'critical',
|
|
92
|
-
description: 'Clerk secret keys grant full access to your auth system. Never expose in frontend.'
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: 'Doppler Service Token',
|
|
96
|
-
pattern: /dp\.st\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{32,}/g,
|
|
97
|
-
severity: 'critical',
|
|
98
|
-
description: 'Doppler service tokens grant access to your secrets. Ironic if leaked!'
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: 'HashiCorp Vault Token',
|
|
102
|
-
pattern: /hvs\.[a-zA-Z0-9_-]{24,}/g,
|
|
103
|
-
severity: 'critical',
|
|
104
|
-
description: 'HashiCorp Vault tokens grant access to your secrets.'
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
name: 'Neon Database Connection String',
|
|
108
|
-
pattern: /postgres(ql)?:\/\/[^:]+:[^@]+@[^.]+\.neon\.tech/g,
|
|
109
|
-
severity: 'critical',
|
|
110
|
-
description: 'Neon Postgres connection strings contain database credentials.'
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: 'MongoDB Atlas Connection String',
|
|
114
|
-
pattern: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@[^.]+\.mongodb\.net/g,
|
|
115
|
-
severity: 'critical',
|
|
116
|
-
description: 'MongoDB Atlas connection strings contain database credentials.'
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
// =========================================================================
|
|
120
|
-
// HIGH: AI/ML Provider Keys (2025-2026)
|
|
121
|
-
// =========================================================================
|
|
122
|
-
{
|
|
123
|
-
name: 'OpenAI API Key',
|
|
124
|
-
pattern: /sk-[a-zA-Z0-9]{20,}/g,
|
|
125
|
-
severity: 'high',
|
|
126
|
-
description: 'OpenAI keys can rack up API charges and access your usage history.'
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: 'OpenAI Project Key',
|
|
130
|
-
pattern: /sk-proj-[a-zA-Z0-9_-]{48,}/g,
|
|
131
|
-
severity: 'high',
|
|
132
|
-
description: 'OpenAI project keys grant access to specific project resources.'
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: 'Anthropic API Key',
|
|
136
|
-
pattern: /sk-ant-[a-zA-Z0-9_-]{32,}/g,
|
|
137
|
-
severity: 'high',
|
|
138
|
-
description: 'Anthropic API keys grant access to Claude and your usage quota.'
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: 'Google AI (Gemini) API Key',
|
|
142
|
-
pattern: /AIzaSy[a-zA-Z0-9_-]{33}/g,
|
|
143
|
-
severity: 'high',
|
|
144
|
-
description: 'Google AI API keys grant access to Gemini and other Google AI services.'
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
name: 'Replicate API Token',
|
|
148
|
-
pattern: /r8_[a-zA-Z0-9]{37}/g,
|
|
149
|
-
severity: 'high',
|
|
150
|
-
description: 'Replicate tokens can run AI models and incur charges on your account.'
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
name: 'Hugging Face Token',
|
|
154
|
-
pattern: /hf_[a-zA-Z0-9]{34}/g,
|
|
155
|
-
severity: 'high',
|
|
156
|
-
description: 'Hugging Face tokens grant access to models, datasets, and Inference API.'
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
name: 'Perplexity API Key',
|
|
160
|
-
pattern: /pplx-[a-f0-9]{48}/g,
|
|
161
|
-
severity: 'high',
|
|
162
|
-
description: 'Perplexity API keys can access their search-augmented AI models.'
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
name: 'Groq API Key',
|
|
166
|
-
pattern: /gsk_[a-zA-Z0-9]{52}/g,
|
|
167
|
-
severity: 'high',
|
|
168
|
-
description: 'Groq API keys provide access to fast LLM inference.'
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
name: 'Cohere API Key',
|
|
172
|
-
pattern: /(?:cohere|COHERE)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{40})["']?/gi,
|
|
173
|
-
severity: 'high',
|
|
174
|
-
description: 'Cohere API keys grant access to their NLP models.'
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
name: 'Mistral API Key',
|
|
178
|
-
pattern: /(?:mistral|MISTRAL)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{32})["']?/gi,
|
|
179
|
-
severity: 'high',
|
|
180
|
-
description: 'Mistral AI API keys can access their language models.'
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
name: 'Together AI API Key',
|
|
184
|
-
pattern: /(?:together|TOGETHER)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{64})["']?/gi,
|
|
185
|
-
severity: 'high',
|
|
186
|
-
description: 'Together AI keys grant access to open-source model hosting.'
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
// =========================================================================
|
|
190
|
-
// HIGH: Communication & Messaging
|
|
191
|
-
// =========================================================================
|
|
192
|
-
{
|
|
193
|
-
name: 'Slack Token',
|
|
194
|
-
pattern: /xox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*/g,
|
|
195
|
-
severity: 'high',
|
|
196
|
-
description: 'Slack tokens can read messages, post content, and access workspace data.'
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
name: 'Slack Webhook',
|
|
200
|
-
pattern: /https:\/\/hooks\.slack\.com\/services\/T[a-zA-Z0-9_]+\/B[a-zA-Z0-9_]+\/[a-zA-Z0-9_]+/g,
|
|
201
|
-
severity: 'high',
|
|
202
|
-
description: 'Slack webhooks allow posting messages to channels.'
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
name: 'Discord Webhook',
|
|
206
|
-
pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[a-zA-Z0-9_-]+/g,
|
|
207
|
-
severity: 'high',
|
|
208
|
-
description: 'Discord webhooks allow posting messages to channels.'
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: 'Discord Bot Token',
|
|
212
|
-
pattern: /[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27}/g,
|
|
213
|
-
severity: 'high',
|
|
214
|
-
description: 'Discord bot tokens grant full control over your bot.'
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
name: 'Telegram Bot Token',
|
|
218
|
-
pattern: /[0-9]{8,10}:[a-zA-Z0-9_-]{35}/g,
|
|
219
|
-
severity: 'high',
|
|
220
|
-
description: 'Telegram bot tokens grant full control over your bot.'
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
// =========================================================================
|
|
224
|
-
// HIGH: Email Services
|
|
225
|
-
// =========================================================================
|
|
226
|
-
{
|
|
227
|
-
name: 'SendGrid API Key',
|
|
228
|
-
pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
|
|
229
|
-
severity: 'high',
|
|
230
|
-
description: 'SendGrid keys can send emails from your account.'
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: 'Mailgun API Key',
|
|
234
|
-
pattern: /key-[a-zA-Z0-9]{32}/g,
|
|
235
|
-
severity: 'high',
|
|
236
|
-
description: 'Mailgun keys can send emails and access logs.'
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
name: 'Resend API Key',
|
|
240
|
-
pattern: /re_[a-zA-Z0-9]{32,}/g,
|
|
241
|
-
severity: 'high',
|
|
242
|
-
description: 'Resend API keys can send emails from your account and access logs.'
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
name: 'Postmark Server Token',
|
|
246
|
-
pattern: /(?:postmark|POSTMARK)[_-]?(?:server[_-]?)?token["']?\s*[:=]\s*["']?([a-f0-9-]{36})["']?/gi,
|
|
247
|
-
severity: 'high',
|
|
248
|
-
description: 'Postmark tokens can send emails from your account.'
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
name: 'Mailchimp API Key',
|
|
252
|
-
pattern: /[a-f0-9]{32}-us[0-9]{1,2}/g,
|
|
253
|
-
severity: 'high',
|
|
254
|
-
description: 'Mailchimp API keys can access your audience and send campaigns.'
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
// =========================================================================
|
|
258
|
-
// HIGH: SMS & Phone
|
|
259
|
-
// =========================================================================
|
|
260
|
-
{
|
|
261
|
-
name: 'Twilio API Key',
|
|
262
|
-
pattern: /SK[a-f0-9]{32}/g,
|
|
263
|
-
severity: 'high',
|
|
264
|
-
description: 'Twilio keys can send SMS/calls and access account data.'
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
name: 'Twilio Account SID',
|
|
268
|
-
pattern: /AC[a-f0-9]{32}/g,
|
|
269
|
-
severity: 'medium',
|
|
270
|
-
description: 'Twilio Account SIDs identify your account. Usually paired with auth token.'
|
|
271
|
-
},
|
|
272
|
-
|
|
273
|
-
// =========================================================================
|
|
274
|
-
// HIGH: Databases & Backend Services
|
|
275
|
-
// =========================================================================
|
|
276
|
-
{
|
|
277
|
-
name: 'Firebase/Google Service Account',
|
|
278
|
-
pattern: /"type":\s*"service_account"/g,
|
|
279
|
-
severity: 'high',
|
|
280
|
-
description: 'Service account JSON files grant broad GCP/Firebase access.'
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
name: 'Supabase Service Role Key',
|
|
284
|
-
pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
|
|
285
|
-
severity: 'high',
|
|
286
|
-
description: 'Supabase service role keys bypass Row Level Security. Keep server-side only.'
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
name: 'Upstash Redis REST Token',
|
|
290
|
-
pattern: /AX[a-zA-Z0-9]{34,}/g,
|
|
291
|
-
severity: 'high',
|
|
292
|
-
description: 'Upstash Redis tokens grant access to your serverless Redis database.'
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
name: 'Upstash QStash Token',
|
|
296
|
-
pattern: /qstash_[a-zA-Z0-9]{32,}/g,
|
|
297
|
-
severity: 'high',
|
|
298
|
-
description: 'Upstash QStash tokens can schedule and manage message queues.'
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
name: 'Turso Database URL',
|
|
302
|
-
pattern: /libsql:\/\/[^.]+\.turso\.io/g,
|
|
303
|
-
severity: 'high',
|
|
304
|
-
description: 'Turso database URLs. Check for embedded auth tokens in full connection string.'
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
name: 'Convex Deployment URL',
|
|
308
|
-
pattern: /https:\/\/[a-z]+-[a-z]+-[0-9]+\.convex\.cloud/g,
|
|
309
|
-
severity: 'medium',
|
|
310
|
-
description: 'Convex deployment URLs identify your backend. Check for paired secrets.'
|
|
311
|
-
},
|
|
312
|
-
|
|
313
|
-
// =========================================================================
|
|
314
|
-
// HIGH: Hosting & Deployment
|
|
315
|
-
// =========================================================================
|
|
316
|
-
{
|
|
317
|
-
name: 'Vercel Token',
|
|
318
|
-
pattern: /vercel_[a-zA-Z0-9]{24}/gi,
|
|
319
|
-
severity: 'high',
|
|
320
|
-
description: 'Vercel tokens can deploy and manage your projects.'
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
name: 'NPM Token',
|
|
324
|
-
pattern: /npm_[a-zA-Z0-9]{36}/g,
|
|
325
|
-
severity: 'high',
|
|
326
|
-
description: 'NPM tokens can publish packages under your account.'
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
name: 'Heroku API Key',
|
|
330
|
-
pattern: /[hH]eroku.*[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g,
|
|
331
|
-
severity: 'high',
|
|
332
|
-
description: 'Heroku API keys can manage apps and dynos.'
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
name: 'DigitalOcean Token',
|
|
336
|
-
pattern: /dop_v1_[a-f0-9]{64}/g,
|
|
337
|
-
severity: 'high',
|
|
338
|
-
description: 'DigitalOcean tokens can manage droplets and resources.'
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
name: 'Render API Key',
|
|
342
|
-
pattern: /rnd_[a-zA-Z0-9]{32,}/g,
|
|
343
|
-
severity: 'high',
|
|
344
|
-
description: 'Render API keys can manage your services and deployments.'
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
name: 'Fly.io Token',
|
|
348
|
-
pattern: /FlyV1\s+[a-zA-Z0-9_-]{43}/g,
|
|
349
|
-
severity: 'high',
|
|
350
|
-
description: 'Fly.io tokens can deploy and manage your applications.'
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
name: 'Railway Token',
|
|
354
|
-
pattern: /(?:railway|RAILWAY)[_-]?token["']?\s*[:=]\s*["']?([a-f0-9-]{36})["']?/gi,
|
|
355
|
-
severity: 'high',
|
|
356
|
-
description: 'Railway API tokens can manage your services.'
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
name: 'Netlify Personal Access Token',
|
|
360
|
-
pattern: /nfp_[a-zA-Z0-9]{40}/g,
|
|
361
|
-
severity: 'high',
|
|
362
|
-
description: 'Netlify PATs can manage sites and deploys.'
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
name: 'Cloudflare API Token',
|
|
366
|
-
pattern: /(?:cloudflare|CF)[_-]?(?:api[_-]?)?token["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{40})["']?/gi,
|
|
367
|
-
severity: 'high',
|
|
368
|
-
description: 'Cloudflare API tokens can manage DNS, workers, and other services.'
|
|
369
|
-
},
|
|
370
|
-
|
|
371
|
-
// =========================================================================
|
|
372
|
-
// HIGH: Auth Providers
|
|
373
|
-
// =========================================================================
|
|
374
|
-
{
|
|
375
|
-
name: 'Clerk Publishable Key (Live)',
|
|
376
|
-
pattern: /pk_live_[a-zA-Z0-9]{27,}/g,
|
|
377
|
-
severity: 'medium',
|
|
378
|
-
description: 'Clerk publishable keys are meant for frontend but verify it\'s intentional.'
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
name: 'Clerk Test Secret Key',
|
|
382
|
-
pattern: /sk_test_[a-zA-Z0-9]{27,}/g,
|
|
383
|
-
severity: 'medium',
|
|
384
|
-
description: 'Clerk test keys are lower risk but should still be in environment variables.'
|
|
385
|
-
},
|
|
386
|
-
{
|
|
387
|
-
name: 'Auth0 Domain with Credentials',
|
|
388
|
-
pattern: /https:\/\/[^.]+\.auth0\.com.*client_secret/gi,
|
|
389
|
-
severity: 'critical',
|
|
390
|
-
description: 'Auth0 URLs with embedded client secrets should never be in code.'
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
name: 'Supabase Anon Key in Code',
|
|
394
|
-
pattern: /(?:supabase|SUPABASE)[_-]?(?:anon[_-]?)?key["']?\s*[:=]\s*["']?(eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)["']?/gi,
|
|
395
|
-
severity: 'medium',
|
|
396
|
-
description: 'Supabase anon keys. Safe for frontend but verify RLS is enabled.'
|
|
397
|
-
},
|
|
398
|
-
{
|
|
399
|
-
name: 'Stytch Secret Key',
|
|
400
|
-
pattern: /secret-(?:live|test)-[a-zA-Z0-9]{30,}/g,
|
|
401
|
-
severity: 'critical',
|
|
402
|
-
description: 'Stytch secret keys grant full access to your authentication system.'
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
name: 'Okta API Token',
|
|
406
|
-
pattern: /(?:okta|OKTA)[_-]?(?:api[_-]?)?token["']?\s*[:=]\s*["']?(00[a-zA-Z0-9_-]{38})["']?/gi,
|
|
407
|
-
severity: 'high',
|
|
408
|
-
description: 'Okta API tokens can manage your identity provider and user directory.'
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
// =========================================================================
|
|
412
|
-
// CRITICAL: Additional Cloud/Infra
|
|
413
|
-
// =========================================================================
|
|
414
|
-
{
|
|
415
|
-
name: 'Azure Storage Connection String',
|
|
416
|
-
pattern: /DefaultEndpointsProtocol=https;AccountName=[^;]{1,50};AccountKey=[a-zA-Z0-9+/=]{44,}/g,
|
|
417
|
-
severity: 'critical',
|
|
418
|
-
description: 'Azure Storage connection strings contain account keys with full storage access.'
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
name: 'AWS Session Token',
|
|
422
|
-
pattern: /(?:aws_session_token|aws_security_token)[\s]*[=:][\s]*["']?([A-Za-z0-9/+=]{100,})["']?/gi,
|
|
423
|
-
severity: 'critical',
|
|
424
|
-
description: 'AWS session tokens are temporary credentials that still grant account access.'
|
|
425
|
-
},
|
|
426
|
-
{
|
|
427
|
-
name: 'PlanetScale Service Token',
|
|
428
|
-
pattern: /pscale_tkn_[a-zA-Z0-9_-]{32,}/g,
|
|
429
|
-
severity: 'critical',
|
|
430
|
-
description: 'PlanetScale service tokens grant programmatic database branch access.'
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
name: 'Shopify Admin API Access Token',
|
|
434
|
-
pattern: /shpat_[a-fA-F0-9]{32}/g,
|
|
435
|
-
severity: 'critical',
|
|
436
|
-
description: 'Shopify admin tokens grant full store management access including orders and customers.'
|
|
437
|
-
},
|
|
438
|
-
{
|
|
439
|
-
name: 'Shopify Custom App Access Token',
|
|
440
|
-
pattern: /shpca_[a-fA-F0-9]{32}/g,
|
|
441
|
-
severity: 'critical',
|
|
442
|
-
description: 'Shopify custom app tokens provide scoped store admin access.'
|
|
443
|
-
},
|
|
444
|
-
|
|
445
|
-
// =========================================================================
|
|
446
|
-
// HIGH: Productivity & SaaS
|
|
447
|
-
// =========================================================================
|
|
448
|
-
{
|
|
449
|
-
name: 'Linear API Key',
|
|
450
|
-
pattern: /lin_api_[a-zA-Z0-9]{40}/g,
|
|
451
|
-
severity: 'high',
|
|
452
|
-
description: 'Linear API keys can access your project management data.'
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
name: 'Notion API Key',
|
|
456
|
-
pattern: /secret_[a-zA-Z0-9]{43}/g,
|
|
457
|
-
severity: 'high',
|
|
458
|
-
description: 'Notion API keys can access and modify your workspace content.'
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
name: 'Airtable API Key',
|
|
462
|
-
pattern: /pat[a-zA-Z0-9]{14}\.[a-f0-9]{64}/g,
|
|
463
|
-
severity: 'high',
|
|
464
|
-
description: 'Airtable personal access tokens grant access to your bases.'
|
|
465
|
-
},
|
|
466
|
-
{
|
|
467
|
-
name: 'Figma Personal Access Token',
|
|
468
|
-
pattern: /figd_[a-zA-Z0-9_-]{40,}/g,
|
|
469
|
-
severity: 'high',
|
|
470
|
-
description: 'Figma PATs can access your design files and projects.'
|
|
471
|
-
},
|
|
472
|
-
|
|
473
|
-
// =========================================================================
|
|
474
|
-
// HIGH: AI/ML Providers (2025-2026 additions)
|
|
475
|
-
// =========================================================================
|
|
476
|
-
{
|
|
477
|
-
name: 'xAI (Grok) API Key',
|
|
478
|
-
pattern: /xai-[A-Za-z0-9]{52,}/g,
|
|
479
|
-
severity: 'high',
|
|
480
|
-
description: 'xAI API keys grant access to Grok models and incur usage charges on your account.'
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
name: 'Tavily API Key',
|
|
484
|
-
pattern: /tvly-[a-zA-Z0-9]{32,}/g,
|
|
485
|
-
severity: 'high',
|
|
486
|
-
description: 'Tavily API keys grant access to their AI-powered search service.'
|
|
487
|
-
},
|
|
488
|
-
{
|
|
489
|
-
name: 'Cerebras API Key',
|
|
490
|
-
pattern: /csk-[a-zA-Z0-9]{48,}/g,
|
|
491
|
-
severity: 'high',
|
|
492
|
-
description: 'Cerebras API keys provide access to fast AI inference.'
|
|
493
|
-
},
|
|
494
|
-
{
|
|
495
|
-
name: 'Pinecone API Key',
|
|
496
|
-
pattern: /pcsk_[a-zA-Z0-9]{47}_[a-zA-Z0-9]{47}/g,
|
|
497
|
-
severity: 'high',
|
|
498
|
-
description: 'Pinecone API keys grant access to your vector database indexes.'
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
name: 'ElevenLabs API Key',
|
|
502
|
-
pattern: /(?:elevenlabs|ELEVENLABS)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-fA-F0-9]{32})["']?/gi,
|
|
503
|
-
severity: 'high',
|
|
504
|
-
description: 'ElevenLabs API keys grant access to their voice AI cloning and synthesis service.'
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
name: 'DeepSeek API Key',
|
|
508
|
-
pattern: /(?:deepseek|DEEPSEEK)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?(sk-[a-zA-Z0-9]{32,})["']?/gi,
|
|
509
|
-
severity: 'high',
|
|
510
|
-
description: 'DeepSeek API keys grant access to their language models.'
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
name: 'Voyage AI API Key',
|
|
514
|
-
pattern: /(?:voyage|VOYAGE)[_-]?(?:ai[_-]?)?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{32,})["']?/gi,
|
|
515
|
-
severity: 'high',
|
|
516
|
-
requiresEntropyCheck: true,
|
|
517
|
-
description: 'Voyage AI API keys provide access to their embedding and reranking models.'
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
name: 'Fireworks AI API Key',
|
|
521
|
-
pattern: /(?:fireworks|FIREWORKS)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{32,})["']?/gi,
|
|
522
|
-
severity: 'high',
|
|
523
|
-
requiresEntropyCheck: true,
|
|
524
|
-
description: 'Fireworks AI API keys grant access to fast open-source model inference.'
|
|
525
|
-
},
|
|
526
|
-
{
|
|
527
|
-
name: 'Anyscale API Key',
|
|
528
|
-
pattern: /esecret_[a-zA-Z0-9]{32,}/g,
|
|
529
|
-
severity: 'high',
|
|
530
|
-
description: 'Anyscale API keys grant access to their managed Ray and LLM endpoints.'
|
|
531
|
-
},
|
|
532
|
-
|
|
533
|
-
// =========================================================================
|
|
534
|
-
// HIGH: Payments (Additional)
|
|
535
|
-
// =========================================================================
|
|
536
|
-
{
|
|
537
|
-
name: 'Stripe Test Secret Key',
|
|
538
|
-
pattern: /sk_test_[a-zA-Z0-9]{24,}/g,
|
|
539
|
-
severity: 'medium',
|
|
540
|
-
description: 'Stripe test keys are lower risk but should still be in environment variables.'
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
name: 'Stripe Live Publishable Key',
|
|
544
|
-
pattern: /pk_live_[a-zA-Z0-9]{24,}/g,
|
|
545
|
-
severity: 'medium',
|
|
546
|
-
description: 'Stripe publishable keys are meant for frontend but verify it\'s intentional.'
|
|
547
|
-
},
|
|
548
|
-
{
|
|
549
|
-
name: 'Stripe Webhook Secret',
|
|
550
|
-
pattern: /whsec_[a-zA-Z0-9]{32,}/g,
|
|
551
|
-
severity: 'high',
|
|
552
|
-
description: 'Stripe webhook secrets validate incoming webhooks. Keep server-side only.'
|
|
553
|
-
},
|
|
554
|
-
{
|
|
555
|
-
name: 'Lemon Squeezy API Key',
|
|
556
|
-
pattern: /(?:lemon|LEMON)[_-]?(?:squeezy|SQUEEZY)?[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9-]{36})["']?/gi,
|
|
557
|
-
severity: 'high',
|
|
558
|
-
description: 'Lemon Squeezy API keys can manage your store and orders.'
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
name: 'Paddle API Key',
|
|
562
|
-
pattern: /(?:paddle|PADDLE)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{64})["']?/gi,
|
|
563
|
-
severity: 'high',
|
|
564
|
-
description: 'Paddle API keys can manage your subscriptions and payments.'
|
|
565
|
-
},
|
|
566
|
-
{
|
|
567
|
-
name: 'Stripe Restricted Key',
|
|
568
|
-
pattern: /rk_(?:live|test)_[a-zA-Z0-9]{24,}/g,
|
|
569
|
-
severity: 'high',
|
|
570
|
-
description: 'Stripe restricted keys have scoped permissions but still grant API access.'
|
|
571
|
-
},
|
|
572
|
-
{
|
|
573
|
-
name: 'Square Access Token',
|
|
574
|
-
pattern: /EAAAl[0-9a-zA-Z_-]{50,}/g,
|
|
575
|
-
severity: 'high',
|
|
576
|
-
description: 'Square access tokens can process payments and manage store data.'
|
|
577
|
-
},
|
|
578
|
-
{
|
|
579
|
-
name: 'Square OAuth Token',
|
|
580
|
-
pattern: /sq0[a-z]tp-[a-zA-Z0-9_-]{22}/g,
|
|
581
|
-
severity: 'high',
|
|
582
|
-
description: 'Square OAuth tokens authorize Square API access for a merchant account.'
|
|
583
|
-
},
|
|
584
|
-
{
|
|
585
|
-
name: 'Shopify Shared Secret',
|
|
586
|
-
pattern: /shpss_[a-fA-F0-9]{32}/g,
|
|
587
|
-
severity: 'high',
|
|
588
|
-
description: 'Shopify shared secrets validate webhook payload signatures.'
|
|
589
|
-
},
|
|
590
|
-
{
|
|
591
|
-
name: 'Braintree Access Token',
|
|
592
|
-
pattern: /access_token\$(?:production|sandbox)\$[a-z0-9]{16}\$[a-f0-9]{32}/g,
|
|
593
|
-
severity: 'critical',
|
|
594
|
-
description: 'Braintree access tokens grant full payment processing access.'
|
|
595
|
-
},
|
|
596
|
-
|
|
597
|
-
// =========================================================================
|
|
598
|
-
// HIGH: Realtime & Messaging
|
|
599
|
-
// =========================================================================
|
|
600
|
-
{
|
|
601
|
-
name: 'Pusher App Secret',
|
|
602
|
-
pattern: /(?:pusher|PUSHER)[_-]?(?:app[_-]?)?secret["']?\s*[:=]\s*["']?([a-f0-9]{32})["']?/gi,
|
|
603
|
-
severity: 'high',
|
|
604
|
-
description: 'Pusher app secrets authenticate private and presence channel subscriptions.'
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
name: 'Ably API Key',
|
|
608
|
-
pattern: /(?:ably|ABLY)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{8}\.[a-zA-Z0-9_-]{6}:[a-zA-Z0-9+/=_-]{43,})["']?/gi,
|
|
609
|
-
severity: 'high',
|
|
610
|
-
description: 'Ably API keys grant full publish and subscribe access to your realtime channels.'
|
|
611
|
-
},
|
|
612
|
-
{
|
|
613
|
-
name: 'Mapbox Access Token',
|
|
614
|
-
pattern: /pk\.eyJ1[a-zA-Z0-9._-]{40,}/g,
|
|
615
|
-
severity: 'medium',
|
|
616
|
-
description: 'Mapbox tokens can incur charges if abused. Restrict token scope and allowed URLs.'
|
|
617
|
-
},
|
|
618
|
-
|
|
619
|
-
// =========================================================================
|
|
620
|
-
// HIGH: DevOps & CI/CD
|
|
621
|
-
// =========================================================================
|
|
622
|
-
{
|
|
623
|
-
name: 'CircleCI Personal API Token',
|
|
624
|
-
pattern: /CCIPAT_[a-zA-Z0-9]{40,}/g,
|
|
625
|
-
severity: 'high',
|
|
626
|
-
description: 'CircleCI API tokens can trigger builds, read logs, and access pipeline data.'
|
|
627
|
-
},
|
|
628
|
-
{
|
|
629
|
-
name: 'Sentry Auth Token',
|
|
630
|
-
pattern: /sntrys_[a-zA-Z0-9_]{64,}/g,
|
|
631
|
-
severity: 'high',
|
|
632
|
-
description: 'Sentry auth tokens provide full API access to your error data and project settings.'
|
|
633
|
-
},
|
|
634
|
-
{
|
|
635
|
-
name: 'Terraform Cloud Token',
|
|
636
|
-
pattern: /(?:terraform|TFC)[_-]?(?:api[_-]?)?token["']?\s*[:=]\s*["']?([a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9_-]{67,})["']?/gi,
|
|
637
|
-
severity: 'high',
|
|
638
|
-
description: 'Terraform Cloud tokens can read and apply infrastructure state.'
|
|
639
|
-
},
|
|
640
|
-
{
|
|
641
|
-
name: 'Cloudinary API Secret',
|
|
642
|
-
pattern: /(?:cloudinary|CLOUDINARY)[_-]?(?:api[_-]?)?secret["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{27,})["']?/gi,
|
|
643
|
-
severity: 'high',
|
|
644
|
-
requiresEntropyCheck: true,
|
|
645
|
-
description: 'Cloudinary API secrets grant access to your media library and transformations.'
|
|
646
|
-
},
|
|
647
|
-
{
|
|
648
|
-
name: 'Algolia Admin API Key',
|
|
649
|
-
pattern: /(?:algolia|ALGOLIA)[_-]?(?:admin[_-]?)?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{32})["']?/gi,
|
|
650
|
-
severity: 'high',
|
|
651
|
-
description: 'Algolia admin keys can modify indices and change search configuration.'
|
|
652
|
-
},
|
|
653
|
-
{
|
|
654
|
-
name: 'LaunchDarkly SDK Key',
|
|
655
|
-
pattern: /sdk-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g,
|
|
656
|
-
severity: 'high',
|
|
657
|
-
description: 'LaunchDarkly SDK keys can read all feature flags and user data.'
|
|
658
|
-
},
|
|
659
|
-
|
|
660
|
-
// =========================================================================
|
|
661
|
-
// HIGH: Analytics & Monitoring
|
|
662
|
-
// =========================================================================
|
|
663
|
-
{
|
|
664
|
-
name: 'Sentry DSN',
|
|
665
|
-
pattern: /https:\/\/[a-f0-9]{32}@[a-z0-9]+\.ingest\.sentry\.io\/[0-9]+/g,
|
|
666
|
-
severity: 'medium',
|
|
667
|
-
description: 'Sentry DSNs are semi-public but contain project identifiers.'
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
name: 'PostHog API Key',
|
|
671
|
-
pattern: /phc_[a-zA-Z0-9]{32,}/g,
|
|
672
|
-
severity: 'medium',
|
|
673
|
-
description: 'PostHog project API keys. Usually safe in frontend but verify.'
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
name: 'New Relic API Key',
|
|
677
|
-
pattern: /NRAK-[A-Z0-9]{27}/g,
|
|
678
|
-
severity: 'high',
|
|
679
|
-
description: 'New Relic API keys can access your monitoring data and configurations.'
|
|
680
|
-
},
|
|
681
|
-
{
|
|
682
|
-
name: 'Datadog API Key',
|
|
683
|
-
pattern: /(?:datadog|DD)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{32})["']?/gi,
|
|
684
|
-
severity: 'high',
|
|
685
|
-
description: 'Datadog API keys can access and send monitoring data.'
|
|
686
|
-
},
|
|
687
|
-
|
|
688
|
-
// =========================================================================
|
|
689
|
-
// MEDIUM: Generic patterns (entropy-checked to reduce false positives)
|
|
690
|
-
// requiresEntropyCheck: true → value is scored before reporting
|
|
691
|
-
// =========================================================================
|
|
692
|
-
{
|
|
693
|
-
name: 'Generic API Key Assignment',
|
|
694
|
-
pattern: /["']?(?:api[_-]?key|apikey)["']?\s*[:=]\s*["']([a-zA-Z0-9_\-]{20,})["']/gi,
|
|
695
|
-
severity: 'medium',
|
|
696
|
-
requiresEntropyCheck: true,
|
|
697
|
-
description: 'Hardcoded API keys should be moved to environment variables.'
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
name: 'Generic Secret Assignment',
|
|
701
|
-
pattern: /["']?(?:secret|secret[_-]?key)["']?\s*[:=]\s*["']([a-zA-Z0-9_\-]{20,})["']/gi,
|
|
702
|
-
severity: 'medium',
|
|
703
|
-
requiresEntropyCheck: true,
|
|
704
|
-
description: 'Hardcoded secrets should be moved to environment variables.'
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
name: 'Password Assignment',
|
|
708
|
-
pattern: /["']?password["']?\s*[:=]\s*["']([^"']{8,})["']/gi,
|
|
709
|
-
severity: 'medium',
|
|
710
|
-
requiresEntropyCheck: true,
|
|
711
|
-
description: 'Hardcoded passwords are a critical vulnerability.'
|
|
712
|
-
},
|
|
713
|
-
{
|
|
714
|
-
name: 'Database URL with Credentials',
|
|
715
|
-
pattern: /(mongodb|postgres|postgresql|mysql|redis):\/\/[^:]+:[^@]+@[^\s"']+/gi,
|
|
716
|
-
severity: 'medium',
|
|
717
|
-
requiresEntropyCheck: true,
|
|
718
|
-
description: 'Database URLs with embedded passwords expose your database.'
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
name: 'Bearer Token in Code',
|
|
722
|
-
pattern: /["']Bearer\s+[a-zA-Z0-9_\-\.=]{20,}["']/gi,
|
|
723
|
-
severity: 'medium',
|
|
724
|
-
requiresEntropyCheck: true,
|
|
725
|
-
description: 'Hardcoded bearer tokens should not be in source code.'
|
|
726
|
-
},
|
|
727
|
-
{
|
|
728
|
-
name: 'Basic Auth Header',
|
|
729
|
-
pattern: /["']Basic\s+[A-Za-z0-9+/=]{20,}["']/gi,
|
|
730
|
-
severity: 'medium',
|
|
731
|
-
requiresEntropyCheck: true,
|
|
732
|
-
description: 'Basic auth headers contain base64-encoded credentials.'
|
|
733
|
-
},
|
|
734
|
-
{
|
|
735
|
-
name: 'Private Key in Environment Variable',
|
|
736
|
-
pattern: /PRIVATE[_-]?KEY["']?\s*[:=]\s*["']([^"']+)["']/gi,
|
|
737
|
-
severity: 'high',
|
|
738
|
-
requiresEntropyCheck: true,
|
|
739
|
-
description: 'Private keys should be loaded from files, not hardcoded.'
|
|
740
|
-
}
|
|
741
|
-
];
|
|
742
|
-
|
|
743
|
-
// =============================================================================
|
|
744
|
-
// FILES AND DIRECTORIES TO SKIP
|
|
745
|
-
// =============================================================================
|
|
746
|
-
|
|
747
|
-
export const SKIP_DIRS = new Set([
|
|
748
|
-
'node_modules',
|
|
749
|
-
'.git',
|
|
750
|
-
'venv',
|
|
751
|
-
'env',
|
|
752
|
-
'.venv',
|
|
753
|
-
'__pycache__',
|
|
754
|
-
'.next',
|
|
755
|
-
'.nuxt',
|
|
756
|
-
'dist',
|
|
757
|
-
'build',
|
|
758
|
-
'out',
|
|
759
|
-
'.output',
|
|
760
|
-
'coverage',
|
|
761
|
-
'.nyc_output',
|
|
762
|
-
'vendor',
|
|
763
|
-
'.bundle',
|
|
764
|
-
'.cache',
|
|
765
|
-
'.parcel-cache',
|
|
766
|
-
'.turbo',
|
|
767
|
-
'bower_components',
|
|
768
|
-
'jspm_packages',
|
|
769
|
-
'.vercel',
|
|
770
|
-
'.netlify',
|
|
771
|
-
'.serverless',
|
|
772
|
-
// Additional build/tooling output
|
|
773
|
-
'.yarn',
|
|
774
|
-
'storybook-static',
|
|
775
|
-
'playwright-report',
|
|
776
|
-
'.playwright',
|
|
777
|
-
'.gradle',
|
|
778
|
-
'target', // Maven/Gradle build output
|
|
779
|
-
'.pytest_cache',
|
|
780
|
-
'.mypy_cache',
|
|
781
|
-
'.ruff_cache',
|
|
782
|
-
'.tox',
|
|
783
|
-
'site-packages',
|
|
784
|
-
'.pnpm',
|
|
785
|
-
'jspm_packages',
|
|
786
|
-
'.expo',
|
|
787
|
-
'.docusaurus',
|
|
788
|
-
'.storybook',
|
|
789
|
-
'.ship-safe',
|
|
790
|
-
]);
|
|
791
|
-
|
|
792
|
-
export const SKIP_EXTENSIONS = new Set([
|
|
793
|
-
// Images
|
|
794
|
-
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.webp', '.bmp', '.tiff',
|
|
795
|
-
// Fonts
|
|
796
|
-
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
797
|
-
// Media
|
|
798
|
-
'.mp3', '.mp4', '.wav', '.avi', '.mov', '.webm', '.ogg',
|
|
799
|
-
// Archives
|
|
800
|
-
'.zip', '.tar', '.gz', '.rar', '.7z', '.bz2',
|
|
801
|
-
// Documents
|
|
802
|
-
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
803
|
-
// Lock files (usually very large and auto-generated)
|
|
804
|
-
'.lock',
|
|
805
|
-
// Minified files
|
|
806
|
-
'.min.js', '.min.css',
|
|
807
|
-
// Binaries
|
|
808
|
-
'.exe', '.dll', '.so', '.dylib', '.bin', '.o', '.a',
|
|
809
|
-
// Maps
|
|
810
|
-
'.map'
|
|
811
|
-
]);
|
|
812
|
-
|
|
813
|
-
// Auto-generated lockfiles — large, no real secrets, cause false positives
|
|
814
|
-
export const SKIP_FILENAMES = new Set([
|
|
815
|
-
'package-lock.json',
|
|
816
|
-
'pnpm-lock.yaml',
|
|
817
|
-
'composer.lock',
|
|
818
|
-
'Gemfile.lock',
|
|
819
|
-
'Pipfile.lock',
|
|
820
|
-
'poetry.lock',
|
|
821
|
-
'cargo.lock',
|
|
822
|
-
'pubspec.lock',
|
|
823
|
-
'go.sum',
|
|
824
|
-
'flake.lock',
|
|
825
|
-
// Skip ship-safe's own output files to avoid scanning the report
|
|
826
|
-
'ship-safe-report.html',
|
|
827
|
-
'ship-safe-report.pdf',
|
|
828
|
-
]);
|
|
829
|
-
|
|
830
|
-
// Maximum file size to scan (1MB)
|
|
831
|
-
export const MAX_FILE_SIZE = 1_000_000;
|
|
832
|
-
|
|
833
|
-
// =============================================================================
|
|
834
|
-
// .GITIGNORE LOADING
|
|
835
|
-
// =============================================================================
|
|
836
|
-
|
|
837
|
-
// Gitignore patterns that should NEVER be skipped by a security scanner.
|
|
838
|
-
// These files are gitignored precisely because they contain secrets or
|
|
839
|
-
// sensitive config — which is exactly what we want to detect.
|
|
840
|
-
const SECURITY_SENSITIVE_PATTERNS = new Set([
|
|
841
|
-
'.env',
|
|
842
|
-
'.env.local',
|
|
843
|
-
'.env.development',
|
|
844
|
-
'.env.development.local',
|
|
845
|
-
'.env.test',
|
|
846
|
-
'.env.test.local',
|
|
847
|
-
'.env.production',
|
|
848
|
-
'.env.production.local',
|
|
849
|
-
'.env.staging',
|
|
850
|
-
'*.pem',
|
|
851
|
-
'*.key',
|
|
852
|
-
'*.p12',
|
|
853
|
-
'*.pfx',
|
|
854
|
-
'*.jks',
|
|
855
|
-
'*.keystore',
|
|
856
|
-
'*.crt',
|
|
857
|
-
'*.cer',
|
|
858
|
-
'credentials.json',
|
|
859
|
-
'service-account.json',
|
|
860
|
-
'serviceAccountKey.json',
|
|
861
|
-
'*.secret',
|
|
862
|
-
'htpasswd',
|
|
863
|
-
'.htpasswd',
|
|
864
|
-
'id_rsa',
|
|
865
|
-
'id_ed25519',
|
|
866
|
-
'*.sqlite',
|
|
867
|
-
'*.db',
|
|
868
|
-
]);
|
|
869
|
-
|
|
870
|
-
/**
|
|
871
|
-
* Load patterns from .gitignore file in the project root.
|
|
872
|
-
* Returns an array of glob-compatible ignore patterns.
|
|
873
|
-
*
|
|
874
|
-
* Smart filtering: skips gitignored build output, caches, and vendor dirs,
|
|
875
|
-
* but ALWAYS scans security-sensitive files (.env, *.key, *.pem, etc.)
|
|
876
|
-
* even if they appear in .gitignore.
|
|
877
|
-
*/
|
|
878
|
-
export function loadGitignorePatterns(rootPath) {
|
|
879
|
-
const gitignorePath = path.join(rootPath, '.gitignore');
|
|
880
|
-
try {
|
|
881
|
-
if (!fs.existsSync(gitignorePath)) return [];
|
|
882
|
-
return fs.readFileSync(gitignorePath, 'utf-8')
|
|
883
|
-
.split('\n')
|
|
884
|
-
.map(l => l.trim())
|
|
885
|
-
.filter(l => l && !l.startsWith('#') && !l.startsWith('!'))
|
|
886
|
-
.filter(p => !isSecuritySensitive(p))
|
|
887
|
-
.map(p => {
|
|
888
|
-
// Convert .gitignore patterns to fast-glob ignore patterns
|
|
889
|
-
if (p.startsWith('/')) {
|
|
890
|
-
// Rooted pattern: /build → build/**
|
|
891
|
-
return p.slice(1) + (p.endsWith('/') ? '**' : '');
|
|
892
|
-
}
|
|
893
|
-
if (p.endsWith('/')) {
|
|
894
|
-
// Directory pattern: logs/ → **/logs/**
|
|
895
|
-
return `**/${p}**`;
|
|
896
|
-
}
|
|
897
|
-
// General pattern: *.log → **/*.log, dist → **/dist, **/dist/**
|
|
898
|
-
if (!p.includes('/') && !p.includes('*')) {
|
|
899
|
-
return [`**/${p}`, `**/${p}/**`];
|
|
900
|
-
}
|
|
901
|
-
return `**/${p}`;
|
|
902
|
-
})
|
|
903
|
-
.flat();
|
|
904
|
-
} catch {
|
|
905
|
-
return [];
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
/**
|
|
910
|
-
* Check if a .gitignore pattern targets security-sensitive files.
|
|
911
|
-
* These should always be scanned regardless of .gitignore.
|
|
912
|
-
*/
|
|
913
|
-
function isSecuritySensitive(pattern) {
|
|
914
|
-
const cleaned = pattern.replace(/^\//, '').replace(/\/$/, '');
|
|
915
|
-
if (SECURITY_SENSITIVE_PATTERNS.has(cleaned)) return true;
|
|
916
|
-
// Check wildcard patterns like *.pem, *.key
|
|
917
|
-
for (const sensitive of SECURITY_SENSITIVE_PATTERNS) {
|
|
918
|
-
if (sensitive.startsWith('*') && cleaned.endsWith(sensitive.slice(1))) return true;
|
|
919
|
-
if (cleaned === sensitive || cleaned.endsWith('/' + sensitive)) return true;
|
|
920
|
-
}
|
|
921
|
-
return false;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// =============================================================================
|
|
925
|
-
// SECURITY VULNERABILITY PATTERNS
|
|
926
|
-
// =============================================================================
|
|
927
|
-
//
|
|
928
|
-
// These patterns detect insecure code patterns (OWASP Top 10, misconfigs, etc.)
|
|
929
|
-
// They are distinct from SECRET_PATTERNS:
|
|
930
|
-
// - Secrets → move to env vars, rotate if exposed
|
|
931
|
-
// - Vulns → fix the code pattern, can't just rotate
|
|
932
|
-
//
|
|
933
|
-
// Each pattern includes category: 'vulnerability' to separate output sections.
|
|
934
|
-
|
|
935
|
-
export const SECURITY_PATTERNS = [
|
|
936
|
-
|
|
937
|
-
// =========================================================================
|
|
938
|
-
// XSS — Cross-Site Scripting
|
|
939
|
-
// =========================================================================
|
|
940
|
-
{
|
|
941
|
-
name: 'XSS: dangerouslySetInnerHTML',
|
|
942
|
-
pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{/g,
|
|
943
|
-
severity: 'high',
|
|
944
|
-
category: 'vulnerability',
|
|
945
|
-
description: 'dangerouslySetInnerHTML can introduce XSS if the value contains user input. Sanitize with DOMPurify or restructure to avoid it.'
|
|
946
|
-
},
|
|
947
|
-
{
|
|
948
|
-
name: 'XSS: innerHTML Assignment',
|
|
949
|
-
pattern: /\.innerHTML\s*=/g,
|
|
950
|
-
severity: 'medium',
|
|
951
|
-
category: 'vulnerability',
|
|
952
|
-
description: 'innerHTML set to user-controlled data leads to XSS. Use textContent for plain text or DOMPurify to sanitize HTML.'
|
|
953
|
-
},
|
|
954
|
-
{
|
|
955
|
-
name: 'XSS: document.write',
|
|
956
|
-
pattern: /\bdocument\.write\s*\(/g,
|
|
957
|
-
severity: 'medium',
|
|
958
|
-
category: 'vulnerability',
|
|
959
|
-
description: 'document.write() is deprecated and can introduce XSS. Use DOM manipulation (createElement, appendChild) instead.'
|
|
960
|
-
},
|
|
961
|
-
|
|
962
|
-
// =========================================================================
|
|
963
|
-
// Code Injection
|
|
964
|
-
// =========================================================================
|
|
965
|
-
{
|
|
966
|
-
name: 'Code Injection: eval()',
|
|
967
|
-
pattern: /\beval\s*\(/g,
|
|
968
|
-
severity: 'high',
|
|
969
|
-
category: 'vulnerability',
|
|
970
|
-
description: 'eval() executes arbitrary JavaScript and is a serious attack vector. Replace with JSON.parse(), Function calls, or safer alternatives.'
|
|
971
|
-
},
|
|
972
|
-
{
|
|
973
|
-
name: 'Code Injection: new Function()',
|
|
974
|
-
pattern: /\bnew\s+Function\s*\(/g,
|
|
975
|
-
severity: 'high',
|
|
976
|
-
category: 'vulnerability',
|
|
977
|
-
description: 'new Function() is functionally equivalent to eval() and can execute arbitrary code. Avoid dynamic code generation.'
|
|
978
|
-
},
|
|
979
|
-
|
|
980
|
-
// =========================================================================
|
|
981
|
-
// SQL Injection
|
|
982
|
-
// =========================================================================
|
|
983
|
-
{
|
|
984
|
-
name: 'SQL Injection: Template Literal Query',
|
|
985
|
-
pattern: /`(?:SELECT|INSERT|UPDATE|DELETE|DROP\s+TABLE|ALTER\s+TABLE)[^`]*\$\{/gi,
|
|
986
|
-
severity: 'critical',
|
|
987
|
-
category: 'vulnerability',
|
|
988
|
-
description: 'SQL queries with interpolated template variables are vulnerable to injection. Use parameterized queries or a query builder.'
|
|
989
|
-
},
|
|
990
|
-
{
|
|
991
|
-
name: 'SQL Injection: String Concatenation Query',
|
|
992
|
-
pattern: /["'](?:SELECT|INSERT|UPDATE|DELETE)\s+[^"']{4,}["']\s*\+/gi,
|
|
993
|
-
severity: 'high',
|
|
994
|
-
category: 'vulnerability',
|
|
995
|
-
description: 'Building SQL with string concatenation is vulnerable to SQL injection. Use parameterized queries (?, $1) or an ORM.'
|
|
996
|
-
},
|
|
997
|
-
|
|
998
|
-
// =========================================================================
|
|
999
|
-
// Command Injection
|
|
1000
|
-
// =========================================================================
|
|
1001
|
-
{
|
|
1002
|
-
name: 'Command Injection: exec with Template Literal',
|
|
1003
|
-
pattern: /\bexec(?:Sync)?\s*\(\s*`[^`]*\$\{/g,
|
|
1004
|
-
severity: 'critical',
|
|
1005
|
-
category: 'vulnerability',
|
|
1006
|
-
description: 'Running shell commands with interpolated values can lead to command injection. Validate all inputs or use execFile() with argument arrays.'
|
|
1007
|
-
},
|
|
1008
|
-
{
|
|
1009
|
-
name: 'Command Injection: shell: true',
|
|
1010
|
-
pattern: /\bspawn(?:Sync)?\s*\([^)]*\bshell\s*:\s*true/g,
|
|
1011
|
-
severity: 'high',
|
|
1012
|
-
category: 'vulnerability',
|
|
1013
|
-
description: 'shell: true in spawn/spawnSync enables shell expansion and can lead to command injection. Remove shell: true and pass arguments as an array.'
|
|
1014
|
-
},
|
|
1015
|
-
|
|
1016
|
-
// =========================================================================
|
|
1017
|
-
// Weak Cryptography
|
|
1018
|
-
// =========================================================================
|
|
1019
|
-
{
|
|
1020
|
-
name: 'Weak Crypto: MD5',
|
|
1021
|
-
pattern: /createHash\s*\(\s*['"]md5['"]\s*\)/gi,
|
|
1022
|
-
severity: 'medium',
|
|
1023
|
-
category: 'vulnerability',
|
|
1024
|
-
description: 'MD5 is cryptographically broken and must not be used for security purposes. Use SHA-256 (createHash("sha256")) or SHA-3.'
|
|
1025
|
-
},
|
|
1026
|
-
{
|
|
1027
|
-
name: 'Weak Crypto: SHA-1',
|
|
1028
|
-
pattern: /createHash\s*\(\s*['"]sha1['"]\s*\)/gi,
|
|
1029
|
-
severity: 'medium',
|
|
1030
|
-
category: 'vulnerability',
|
|
1031
|
-
description: 'SHA-1 is cryptographically weak and collision-prone. Use SHA-256 (createHash("sha256")) or SHA-3 instead.'
|
|
1032
|
-
},
|
|
1033
|
-
|
|
1034
|
-
// =========================================================================
|
|
1035
|
-
// TLS / SSL Bypass
|
|
1036
|
-
// =========================================================================
|
|
1037
|
-
{
|
|
1038
|
-
name: 'TLS Bypass: NODE_TLS_REJECT_UNAUTHORIZED=0',
|
|
1039
|
-
pattern: /NODE_TLS_REJECT_UNAUTHORIZED\s*[=:]\s*['"]?0['"]?/g,
|
|
1040
|
-
severity: 'critical',
|
|
1041
|
-
category: 'vulnerability',
|
|
1042
|
-
description: 'Setting NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS certificate validation and exposes your app to MITM attacks. Never use in production.'
|
|
1043
|
-
},
|
|
1044
|
-
{
|
|
1045
|
-
name: 'TLS Bypass: rejectUnauthorized false',
|
|
1046
|
-
pattern: /\brejectUnauthorized\s*:\s*false\b/g,
|
|
1047
|
-
severity: 'high',
|
|
1048
|
-
category: 'vulnerability',
|
|
1049
|
-
description: 'rejectUnauthorized: false disables TLS certificate checking and enables man-in-the-middle attacks. Remove it or use a proper CA bundle.'
|
|
1050
|
-
},
|
|
1051
|
-
{
|
|
1052
|
-
name: 'TLS Bypass: verify=False (Python)',
|
|
1053
|
-
pattern: /\brequests\.\w+\s*\([^)]*\bverify\s*=\s*False\b/g,
|
|
1054
|
-
severity: 'high',
|
|
1055
|
-
category: 'vulnerability',
|
|
1056
|
-
description: 'verify=False in Python requests disables SSL certificate verification. Remove this or pass verify="/path/to/ca-bundle.crt".'
|
|
1057
|
-
},
|
|
1058
|
-
|
|
1059
|
-
// =========================================================================
|
|
1060
|
-
// Unsafe Deserialization
|
|
1061
|
-
// =========================================================================
|
|
1062
|
-
{
|
|
1063
|
-
name: 'Unsafe Deserialization: pickle.loads',
|
|
1064
|
-
pattern: /\bpickle\.loads?\s*\(/g,
|
|
1065
|
-
severity: 'high',
|
|
1066
|
-
category: 'vulnerability',
|
|
1067
|
-
description: 'pickle.loads() on untrusted data can execute arbitrary Python code (RCE). Use JSON or another safe format for data from untrusted sources.'
|
|
1068
|
-
},
|
|
1069
|
-
{
|
|
1070
|
-
name: 'Unsafe Deserialization: yaml.load',
|
|
1071
|
-
pattern: /\byaml\.load\s*\(/g,
|
|
1072
|
-
severity: 'medium',
|
|
1073
|
-
category: 'vulnerability',
|
|
1074
|
-
description: 'yaml.load() can execute arbitrary code with certain YAML tags. Use yaml.safe_load() for untrusted input.'
|
|
1075
|
-
},
|
|
1076
|
-
|
|
1077
|
-
// =========================================================================
|
|
1078
|
-
// Security Misconfigurations
|
|
1079
|
-
// =========================================================================
|
|
1080
|
-
{
|
|
1081
|
-
name: 'Security Config: CORS Wildcard',
|
|
1082
|
-
pattern: /\borigin\s*:\s*['"]?\*['"]?/g,
|
|
1083
|
-
severity: 'medium',
|
|
1084
|
-
category: 'vulnerability',
|
|
1085
|
-
description: 'CORS wildcard (*) allows any origin to make credentialed requests to your API. Use a specific allowlist of trusted origins.'
|
|
1086
|
-
},
|
|
1087
|
-
|
|
1088
|
-
// =========================================================================
|
|
1089
|
-
// Deprecated / Insecure Node.js APIs
|
|
1090
|
-
// =========================================================================
|
|
1091
|
-
{
|
|
1092
|
-
name: 'Deprecated API: new Buffer()',
|
|
1093
|
-
pattern: /\bnew\s+Buffer\s*\(/g,
|
|
1094
|
-
severity: 'medium',
|
|
1095
|
-
category: 'vulnerability',
|
|
1096
|
-
description: 'new Buffer() is deprecated since Node.js 6 and has security implications. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe().'
|
|
1097
|
-
},
|
|
1098
|
-
];
|
|
1099
|
-
|
|
1100
|
-
// =============================================================================
|
|
1101
|
-
// TEST FILE PATTERNS (skipped by default, override with --include-tests)
|
|
1102
|
-
// =============================================================================
|
|
1103
|
-
// Test fixtures are the #1 source of false positives. They contain fake
|
|
1104
|
-
// credentials, mock data, and example values that look like real secrets.
|
|
1105
|
-
|
|
1106
|
-
export const TEST_FILE_PATTERNS = [
|
|
1107
|
-
/\.test\.[jt]sx?$/,
|
|
1108
|
-
/\.spec\.[jt]sx?$/,
|
|
1109
|
-
/\.test\.py$/,
|
|
1110
|
-
/test_[^/]+\.py$/,
|
|
1111
|
-
/__tests__[/\\]/,
|
|
1112
|
-
/[/\\]tests?[/\\]/,
|
|
1113
|
-
/[/\\]test[/\\]/,
|
|
1114
|
-
/[/\\]fixtures?[/\\]/,
|
|
1115
|
-
/[/\\]mocks?[/\\]/,
|
|
1116
|
-
/[/\\]__mocks__[/\\]/,
|
|
1117
|
-
/[/\\]stubs?[/\\]/,
|
|
1118
|
-
/[/\\]fakes?[/\\]/,
|
|
1119
|
-
/\.stories\.[jt]sx?$/, // Storybook story files
|
|
1120
|
-
/\.mock\.[jt]sx?$/,
|
|
1121
|
-
];
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Secret Detection Patterns
|
|
6
|
+
* =========================
|
|
7
|
+
*
|
|
8
|
+
* These regex patterns detect common secret formats.
|
|
9
|
+
* Each pattern includes:
|
|
10
|
+
* - name: Human-readable identifier
|
|
11
|
+
* - pattern: Regular expression
|
|
12
|
+
* - severity: 'critical' | 'high' | 'medium'
|
|
13
|
+
* - description: Why this matters
|
|
14
|
+
*
|
|
15
|
+
* MAINTENANCE NOTES:
|
|
16
|
+
* - Patterns should have low false-positive rates
|
|
17
|
+
* - Only include patterns with SPECIFIC PREFIXES to avoid noise
|
|
18
|
+
* - Test new patterns against real codebases before adding
|
|
19
|
+
* - Order doesn't matter (all patterns are checked)
|
|
20
|
+
*
|
|
21
|
+
* v1.2.0 - Added 40+ new patterns for 2025-2026 services
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const SECRET_PATTERNS = [
|
|
25
|
+
// =========================================================================
|
|
26
|
+
// CRITICAL: These are almost always real secrets
|
|
27
|
+
// =========================================================================
|
|
28
|
+
{
|
|
29
|
+
name: 'AWS Access Key ID',
|
|
30
|
+
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
31
|
+
severity: 'critical',
|
|
32
|
+
description: 'AWS Access Keys can access your entire AWS account. Rotate immediately if exposed.'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'AWS Secret Access Key',
|
|
36
|
+
pattern: /(?:aws_secret_access_key|aws_secret_key)[\s]*[=:][\s]*["']?([A-Za-z0-9/+=]{40})["']?/gi,
|
|
37
|
+
severity: 'critical',
|
|
38
|
+
description: 'AWS Secret Keys paired with Access Keys grant full AWS access.'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'GitHub Personal Access Token',
|
|
42
|
+
pattern: /ghp_[a-zA-Z0-9]{36}/g,
|
|
43
|
+
severity: 'critical',
|
|
44
|
+
description: 'GitHub PATs can access repositories, create commits, and manage settings.'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'GitHub OAuth Token',
|
|
48
|
+
pattern: /gho_[a-zA-Z0-9]{36}/g,
|
|
49
|
+
severity: 'critical',
|
|
50
|
+
description: 'GitHub OAuth tokens grant authorized application access.'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'GitHub App Token',
|
|
54
|
+
pattern: /ghu_[a-zA-Z0-9]{36}|ghs_[a-zA-Z0-9]{36}/g,
|
|
55
|
+
severity: 'critical',
|
|
56
|
+
description: 'GitHub App tokens have installation-level access to repositories.'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'GitHub Fine-Grained PAT',
|
|
60
|
+
pattern: /github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/g,
|
|
61
|
+
severity: 'critical',
|
|
62
|
+
description: 'GitHub fine-grained PATs can access repositories with scoped permissions.'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Stripe Live Secret Key',
|
|
66
|
+
pattern: /sk_live_[a-zA-Z0-9]{24,}/g,
|
|
67
|
+
severity: 'critical',
|
|
68
|
+
description: 'Stripe live keys can process real payments and access customer data.'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'Private Key Block',
|
|
72
|
+
pattern: /-----BEGIN\s+(RSA\s+|EC\s+|DSA\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/g,
|
|
73
|
+
severity: 'critical',
|
|
74
|
+
description: 'Private keys enable impersonation and decryption. Never commit these.'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'PlanetScale Password',
|
|
78
|
+
pattern: /pscale_pw_[a-zA-Z0-9_-]{32,}/g,
|
|
79
|
+
severity: 'critical',
|
|
80
|
+
description: 'PlanetScale passwords grant database access. Keep in environment variables.'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'PlanetScale OAuth Token',
|
|
84
|
+
pattern: /pscale_oauth_[a-zA-Z0-9_-]{32,}/g,
|
|
85
|
+
severity: 'critical',
|
|
86
|
+
description: 'PlanetScale OAuth tokens can manage your database branches and schema.'
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'Clerk Secret Key',
|
|
90
|
+
pattern: /sk_live_[a-zA-Z0-9]{27,}/g,
|
|
91
|
+
severity: 'critical',
|
|
92
|
+
description: 'Clerk secret keys grant full access to your auth system. Never expose in frontend.'
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'Doppler Service Token',
|
|
96
|
+
pattern: /dp\.st\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{32,}/g,
|
|
97
|
+
severity: 'critical',
|
|
98
|
+
description: 'Doppler service tokens grant access to your secrets. Ironic if leaked!'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'HashiCorp Vault Token',
|
|
102
|
+
pattern: /hvs\.[a-zA-Z0-9_-]{24,}/g,
|
|
103
|
+
severity: 'critical',
|
|
104
|
+
description: 'HashiCorp Vault tokens grant access to your secrets.'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'Neon Database Connection String',
|
|
108
|
+
pattern: /postgres(ql)?:\/\/[^:]+:[^@]+@[^.]+\.neon\.tech/g,
|
|
109
|
+
severity: 'critical',
|
|
110
|
+
description: 'Neon Postgres connection strings contain database credentials.'
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'MongoDB Atlas Connection String',
|
|
114
|
+
pattern: /mongodb(\+srv)?:\/\/[^:]+:[^@]+@[^.]+\.mongodb\.net/g,
|
|
115
|
+
severity: 'critical',
|
|
116
|
+
description: 'MongoDB Atlas connection strings contain database credentials.'
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
// =========================================================================
|
|
120
|
+
// HIGH: AI/ML Provider Keys (2025-2026)
|
|
121
|
+
// =========================================================================
|
|
122
|
+
{
|
|
123
|
+
name: 'OpenAI API Key',
|
|
124
|
+
pattern: /sk-[a-zA-Z0-9]{20,}/g,
|
|
125
|
+
severity: 'high',
|
|
126
|
+
description: 'OpenAI keys can rack up API charges and access your usage history.'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'OpenAI Project Key',
|
|
130
|
+
pattern: /sk-proj-[a-zA-Z0-9_-]{48,}/g,
|
|
131
|
+
severity: 'high',
|
|
132
|
+
description: 'OpenAI project keys grant access to specific project resources.'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'Anthropic API Key',
|
|
136
|
+
pattern: /sk-ant-[a-zA-Z0-9_-]{32,}/g,
|
|
137
|
+
severity: 'high',
|
|
138
|
+
description: 'Anthropic API keys grant access to Claude and your usage quota.'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'Google AI (Gemini) API Key',
|
|
142
|
+
pattern: /AIzaSy[a-zA-Z0-9_-]{33}/g,
|
|
143
|
+
severity: 'high',
|
|
144
|
+
description: 'Google AI API keys grant access to Gemini and other Google AI services.'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'Replicate API Token',
|
|
148
|
+
pattern: /r8_[a-zA-Z0-9]{37}/g,
|
|
149
|
+
severity: 'high',
|
|
150
|
+
description: 'Replicate tokens can run AI models and incur charges on your account.'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'Hugging Face Token',
|
|
154
|
+
pattern: /hf_[a-zA-Z0-9]{34}/g,
|
|
155
|
+
severity: 'high',
|
|
156
|
+
description: 'Hugging Face tokens grant access to models, datasets, and Inference API.'
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'Perplexity API Key',
|
|
160
|
+
pattern: /pplx-[a-f0-9]{48}/g,
|
|
161
|
+
severity: 'high',
|
|
162
|
+
description: 'Perplexity API keys can access their search-augmented AI models.'
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'Groq API Key',
|
|
166
|
+
pattern: /gsk_[a-zA-Z0-9]{52}/g,
|
|
167
|
+
severity: 'high',
|
|
168
|
+
description: 'Groq API keys provide access to fast LLM inference.'
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'Cohere API Key',
|
|
172
|
+
pattern: /(?:cohere|COHERE)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{40})["']?/gi,
|
|
173
|
+
severity: 'high',
|
|
174
|
+
description: 'Cohere API keys grant access to their NLP models.'
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'Mistral API Key',
|
|
178
|
+
pattern: /(?:mistral|MISTRAL)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{32})["']?/gi,
|
|
179
|
+
severity: 'high',
|
|
180
|
+
description: 'Mistral AI API keys can access their language models.'
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'Together AI API Key',
|
|
184
|
+
pattern: /(?:together|TOGETHER)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{64})["']?/gi,
|
|
185
|
+
severity: 'high',
|
|
186
|
+
description: 'Together AI keys grant access to open-source model hosting.'
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// =========================================================================
|
|
190
|
+
// HIGH: Communication & Messaging
|
|
191
|
+
// =========================================================================
|
|
192
|
+
{
|
|
193
|
+
name: 'Slack Token',
|
|
194
|
+
pattern: /xox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*/g,
|
|
195
|
+
severity: 'high',
|
|
196
|
+
description: 'Slack tokens can read messages, post content, and access workspace data.'
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'Slack Webhook',
|
|
200
|
+
pattern: /https:\/\/hooks\.slack\.com\/services\/T[a-zA-Z0-9_]+\/B[a-zA-Z0-9_]+\/[a-zA-Z0-9_]+/g,
|
|
201
|
+
severity: 'high',
|
|
202
|
+
description: 'Slack webhooks allow posting messages to channels.'
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'Discord Webhook',
|
|
206
|
+
pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[a-zA-Z0-9_-]+/g,
|
|
207
|
+
severity: 'high',
|
|
208
|
+
description: 'Discord webhooks allow posting messages to channels.'
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'Discord Bot Token',
|
|
212
|
+
pattern: /[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27}/g,
|
|
213
|
+
severity: 'high',
|
|
214
|
+
description: 'Discord bot tokens grant full control over your bot.'
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: 'Telegram Bot Token',
|
|
218
|
+
pattern: /[0-9]{8,10}:[a-zA-Z0-9_-]{35}/g,
|
|
219
|
+
severity: 'high',
|
|
220
|
+
description: 'Telegram bot tokens grant full control over your bot.'
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// =========================================================================
|
|
224
|
+
// HIGH: Email Services
|
|
225
|
+
// =========================================================================
|
|
226
|
+
{
|
|
227
|
+
name: 'SendGrid API Key',
|
|
228
|
+
pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
|
|
229
|
+
severity: 'high',
|
|
230
|
+
description: 'SendGrid keys can send emails from your account.'
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'Mailgun API Key',
|
|
234
|
+
pattern: /key-[a-zA-Z0-9]{32}/g,
|
|
235
|
+
severity: 'high',
|
|
236
|
+
description: 'Mailgun keys can send emails and access logs.'
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'Resend API Key',
|
|
240
|
+
pattern: /re_[a-zA-Z0-9]{32,}/g,
|
|
241
|
+
severity: 'high',
|
|
242
|
+
description: 'Resend API keys can send emails from your account and access logs.'
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'Postmark Server Token',
|
|
246
|
+
pattern: /(?:postmark|POSTMARK)[_-]?(?:server[_-]?)?token["']?\s*[:=]\s*["']?([a-f0-9-]{36})["']?/gi,
|
|
247
|
+
severity: 'high',
|
|
248
|
+
description: 'Postmark tokens can send emails from your account.'
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'Mailchimp API Key',
|
|
252
|
+
pattern: /[a-f0-9]{32}-us[0-9]{1,2}/g,
|
|
253
|
+
severity: 'high',
|
|
254
|
+
description: 'Mailchimp API keys can access your audience and send campaigns.'
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
// =========================================================================
|
|
258
|
+
// HIGH: SMS & Phone
|
|
259
|
+
// =========================================================================
|
|
260
|
+
{
|
|
261
|
+
name: 'Twilio API Key',
|
|
262
|
+
pattern: /SK[a-f0-9]{32}/g,
|
|
263
|
+
severity: 'high',
|
|
264
|
+
description: 'Twilio keys can send SMS/calls and access account data.'
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: 'Twilio Account SID',
|
|
268
|
+
pattern: /AC[a-f0-9]{32}/g,
|
|
269
|
+
severity: 'medium',
|
|
270
|
+
description: 'Twilio Account SIDs identify your account. Usually paired with auth token.'
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// =========================================================================
|
|
274
|
+
// HIGH: Databases & Backend Services
|
|
275
|
+
// =========================================================================
|
|
276
|
+
{
|
|
277
|
+
name: 'Firebase/Google Service Account',
|
|
278
|
+
pattern: /"type":\s*"service_account"/g,
|
|
279
|
+
severity: 'high',
|
|
280
|
+
description: 'Service account JSON files grant broad GCP/Firebase access.'
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: 'Supabase Service Role Key',
|
|
284
|
+
pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
|
|
285
|
+
severity: 'high',
|
|
286
|
+
description: 'Supabase service role keys bypass Row Level Security. Keep server-side only.'
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'Upstash Redis REST Token',
|
|
290
|
+
pattern: /AX[a-zA-Z0-9]{34,}/g,
|
|
291
|
+
severity: 'high',
|
|
292
|
+
description: 'Upstash Redis tokens grant access to your serverless Redis database.'
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'Upstash QStash Token',
|
|
296
|
+
pattern: /qstash_[a-zA-Z0-9]{32,}/g,
|
|
297
|
+
severity: 'high',
|
|
298
|
+
description: 'Upstash QStash tokens can schedule and manage message queues.'
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: 'Turso Database URL',
|
|
302
|
+
pattern: /libsql:\/\/[^.]+\.turso\.io/g,
|
|
303
|
+
severity: 'high',
|
|
304
|
+
description: 'Turso database URLs. Check for embedded auth tokens in full connection string.'
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: 'Convex Deployment URL',
|
|
308
|
+
pattern: /https:\/\/[a-z]+-[a-z]+-[0-9]+\.convex\.cloud/g,
|
|
309
|
+
severity: 'medium',
|
|
310
|
+
description: 'Convex deployment URLs identify your backend. Check for paired secrets.'
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
// =========================================================================
|
|
314
|
+
// HIGH: Hosting & Deployment
|
|
315
|
+
// =========================================================================
|
|
316
|
+
{
|
|
317
|
+
name: 'Vercel Token',
|
|
318
|
+
pattern: /vercel_[a-zA-Z0-9]{24}/gi,
|
|
319
|
+
severity: 'high',
|
|
320
|
+
description: 'Vercel tokens can deploy and manage your projects.'
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'NPM Token',
|
|
324
|
+
pattern: /npm_[a-zA-Z0-9]{36}/g,
|
|
325
|
+
severity: 'high',
|
|
326
|
+
description: 'NPM tokens can publish packages under your account.'
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: 'Heroku API Key',
|
|
330
|
+
pattern: /[hH]eroku.*[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g,
|
|
331
|
+
severity: 'high',
|
|
332
|
+
description: 'Heroku API keys can manage apps and dynos.'
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: 'DigitalOcean Token',
|
|
336
|
+
pattern: /dop_v1_[a-f0-9]{64}/g,
|
|
337
|
+
severity: 'high',
|
|
338
|
+
description: 'DigitalOcean tokens can manage droplets and resources.'
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'Render API Key',
|
|
342
|
+
pattern: /rnd_[a-zA-Z0-9]{32,}/g,
|
|
343
|
+
severity: 'high',
|
|
344
|
+
description: 'Render API keys can manage your services and deployments.'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'Fly.io Token',
|
|
348
|
+
pattern: /FlyV1\s+[a-zA-Z0-9_-]{43}/g,
|
|
349
|
+
severity: 'high',
|
|
350
|
+
description: 'Fly.io tokens can deploy and manage your applications.'
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'Railway Token',
|
|
354
|
+
pattern: /(?:railway|RAILWAY)[_-]?token["']?\s*[:=]\s*["']?([a-f0-9-]{36})["']?/gi,
|
|
355
|
+
severity: 'high',
|
|
356
|
+
description: 'Railway API tokens can manage your services.'
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'Netlify Personal Access Token',
|
|
360
|
+
pattern: /nfp_[a-zA-Z0-9]{40}/g,
|
|
361
|
+
severity: 'high',
|
|
362
|
+
description: 'Netlify PATs can manage sites and deploys.'
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: 'Cloudflare API Token',
|
|
366
|
+
pattern: /(?:cloudflare|CF)[_-]?(?:api[_-]?)?token["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{40})["']?/gi,
|
|
367
|
+
severity: 'high',
|
|
368
|
+
description: 'Cloudflare API tokens can manage DNS, workers, and other services.'
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
// =========================================================================
|
|
372
|
+
// HIGH: Auth Providers
|
|
373
|
+
// =========================================================================
|
|
374
|
+
{
|
|
375
|
+
name: 'Clerk Publishable Key (Live)',
|
|
376
|
+
pattern: /pk_live_[a-zA-Z0-9]{27,}/g,
|
|
377
|
+
severity: 'medium',
|
|
378
|
+
description: 'Clerk publishable keys are meant for frontend but verify it\'s intentional.'
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: 'Clerk Test Secret Key',
|
|
382
|
+
pattern: /sk_test_[a-zA-Z0-9]{27,}/g,
|
|
383
|
+
severity: 'medium',
|
|
384
|
+
description: 'Clerk test keys are lower risk but should still be in environment variables.'
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: 'Auth0 Domain with Credentials',
|
|
388
|
+
pattern: /https:\/\/[^.]+\.auth0\.com.*client_secret/gi,
|
|
389
|
+
severity: 'critical',
|
|
390
|
+
description: 'Auth0 URLs with embedded client secrets should never be in code.'
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'Supabase Anon Key in Code',
|
|
394
|
+
pattern: /(?:supabase|SUPABASE)[_-]?(?:anon[_-]?)?key["']?\s*[:=]\s*["']?(eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)["']?/gi,
|
|
395
|
+
severity: 'medium',
|
|
396
|
+
description: 'Supabase anon keys. Safe for frontend but verify RLS is enabled.'
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
name: 'Stytch Secret Key',
|
|
400
|
+
pattern: /secret-(?:live|test)-[a-zA-Z0-9]{30,}/g,
|
|
401
|
+
severity: 'critical',
|
|
402
|
+
description: 'Stytch secret keys grant full access to your authentication system.'
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: 'Okta API Token',
|
|
406
|
+
pattern: /(?:okta|OKTA)[_-]?(?:api[_-]?)?token["']?\s*[:=]\s*["']?(00[a-zA-Z0-9_-]{38})["']?/gi,
|
|
407
|
+
severity: 'high',
|
|
408
|
+
description: 'Okta API tokens can manage your identity provider and user directory.'
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
// =========================================================================
|
|
412
|
+
// CRITICAL: Additional Cloud/Infra
|
|
413
|
+
// =========================================================================
|
|
414
|
+
{
|
|
415
|
+
name: 'Azure Storage Connection String',
|
|
416
|
+
pattern: /DefaultEndpointsProtocol=https;AccountName=[^;]{1,50};AccountKey=[a-zA-Z0-9+/=]{44,}/g,
|
|
417
|
+
severity: 'critical',
|
|
418
|
+
description: 'Azure Storage connection strings contain account keys with full storage access.'
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: 'AWS Session Token',
|
|
422
|
+
pattern: /(?:aws_session_token|aws_security_token)[\s]*[=:][\s]*["']?([A-Za-z0-9/+=]{100,})["']?/gi,
|
|
423
|
+
severity: 'critical',
|
|
424
|
+
description: 'AWS session tokens are temporary credentials that still grant account access.'
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: 'PlanetScale Service Token',
|
|
428
|
+
pattern: /pscale_tkn_[a-zA-Z0-9_-]{32,}/g,
|
|
429
|
+
severity: 'critical',
|
|
430
|
+
description: 'PlanetScale service tokens grant programmatic database branch access.'
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: 'Shopify Admin API Access Token',
|
|
434
|
+
pattern: /shpat_[a-fA-F0-9]{32}/g,
|
|
435
|
+
severity: 'critical',
|
|
436
|
+
description: 'Shopify admin tokens grant full store management access including orders and customers.'
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: 'Shopify Custom App Access Token',
|
|
440
|
+
pattern: /shpca_[a-fA-F0-9]{32}/g,
|
|
441
|
+
severity: 'critical',
|
|
442
|
+
description: 'Shopify custom app tokens provide scoped store admin access.'
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
// =========================================================================
|
|
446
|
+
// HIGH: Productivity & SaaS
|
|
447
|
+
// =========================================================================
|
|
448
|
+
{
|
|
449
|
+
name: 'Linear API Key',
|
|
450
|
+
pattern: /lin_api_[a-zA-Z0-9]{40}/g,
|
|
451
|
+
severity: 'high',
|
|
452
|
+
description: 'Linear API keys can access your project management data.'
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: 'Notion API Key',
|
|
456
|
+
pattern: /secret_[a-zA-Z0-9]{43}/g,
|
|
457
|
+
severity: 'high',
|
|
458
|
+
description: 'Notion API keys can access and modify your workspace content.'
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: 'Airtable API Key',
|
|
462
|
+
pattern: /pat[a-zA-Z0-9]{14}\.[a-f0-9]{64}/g,
|
|
463
|
+
severity: 'high',
|
|
464
|
+
description: 'Airtable personal access tokens grant access to your bases.'
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: 'Figma Personal Access Token',
|
|
468
|
+
pattern: /figd_[a-zA-Z0-9_-]{40,}/g,
|
|
469
|
+
severity: 'high',
|
|
470
|
+
description: 'Figma PATs can access your design files and projects.'
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
// =========================================================================
|
|
474
|
+
// HIGH: AI/ML Providers (2025-2026 additions)
|
|
475
|
+
// =========================================================================
|
|
476
|
+
{
|
|
477
|
+
name: 'xAI (Grok) API Key',
|
|
478
|
+
pattern: /xai-[A-Za-z0-9]{52,}/g,
|
|
479
|
+
severity: 'high',
|
|
480
|
+
description: 'xAI API keys grant access to Grok models and incur usage charges on your account.'
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: 'Tavily API Key',
|
|
484
|
+
pattern: /tvly-[a-zA-Z0-9]{32,}/g,
|
|
485
|
+
severity: 'high',
|
|
486
|
+
description: 'Tavily API keys grant access to their AI-powered search service.'
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: 'Cerebras API Key',
|
|
490
|
+
pattern: /csk-[a-zA-Z0-9]{48,}/g,
|
|
491
|
+
severity: 'high',
|
|
492
|
+
description: 'Cerebras API keys provide access to fast AI inference.'
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: 'Pinecone API Key',
|
|
496
|
+
pattern: /pcsk_[a-zA-Z0-9]{47}_[a-zA-Z0-9]{47}/g,
|
|
497
|
+
severity: 'high',
|
|
498
|
+
description: 'Pinecone API keys grant access to your vector database indexes.'
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'ElevenLabs API Key',
|
|
502
|
+
pattern: /(?:elevenlabs|ELEVENLABS)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-fA-F0-9]{32})["']?/gi,
|
|
503
|
+
severity: 'high',
|
|
504
|
+
description: 'ElevenLabs API keys grant access to their voice AI cloning and synthesis service.'
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: 'DeepSeek API Key',
|
|
508
|
+
pattern: /(?:deepseek|DEEPSEEK)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?(sk-[a-zA-Z0-9]{32,})["']?/gi,
|
|
509
|
+
severity: 'high',
|
|
510
|
+
description: 'DeepSeek API keys grant access to their language models.'
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: 'Voyage AI API Key',
|
|
514
|
+
pattern: /(?:voyage|VOYAGE)[_-]?(?:ai[_-]?)?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{32,})["']?/gi,
|
|
515
|
+
severity: 'high',
|
|
516
|
+
requiresEntropyCheck: true,
|
|
517
|
+
description: 'Voyage AI API keys provide access to their embedding and reranking models.'
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
name: 'Fireworks AI API Key',
|
|
521
|
+
pattern: /(?:fireworks|FIREWORKS)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{32,})["']?/gi,
|
|
522
|
+
severity: 'high',
|
|
523
|
+
requiresEntropyCheck: true,
|
|
524
|
+
description: 'Fireworks AI API keys grant access to fast open-source model inference.'
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: 'Anyscale API Key',
|
|
528
|
+
pattern: /esecret_[a-zA-Z0-9]{32,}/g,
|
|
529
|
+
severity: 'high',
|
|
530
|
+
description: 'Anyscale API keys grant access to their managed Ray and LLM endpoints.'
|
|
531
|
+
},
|
|
532
|
+
|
|
533
|
+
// =========================================================================
|
|
534
|
+
// HIGH: Payments (Additional)
|
|
535
|
+
// =========================================================================
|
|
536
|
+
{
|
|
537
|
+
name: 'Stripe Test Secret Key',
|
|
538
|
+
pattern: /sk_test_[a-zA-Z0-9]{24,}/g,
|
|
539
|
+
severity: 'medium',
|
|
540
|
+
description: 'Stripe test keys are lower risk but should still be in environment variables.'
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: 'Stripe Live Publishable Key',
|
|
544
|
+
pattern: /pk_live_[a-zA-Z0-9]{24,}/g,
|
|
545
|
+
severity: 'medium',
|
|
546
|
+
description: 'Stripe publishable keys are meant for frontend but verify it\'s intentional.'
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: 'Stripe Webhook Secret',
|
|
550
|
+
pattern: /whsec_[a-zA-Z0-9]{32,}/g,
|
|
551
|
+
severity: 'high',
|
|
552
|
+
description: 'Stripe webhook secrets validate incoming webhooks. Keep server-side only.'
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: 'Lemon Squeezy API Key',
|
|
556
|
+
pattern: /(?:lemon|LEMON)[_-]?(?:squeezy|SQUEEZY)?[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9-]{36})["']?/gi,
|
|
557
|
+
severity: 'high',
|
|
558
|
+
description: 'Lemon Squeezy API keys can manage your store and orders.'
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: 'Paddle API Key',
|
|
562
|
+
pattern: /(?:paddle|PADDLE)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{64})["']?/gi,
|
|
563
|
+
severity: 'high',
|
|
564
|
+
description: 'Paddle API keys can manage your subscriptions and payments.'
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
name: 'Stripe Restricted Key',
|
|
568
|
+
pattern: /rk_(?:live|test)_[a-zA-Z0-9]{24,}/g,
|
|
569
|
+
severity: 'high',
|
|
570
|
+
description: 'Stripe restricted keys have scoped permissions but still grant API access.'
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: 'Square Access Token',
|
|
574
|
+
pattern: /EAAAl[0-9a-zA-Z_-]{50,}/g,
|
|
575
|
+
severity: 'high',
|
|
576
|
+
description: 'Square access tokens can process payments and manage store data.'
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: 'Square OAuth Token',
|
|
580
|
+
pattern: /sq0[a-z]tp-[a-zA-Z0-9_-]{22}/g,
|
|
581
|
+
severity: 'high',
|
|
582
|
+
description: 'Square OAuth tokens authorize Square API access for a merchant account.'
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: 'Shopify Shared Secret',
|
|
586
|
+
pattern: /shpss_[a-fA-F0-9]{32}/g,
|
|
587
|
+
severity: 'high',
|
|
588
|
+
description: 'Shopify shared secrets validate webhook payload signatures.'
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: 'Braintree Access Token',
|
|
592
|
+
pattern: /access_token\$(?:production|sandbox)\$[a-z0-9]{16}\$[a-f0-9]{32}/g,
|
|
593
|
+
severity: 'critical',
|
|
594
|
+
description: 'Braintree access tokens grant full payment processing access.'
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
// =========================================================================
|
|
598
|
+
// HIGH: Realtime & Messaging
|
|
599
|
+
// =========================================================================
|
|
600
|
+
{
|
|
601
|
+
name: 'Pusher App Secret',
|
|
602
|
+
pattern: /(?:pusher|PUSHER)[_-]?(?:app[_-]?)?secret["']?\s*[:=]\s*["']?([a-f0-9]{32})["']?/gi,
|
|
603
|
+
severity: 'high',
|
|
604
|
+
description: 'Pusher app secrets authenticate private and presence channel subscriptions.'
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
name: 'Ably API Key',
|
|
608
|
+
pattern: /(?:ably|ABLY)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{8}\.[a-zA-Z0-9_-]{6}:[a-zA-Z0-9+/=_-]{43,})["']?/gi,
|
|
609
|
+
severity: 'high',
|
|
610
|
+
description: 'Ably API keys grant full publish and subscribe access to your realtime channels.'
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: 'Mapbox Access Token',
|
|
614
|
+
pattern: /pk\.eyJ1[a-zA-Z0-9._-]{40,}/g,
|
|
615
|
+
severity: 'medium',
|
|
616
|
+
description: 'Mapbox tokens can incur charges if abused. Restrict token scope and allowed URLs.'
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
// =========================================================================
|
|
620
|
+
// HIGH: DevOps & CI/CD
|
|
621
|
+
// =========================================================================
|
|
622
|
+
{
|
|
623
|
+
name: 'CircleCI Personal API Token',
|
|
624
|
+
pattern: /CCIPAT_[a-zA-Z0-9]{40,}/g,
|
|
625
|
+
severity: 'high',
|
|
626
|
+
description: 'CircleCI API tokens can trigger builds, read logs, and access pipeline data.'
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
name: 'Sentry Auth Token',
|
|
630
|
+
pattern: /sntrys_[a-zA-Z0-9_]{64,}/g,
|
|
631
|
+
severity: 'high',
|
|
632
|
+
description: 'Sentry auth tokens provide full API access to your error data and project settings.'
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
name: 'Terraform Cloud Token',
|
|
636
|
+
pattern: /(?:terraform|TFC)[_-]?(?:api[_-]?)?token["']?\s*[:=]\s*["']?([a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9_-]{67,})["']?/gi,
|
|
637
|
+
severity: 'high',
|
|
638
|
+
description: 'Terraform Cloud tokens can read and apply infrastructure state.'
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: 'Cloudinary API Secret',
|
|
642
|
+
pattern: /(?:cloudinary|CLOUDINARY)[_-]?(?:api[_-]?)?secret["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{27,})["']?/gi,
|
|
643
|
+
severity: 'high',
|
|
644
|
+
requiresEntropyCheck: true,
|
|
645
|
+
description: 'Cloudinary API secrets grant access to your media library and transformations.'
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: 'Algolia Admin API Key',
|
|
649
|
+
pattern: /(?:algolia|ALGOLIA)[_-]?(?:admin[_-]?)?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{32})["']?/gi,
|
|
650
|
+
severity: 'high',
|
|
651
|
+
description: 'Algolia admin keys can modify indices and change search configuration.'
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: 'LaunchDarkly SDK Key',
|
|
655
|
+
pattern: /sdk-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g,
|
|
656
|
+
severity: 'high',
|
|
657
|
+
description: 'LaunchDarkly SDK keys can read all feature flags and user data.'
|
|
658
|
+
},
|
|
659
|
+
|
|
660
|
+
// =========================================================================
|
|
661
|
+
// HIGH: Analytics & Monitoring
|
|
662
|
+
// =========================================================================
|
|
663
|
+
{
|
|
664
|
+
name: 'Sentry DSN',
|
|
665
|
+
pattern: /https:\/\/[a-f0-9]{32}@[a-z0-9]+\.ingest\.sentry\.io\/[0-9]+/g,
|
|
666
|
+
severity: 'medium',
|
|
667
|
+
description: 'Sentry DSNs are semi-public but contain project identifiers.'
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: 'PostHog API Key',
|
|
671
|
+
pattern: /phc_[a-zA-Z0-9]{32,}/g,
|
|
672
|
+
severity: 'medium',
|
|
673
|
+
description: 'PostHog project API keys. Usually safe in frontend but verify.'
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
name: 'New Relic API Key',
|
|
677
|
+
pattern: /NRAK-[A-Z0-9]{27}/g,
|
|
678
|
+
severity: 'high',
|
|
679
|
+
description: 'New Relic API keys can access your monitoring data and configurations.'
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
name: 'Datadog API Key',
|
|
683
|
+
pattern: /(?:datadog|DD)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{32})["']?/gi,
|
|
684
|
+
severity: 'high',
|
|
685
|
+
description: 'Datadog API keys can access and send monitoring data.'
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
// =========================================================================
|
|
689
|
+
// MEDIUM: Generic patterns (entropy-checked to reduce false positives)
|
|
690
|
+
// requiresEntropyCheck: true → value is scored before reporting
|
|
691
|
+
// =========================================================================
|
|
692
|
+
{
|
|
693
|
+
name: 'Generic API Key Assignment',
|
|
694
|
+
pattern: /["']?(?:api[_-]?key|apikey)["']?\s*[:=]\s*["']([a-zA-Z0-9_\-]{20,})["']/gi,
|
|
695
|
+
severity: 'medium',
|
|
696
|
+
requiresEntropyCheck: true,
|
|
697
|
+
description: 'Hardcoded API keys should be moved to environment variables.'
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: 'Generic Secret Assignment',
|
|
701
|
+
pattern: /["']?(?:secret|secret[_-]?key)["']?\s*[:=]\s*["']([a-zA-Z0-9_\-]{20,})["']/gi,
|
|
702
|
+
severity: 'medium',
|
|
703
|
+
requiresEntropyCheck: true,
|
|
704
|
+
description: 'Hardcoded secrets should be moved to environment variables.'
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: 'Password Assignment',
|
|
708
|
+
pattern: /["']?password["']?\s*[:=]\s*["']([^"']{8,})["']/gi,
|
|
709
|
+
severity: 'medium',
|
|
710
|
+
requiresEntropyCheck: true,
|
|
711
|
+
description: 'Hardcoded passwords are a critical vulnerability.'
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: 'Database URL with Credentials',
|
|
715
|
+
pattern: /(mongodb|postgres|postgresql|mysql|redis):\/\/[^:]+:[^@]+@[^\s"']+/gi,
|
|
716
|
+
severity: 'medium',
|
|
717
|
+
requiresEntropyCheck: true,
|
|
718
|
+
description: 'Database URLs with embedded passwords expose your database.'
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
name: 'Bearer Token in Code',
|
|
722
|
+
pattern: /["']Bearer\s+[a-zA-Z0-9_\-\.=]{20,}["']/gi,
|
|
723
|
+
severity: 'medium',
|
|
724
|
+
requiresEntropyCheck: true,
|
|
725
|
+
description: 'Hardcoded bearer tokens should not be in source code.'
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
name: 'Basic Auth Header',
|
|
729
|
+
pattern: /["']Basic\s+[A-Za-z0-9+/=]{20,}["']/gi,
|
|
730
|
+
severity: 'medium',
|
|
731
|
+
requiresEntropyCheck: true,
|
|
732
|
+
description: 'Basic auth headers contain base64-encoded credentials.'
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: 'Private Key in Environment Variable',
|
|
736
|
+
pattern: /PRIVATE[_-]?KEY["']?\s*[:=]\s*["']([^"']+)["']/gi,
|
|
737
|
+
severity: 'high',
|
|
738
|
+
requiresEntropyCheck: true,
|
|
739
|
+
description: 'Private keys should be loaded from files, not hardcoded.'
|
|
740
|
+
}
|
|
741
|
+
];
|
|
742
|
+
|
|
743
|
+
// =============================================================================
|
|
744
|
+
// FILES AND DIRECTORIES TO SKIP
|
|
745
|
+
// =============================================================================
|
|
746
|
+
|
|
747
|
+
export const SKIP_DIRS = new Set([
|
|
748
|
+
'node_modules',
|
|
749
|
+
'.git',
|
|
750
|
+
'venv',
|
|
751
|
+
'env',
|
|
752
|
+
'.venv',
|
|
753
|
+
'__pycache__',
|
|
754
|
+
'.next',
|
|
755
|
+
'.nuxt',
|
|
756
|
+
'dist',
|
|
757
|
+
'build',
|
|
758
|
+
'out',
|
|
759
|
+
'.output',
|
|
760
|
+
'coverage',
|
|
761
|
+
'.nyc_output',
|
|
762
|
+
'vendor',
|
|
763
|
+
'.bundle',
|
|
764
|
+
'.cache',
|
|
765
|
+
'.parcel-cache',
|
|
766
|
+
'.turbo',
|
|
767
|
+
'bower_components',
|
|
768
|
+
'jspm_packages',
|
|
769
|
+
'.vercel',
|
|
770
|
+
'.netlify',
|
|
771
|
+
'.serverless',
|
|
772
|
+
// Additional build/tooling output
|
|
773
|
+
'.yarn',
|
|
774
|
+
'storybook-static',
|
|
775
|
+
'playwright-report',
|
|
776
|
+
'.playwright',
|
|
777
|
+
'.gradle',
|
|
778
|
+
'target', // Maven/Gradle build output
|
|
779
|
+
'.pytest_cache',
|
|
780
|
+
'.mypy_cache',
|
|
781
|
+
'.ruff_cache',
|
|
782
|
+
'.tox',
|
|
783
|
+
'site-packages',
|
|
784
|
+
'.pnpm',
|
|
785
|
+
'jspm_packages',
|
|
786
|
+
'.expo',
|
|
787
|
+
'.docusaurus',
|
|
788
|
+
'.storybook',
|
|
789
|
+
'.ship-safe',
|
|
790
|
+
]);
|
|
791
|
+
|
|
792
|
+
export const SKIP_EXTENSIONS = new Set([
|
|
793
|
+
// Images
|
|
794
|
+
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.webp', '.bmp', '.tiff',
|
|
795
|
+
// Fonts
|
|
796
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
797
|
+
// Media
|
|
798
|
+
'.mp3', '.mp4', '.wav', '.avi', '.mov', '.webm', '.ogg',
|
|
799
|
+
// Archives
|
|
800
|
+
'.zip', '.tar', '.gz', '.rar', '.7z', '.bz2',
|
|
801
|
+
// Documents
|
|
802
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
803
|
+
// Lock files (usually very large and auto-generated)
|
|
804
|
+
'.lock',
|
|
805
|
+
// Minified files
|
|
806
|
+
'.min.js', '.min.css',
|
|
807
|
+
// Binaries
|
|
808
|
+
'.exe', '.dll', '.so', '.dylib', '.bin', '.o', '.a',
|
|
809
|
+
// Maps
|
|
810
|
+
'.map'
|
|
811
|
+
]);
|
|
812
|
+
|
|
813
|
+
// Auto-generated lockfiles — large, no real secrets, cause false positives
|
|
814
|
+
export const SKIP_FILENAMES = new Set([
|
|
815
|
+
'package-lock.json',
|
|
816
|
+
'pnpm-lock.yaml',
|
|
817
|
+
'composer.lock',
|
|
818
|
+
'Gemfile.lock',
|
|
819
|
+
'Pipfile.lock',
|
|
820
|
+
'poetry.lock',
|
|
821
|
+
'cargo.lock',
|
|
822
|
+
'pubspec.lock',
|
|
823
|
+
'go.sum',
|
|
824
|
+
'flake.lock',
|
|
825
|
+
// Skip ship-safe's own output files to avoid scanning the report
|
|
826
|
+
'ship-safe-report.html',
|
|
827
|
+
'ship-safe-report.pdf',
|
|
828
|
+
]);
|
|
829
|
+
|
|
830
|
+
// Maximum file size to scan (1MB)
|
|
831
|
+
export const MAX_FILE_SIZE = 1_000_000;
|
|
832
|
+
|
|
833
|
+
// =============================================================================
|
|
834
|
+
// .GITIGNORE LOADING
|
|
835
|
+
// =============================================================================
|
|
836
|
+
|
|
837
|
+
// Gitignore patterns that should NEVER be skipped by a security scanner.
|
|
838
|
+
// These files are gitignored precisely because they contain secrets or
|
|
839
|
+
// sensitive config — which is exactly what we want to detect.
|
|
840
|
+
const SECURITY_SENSITIVE_PATTERNS = new Set([
|
|
841
|
+
'.env',
|
|
842
|
+
'.env.local',
|
|
843
|
+
'.env.development',
|
|
844
|
+
'.env.development.local',
|
|
845
|
+
'.env.test',
|
|
846
|
+
'.env.test.local',
|
|
847
|
+
'.env.production',
|
|
848
|
+
'.env.production.local',
|
|
849
|
+
'.env.staging',
|
|
850
|
+
'*.pem',
|
|
851
|
+
'*.key',
|
|
852
|
+
'*.p12',
|
|
853
|
+
'*.pfx',
|
|
854
|
+
'*.jks',
|
|
855
|
+
'*.keystore',
|
|
856
|
+
'*.crt',
|
|
857
|
+
'*.cer',
|
|
858
|
+
'credentials.json',
|
|
859
|
+
'service-account.json',
|
|
860
|
+
'serviceAccountKey.json',
|
|
861
|
+
'*.secret',
|
|
862
|
+
'htpasswd',
|
|
863
|
+
'.htpasswd',
|
|
864
|
+
'id_rsa',
|
|
865
|
+
'id_ed25519',
|
|
866
|
+
'*.sqlite',
|
|
867
|
+
'*.db',
|
|
868
|
+
]);
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Load patterns from .gitignore file in the project root.
|
|
872
|
+
* Returns an array of glob-compatible ignore patterns.
|
|
873
|
+
*
|
|
874
|
+
* Smart filtering: skips gitignored build output, caches, and vendor dirs,
|
|
875
|
+
* but ALWAYS scans security-sensitive files (.env, *.key, *.pem, etc.)
|
|
876
|
+
* even if they appear in .gitignore.
|
|
877
|
+
*/
|
|
878
|
+
export function loadGitignorePatterns(rootPath) {
|
|
879
|
+
const gitignorePath = path.join(rootPath, '.gitignore');
|
|
880
|
+
try {
|
|
881
|
+
if (!fs.existsSync(gitignorePath)) return [];
|
|
882
|
+
return fs.readFileSync(gitignorePath, 'utf-8')
|
|
883
|
+
.split('\n')
|
|
884
|
+
.map(l => l.trim())
|
|
885
|
+
.filter(l => l && !l.startsWith('#') && !l.startsWith('!'))
|
|
886
|
+
.filter(p => !isSecuritySensitive(p))
|
|
887
|
+
.map(p => {
|
|
888
|
+
// Convert .gitignore patterns to fast-glob ignore patterns
|
|
889
|
+
if (p.startsWith('/')) {
|
|
890
|
+
// Rooted pattern: /build → build/**
|
|
891
|
+
return p.slice(1) + (p.endsWith('/') ? '**' : '');
|
|
892
|
+
}
|
|
893
|
+
if (p.endsWith('/')) {
|
|
894
|
+
// Directory pattern: logs/ → **/logs/**
|
|
895
|
+
return `**/${p}**`;
|
|
896
|
+
}
|
|
897
|
+
// General pattern: *.log → **/*.log, dist → **/dist, **/dist/**
|
|
898
|
+
if (!p.includes('/') && !p.includes('*')) {
|
|
899
|
+
return [`**/${p}`, `**/${p}/**`];
|
|
900
|
+
}
|
|
901
|
+
return `**/${p}`;
|
|
902
|
+
})
|
|
903
|
+
.flat();
|
|
904
|
+
} catch {
|
|
905
|
+
return [];
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Check if a .gitignore pattern targets security-sensitive files.
|
|
911
|
+
* These should always be scanned regardless of .gitignore.
|
|
912
|
+
*/
|
|
913
|
+
function isSecuritySensitive(pattern) {
|
|
914
|
+
const cleaned = pattern.replace(/^\//, '').replace(/\/$/, '');
|
|
915
|
+
if (SECURITY_SENSITIVE_PATTERNS.has(cleaned)) return true;
|
|
916
|
+
// Check wildcard patterns like *.pem, *.key
|
|
917
|
+
for (const sensitive of SECURITY_SENSITIVE_PATTERNS) {
|
|
918
|
+
if (sensitive.startsWith('*') && cleaned.endsWith(sensitive.slice(1))) return true;
|
|
919
|
+
if (cleaned === sensitive || cleaned.endsWith('/' + sensitive)) return true;
|
|
920
|
+
}
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// =============================================================================
|
|
925
|
+
// SECURITY VULNERABILITY PATTERNS
|
|
926
|
+
// =============================================================================
|
|
927
|
+
//
|
|
928
|
+
// These patterns detect insecure code patterns (OWASP Top 10, misconfigs, etc.)
|
|
929
|
+
// They are distinct from SECRET_PATTERNS:
|
|
930
|
+
// - Secrets → move to env vars, rotate if exposed
|
|
931
|
+
// - Vulns → fix the code pattern, can't just rotate
|
|
932
|
+
//
|
|
933
|
+
// Each pattern includes category: 'vulnerability' to separate output sections.
|
|
934
|
+
|
|
935
|
+
export const SECURITY_PATTERNS = [
|
|
936
|
+
|
|
937
|
+
// =========================================================================
|
|
938
|
+
// XSS — Cross-Site Scripting
|
|
939
|
+
// =========================================================================
|
|
940
|
+
{
|
|
941
|
+
name: 'XSS: dangerouslySetInnerHTML',
|
|
942
|
+
pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{/g,
|
|
943
|
+
severity: 'high',
|
|
944
|
+
category: 'vulnerability',
|
|
945
|
+
description: 'dangerouslySetInnerHTML can introduce XSS if the value contains user input. Sanitize with DOMPurify or restructure to avoid it.'
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
name: 'XSS: innerHTML Assignment',
|
|
949
|
+
pattern: /\.innerHTML\s*=/g,
|
|
950
|
+
severity: 'medium',
|
|
951
|
+
category: 'vulnerability',
|
|
952
|
+
description: 'innerHTML set to user-controlled data leads to XSS. Use textContent for plain text or DOMPurify to sanitize HTML.'
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
name: 'XSS: document.write',
|
|
956
|
+
pattern: /\bdocument\.write\s*\(/g,
|
|
957
|
+
severity: 'medium',
|
|
958
|
+
category: 'vulnerability',
|
|
959
|
+
description: 'document.write() is deprecated and can introduce XSS. Use DOM manipulation (createElement, appendChild) instead.'
|
|
960
|
+
},
|
|
961
|
+
|
|
962
|
+
// =========================================================================
|
|
963
|
+
// Code Injection
|
|
964
|
+
// =========================================================================
|
|
965
|
+
{
|
|
966
|
+
name: 'Code Injection: eval()',
|
|
967
|
+
pattern: /\beval\s*\(/g,
|
|
968
|
+
severity: 'high',
|
|
969
|
+
category: 'vulnerability',
|
|
970
|
+
description: 'eval() executes arbitrary JavaScript and is a serious attack vector. Replace with JSON.parse(), Function calls, or safer alternatives.'
|
|
971
|
+
},
|
|
972
|
+
{
|
|
973
|
+
name: 'Code Injection: new Function()',
|
|
974
|
+
pattern: /\bnew\s+Function\s*\(/g,
|
|
975
|
+
severity: 'high',
|
|
976
|
+
category: 'vulnerability',
|
|
977
|
+
description: 'new Function() is functionally equivalent to eval() and can execute arbitrary code. Avoid dynamic code generation.'
|
|
978
|
+
},
|
|
979
|
+
|
|
980
|
+
// =========================================================================
|
|
981
|
+
// SQL Injection
|
|
982
|
+
// =========================================================================
|
|
983
|
+
{
|
|
984
|
+
name: 'SQL Injection: Template Literal Query',
|
|
985
|
+
pattern: /`(?:SELECT|INSERT|UPDATE|DELETE|DROP\s+TABLE|ALTER\s+TABLE)[^`]*\$\{/gi,
|
|
986
|
+
severity: 'critical',
|
|
987
|
+
category: 'vulnerability',
|
|
988
|
+
description: 'SQL queries with interpolated template variables are vulnerable to injection. Use parameterized queries or a query builder.'
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
name: 'SQL Injection: String Concatenation Query',
|
|
992
|
+
pattern: /["'](?:SELECT|INSERT|UPDATE|DELETE)\s+[^"']{4,}["']\s*\+/gi,
|
|
993
|
+
severity: 'high',
|
|
994
|
+
category: 'vulnerability',
|
|
995
|
+
description: 'Building SQL with string concatenation is vulnerable to SQL injection. Use parameterized queries (?, $1) or an ORM.'
|
|
996
|
+
},
|
|
997
|
+
|
|
998
|
+
// =========================================================================
|
|
999
|
+
// Command Injection
|
|
1000
|
+
// =========================================================================
|
|
1001
|
+
{
|
|
1002
|
+
name: 'Command Injection: exec with Template Literal',
|
|
1003
|
+
pattern: /\bexec(?:Sync)?\s*\(\s*`[^`]*\$\{/g,
|
|
1004
|
+
severity: 'critical',
|
|
1005
|
+
category: 'vulnerability',
|
|
1006
|
+
description: 'Running shell commands with interpolated values can lead to command injection. Validate all inputs or use execFile() with argument arrays.'
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
name: 'Command Injection: shell: true',
|
|
1010
|
+
pattern: /\bspawn(?:Sync)?\s*\([^)]*\bshell\s*:\s*true/g,
|
|
1011
|
+
severity: 'high',
|
|
1012
|
+
category: 'vulnerability',
|
|
1013
|
+
description: 'shell: true in spawn/spawnSync enables shell expansion and can lead to command injection. Remove shell: true and pass arguments as an array.'
|
|
1014
|
+
},
|
|
1015
|
+
|
|
1016
|
+
// =========================================================================
|
|
1017
|
+
// Weak Cryptography
|
|
1018
|
+
// =========================================================================
|
|
1019
|
+
{
|
|
1020
|
+
name: 'Weak Crypto: MD5',
|
|
1021
|
+
pattern: /createHash\s*\(\s*['"]md5['"]\s*\)/gi,
|
|
1022
|
+
severity: 'medium',
|
|
1023
|
+
category: 'vulnerability',
|
|
1024
|
+
description: 'MD5 is cryptographically broken and must not be used for security purposes. Use SHA-256 (createHash("sha256")) or SHA-3.'
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
name: 'Weak Crypto: SHA-1',
|
|
1028
|
+
pattern: /createHash\s*\(\s*['"]sha1['"]\s*\)/gi,
|
|
1029
|
+
severity: 'medium',
|
|
1030
|
+
category: 'vulnerability',
|
|
1031
|
+
description: 'SHA-1 is cryptographically weak and collision-prone. Use SHA-256 (createHash("sha256")) or SHA-3 instead.'
|
|
1032
|
+
},
|
|
1033
|
+
|
|
1034
|
+
// =========================================================================
|
|
1035
|
+
// TLS / SSL Bypass
|
|
1036
|
+
// =========================================================================
|
|
1037
|
+
{
|
|
1038
|
+
name: 'TLS Bypass: NODE_TLS_REJECT_UNAUTHORIZED=0',
|
|
1039
|
+
pattern: /NODE_TLS_REJECT_UNAUTHORIZED\s*[=:]\s*['"]?0['"]?/g,
|
|
1040
|
+
severity: 'critical',
|
|
1041
|
+
category: 'vulnerability',
|
|
1042
|
+
description: 'Setting NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS certificate validation and exposes your app to MITM attacks. Never use in production.'
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
name: 'TLS Bypass: rejectUnauthorized false',
|
|
1046
|
+
pattern: /\brejectUnauthorized\s*:\s*false\b/g,
|
|
1047
|
+
severity: 'high',
|
|
1048
|
+
category: 'vulnerability',
|
|
1049
|
+
description: 'rejectUnauthorized: false disables TLS certificate checking and enables man-in-the-middle attacks. Remove it or use a proper CA bundle.'
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
name: 'TLS Bypass: verify=False (Python)',
|
|
1053
|
+
pattern: /\brequests\.\w+\s*\([^)]*\bverify\s*=\s*False\b/g,
|
|
1054
|
+
severity: 'high',
|
|
1055
|
+
category: 'vulnerability',
|
|
1056
|
+
description: 'verify=False in Python requests disables SSL certificate verification. Remove this or pass verify="/path/to/ca-bundle.crt".'
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
// =========================================================================
|
|
1060
|
+
// Unsafe Deserialization
|
|
1061
|
+
// =========================================================================
|
|
1062
|
+
{
|
|
1063
|
+
name: 'Unsafe Deserialization: pickle.loads',
|
|
1064
|
+
pattern: /\bpickle\.loads?\s*\(/g,
|
|
1065
|
+
severity: 'high',
|
|
1066
|
+
category: 'vulnerability',
|
|
1067
|
+
description: 'pickle.loads() on untrusted data can execute arbitrary Python code (RCE). Use JSON or another safe format for data from untrusted sources.'
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
name: 'Unsafe Deserialization: yaml.load',
|
|
1071
|
+
pattern: /\byaml\.load\s*\(/g,
|
|
1072
|
+
severity: 'medium',
|
|
1073
|
+
category: 'vulnerability',
|
|
1074
|
+
description: 'yaml.load() can execute arbitrary code with certain YAML tags. Use yaml.safe_load() for untrusted input.'
|
|
1075
|
+
},
|
|
1076
|
+
|
|
1077
|
+
// =========================================================================
|
|
1078
|
+
// Security Misconfigurations
|
|
1079
|
+
// =========================================================================
|
|
1080
|
+
{
|
|
1081
|
+
name: 'Security Config: CORS Wildcard',
|
|
1082
|
+
pattern: /\borigin\s*:\s*['"]?\*['"]?/g,
|
|
1083
|
+
severity: 'medium',
|
|
1084
|
+
category: 'vulnerability',
|
|
1085
|
+
description: 'CORS wildcard (*) allows any origin to make credentialed requests to your API. Use a specific allowlist of trusted origins.'
|
|
1086
|
+
},
|
|
1087
|
+
|
|
1088
|
+
// =========================================================================
|
|
1089
|
+
// Deprecated / Insecure Node.js APIs
|
|
1090
|
+
// =========================================================================
|
|
1091
|
+
{
|
|
1092
|
+
name: 'Deprecated API: new Buffer()',
|
|
1093
|
+
pattern: /\bnew\s+Buffer\s*\(/g,
|
|
1094
|
+
severity: 'medium',
|
|
1095
|
+
category: 'vulnerability',
|
|
1096
|
+
description: 'new Buffer() is deprecated since Node.js 6 and has security implications. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe().'
|
|
1097
|
+
},
|
|
1098
|
+
];
|
|
1099
|
+
|
|
1100
|
+
// =============================================================================
|
|
1101
|
+
// TEST FILE PATTERNS (skipped by default, override with --include-tests)
|
|
1102
|
+
// =============================================================================
|
|
1103
|
+
// Test fixtures are the #1 source of false positives. They contain fake
|
|
1104
|
+
// credentials, mock data, and example values that look like real secrets.
|
|
1105
|
+
|
|
1106
|
+
export const TEST_FILE_PATTERNS = [
|
|
1107
|
+
/\.test\.[jt]sx?$/,
|
|
1108
|
+
/\.spec\.[jt]sx?$/,
|
|
1109
|
+
/\.test\.py$/,
|
|
1110
|
+
/test_[^/]+\.py$/,
|
|
1111
|
+
/__tests__[/\\]/,
|
|
1112
|
+
/[/\\]tests?[/\\]/,
|
|
1113
|
+
/[/\\]test[/\\]/,
|
|
1114
|
+
/[/\\]fixtures?[/\\]/,
|
|
1115
|
+
/[/\\]mocks?[/\\]/,
|
|
1116
|
+
/[/\\]__mocks__[/\\]/,
|
|
1117
|
+
/[/\\]stubs?[/\\]/,
|
|
1118
|
+
/[/\\]fakes?[/\\]/,
|
|
1119
|
+
/\.stories\.[jt]sx?$/, // Storybook story files
|
|
1120
|
+
/\.mock\.[jt]sx?$/,
|
|
1121
|
+
];
|