@cencori/scan 0.1.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 +98 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +679 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +656 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +536 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +496 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
// src/scanner/index.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { glob } from "glob";
|
|
5
|
+
|
|
6
|
+
// src/scanner/patterns.ts
|
|
7
|
+
var SECRET_PATTERNS = [
|
|
8
|
+
// OpenAI
|
|
9
|
+
{
|
|
10
|
+
name: "OpenAI API Key",
|
|
11
|
+
provider: "OpenAI",
|
|
12
|
+
pattern: /sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20}/g,
|
|
13
|
+
severity: "critical"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "OpenAI Project Key",
|
|
17
|
+
provider: "OpenAI",
|
|
18
|
+
pattern: /sk-proj-[a-zA-Z0-9_-]{80,}/g,
|
|
19
|
+
severity: "critical"
|
|
20
|
+
},
|
|
21
|
+
// Anthropic
|
|
22
|
+
{
|
|
23
|
+
name: "Anthropic API Key",
|
|
24
|
+
provider: "Anthropic",
|
|
25
|
+
pattern: /sk-ant-[a-zA-Z0-9-]{90,}/g,
|
|
26
|
+
severity: "critical"
|
|
27
|
+
},
|
|
28
|
+
// Google
|
|
29
|
+
{
|
|
30
|
+
name: "Google API Key",
|
|
31
|
+
provider: "Google",
|
|
32
|
+
pattern: /AIza[0-9A-Za-z_-]{35}/g,
|
|
33
|
+
severity: "critical"
|
|
34
|
+
},
|
|
35
|
+
// Supabase
|
|
36
|
+
{
|
|
37
|
+
name: "Supabase Service Role Key",
|
|
38
|
+
provider: "Supabase",
|
|
39
|
+
pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
|
|
40
|
+
severity: "critical"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "Supabase Anon Key (if hardcoded)",
|
|
44
|
+
provider: "Supabase",
|
|
45
|
+
pattern: /SUPABASE_ANON_KEY\s*[:=]\s*["']eyJ[^"']+["']/g,
|
|
46
|
+
severity: "medium"
|
|
47
|
+
},
|
|
48
|
+
// Stripe
|
|
49
|
+
{
|
|
50
|
+
name: "Stripe Secret Key",
|
|
51
|
+
provider: "Stripe",
|
|
52
|
+
pattern: /sk_live_[0-9a-zA-Z]{24,}/g,
|
|
53
|
+
severity: "critical"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "Stripe Test Key",
|
|
57
|
+
provider: "Stripe",
|
|
58
|
+
pattern: /sk_test_[0-9a-zA-Z]{24,}/g,
|
|
59
|
+
severity: "medium"
|
|
60
|
+
},
|
|
61
|
+
// AWS
|
|
62
|
+
{
|
|
63
|
+
name: "AWS Access Key ID",
|
|
64
|
+
provider: "AWS",
|
|
65
|
+
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
66
|
+
severity: "critical"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "AWS Secret Access Key",
|
|
70
|
+
provider: "AWS",
|
|
71
|
+
pattern: /aws_secret_access_key\s*[:=]\s*["'][A-Za-z0-9/+=]{40}["']/gi,
|
|
72
|
+
severity: "critical"
|
|
73
|
+
},
|
|
74
|
+
// GitHub
|
|
75
|
+
{
|
|
76
|
+
name: "GitHub Personal Access Token",
|
|
77
|
+
provider: "GitHub",
|
|
78
|
+
pattern: /ghp_[a-zA-Z0-9]{36}/g,
|
|
79
|
+
severity: "critical"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "GitHub OAuth Token",
|
|
83
|
+
provider: "GitHub",
|
|
84
|
+
pattern: /gho_[a-zA-Z0-9]{36}/g,
|
|
85
|
+
severity: "critical"
|
|
86
|
+
},
|
|
87
|
+
// Telegram
|
|
88
|
+
{
|
|
89
|
+
name: "Telegram Bot Token",
|
|
90
|
+
provider: "Telegram",
|
|
91
|
+
pattern: /[0-9]{9,10}:[a-zA-Z0-9_-]{35}/g,
|
|
92
|
+
severity: "high"
|
|
93
|
+
},
|
|
94
|
+
// Discord
|
|
95
|
+
{
|
|
96
|
+
name: "Discord Bot Token",
|
|
97
|
+
provider: "Discord",
|
|
98
|
+
pattern: /[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27}/g,
|
|
99
|
+
severity: "high"
|
|
100
|
+
},
|
|
101
|
+
// Slack
|
|
102
|
+
{
|
|
103
|
+
name: "Slack Bot Token",
|
|
104
|
+
provider: "Slack",
|
|
105
|
+
pattern: /xoxb-[0-9]{11}-[0-9]{11}-[a-zA-Z0-9]{24}/g,
|
|
106
|
+
severity: "high"
|
|
107
|
+
},
|
|
108
|
+
// SendGrid
|
|
109
|
+
{
|
|
110
|
+
name: "SendGrid API Key",
|
|
111
|
+
provider: "SendGrid",
|
|
112
|
+
pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
|
|
113
|
+
severity: "high"
|
|
114
|
+
},
|
|
115
|
+
// Twilio
|
|
116
|
+
{
|
|
117
|
+
name: "Twilio API Key",
|
|
118
|
+
provider: "Twilio",
|
|
119
|
+
pattern: /SK[a-fA-F0-9]{32}/g,
|
|
120
|
+
severity: "high"
|
|
121
|
+
},
|
|
122
|
+
// Mailgun
|
|
123
|
+
{
|
|
124
|
+
name: "Mailgun API Key",
|
|
125
|
+
provider: "Mailgun",
|
|
126
|
+
pattern: /key-[a-zA-Z0-9]{32}/g,
|
|
127
|
+
severity: "high"
|
|
128
|
+
},
|
|
129
|
+
// Firebase
|
|
130
|
+
{
|
|
131
|
+
name: "Firebase Database URL",
|
|
132
|
+
provider: "Firebase",
|
|
133
|
+
pattern: /https:\/\/[a-z0-9-]+\.firebaseio\.com/g,
|
|
134
|
+
severity: "medium"
|
|
135
|
+
},
|
|
136
|
+
// Generic patterns
|
|
137
|
+
{
|
|
138
|
+
name: "Private Key",
|
|
139
|
+
provider: "Generic",
|
|
140
|
+
pattern: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
141
|
+
severity: "critical"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "Generic API Key Assignment",
|
|
145
|
+
provider: "Generic",
|
|
146
|
+
pattern: /(api_key|apikey|api_secret|secret_key)\s*[:=]\s*["'][a-zA-Z0-9_-]{20,}["']/gi,
|
|
147
|
+
severity: "high"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "Password Assignment",
|
|
151
|
+
provider: "Generic",
|
|
152
|
+
pattern: /(password|passwd|pwd)\s*[:=]\s*["'][^"']{8,}["']/gi,
|
|
153
|
+
severity: "high"
|
|
154
|
+
},
|
|
155
|
+
// Replicate
|
|
156
|
+
{
|
|
157
|
+
name: "Replicate API Token",
|
|
158
|
+
provider: "Replicate",
|
|
159
|
+
pattern: /r8_[a-zA-Z0-9]{38}/g,
|
|
160
|
+
severity: "critical"
|
|
161
|
+
},
|
|
162
|
+
// Hugging Face
|
|
163
|
+
{
|
|
164
|
+
name: "Hugging Face Token",
|
|
165
|
+
provider: "Hugging Face",
|
|
166
|
+
pattern: /hf_[a-zA-Z0-9]{34}/g,
|
|
167
|
+
severity: "critical"
|
|
168
|
+
},
|
|
169
|
+
// Cohere
|
|
170
|
+
{
|
|
171
|
+
name: "Cohere API Key",
|
|
172
|
+
provider: "Cohere",
|
|
173
|
+
pattern: /[a-zA-Z0-9]{40}/g,
|
|
174
|
+
// Less specific, check context
|
|
175
|
+
severity: "medium"
|
|
176
|
+
}
|
|
177
|
+
];
|
|
178
|
+
var PII_PATTERNS = [
|
|
179
|
+
{
|
|
180
|
+
name: "Email Address",
|
|
181
|
+
pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
182
|
+
severity: "medium"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "Phone Number (US)",
|
|
186
|
+
pattern: /(\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,
|
|
187
|
+
severity: "medium"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "Phone Number (International)",
|
|
191
|
+
pattern: /\+[1-9]\d{1,14}/g,
|
|
192
|
+
severity: "medium"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "Social Security Number",
|
|
196
|
+
pattern: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
197
|
+
severity: "high"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "Credit Card Number",
|
|
201
|
+
pattern: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
|
|
202
|
+
severity: "high"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "IP Address",
|
|
206
|
+
pattern: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
|
|
207
|
+
severity: "low"
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
var ROUTE_PATTERNS = [
|
|
211
|
+
// Next.js API routes without auth
|
|
212
|
+
{
|
|
213
|
+
name: "Next.js API Route (check for auth)",
|
|
214
|
+
framework: "Next.js",
|
|
215
|
+
pattern: /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)\s*\(/g,
|
|
216
|
+
severity: "medium",
|
|
217
|
+
description: "API route handler - verify authentication is implemented"
|
|
218
|
+
},
|
|
219
|
+
// Express routes
|
|
220
|
+
{
|
|
221
|
+
name: "Express Route without Auth Middleware",
|
|
222
|
+
framework: "Express",
|
|
223
|
+
pattern: /app\.(get|post|put|delete|patch)\s*\(\s*["'`][^"'`]+["'`]\s*,\s*(?!.*auth)/gi,
|
|
224
|
+
severity: "medium",
|
|
225
|
+
description: "Express route - check if auth middleware is applied"
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
var IGNORE_PATTERNS = [
|
|
229
|
+
"node_modules",
|
|
230
|
+
".git",
|
|
231
|
+
"dist",
|
|
232
|
+
"build",
|
|
233
|
+
".next",
|
|
234
|
+
".venv",
|
|
235
|
+
"__pycache__",
|
|
236
|
+
"*.min.js",
|
|
237
|
+
"*.min.css",
|
|
238
|
+
"*.map",
|
|
239
|
+
"package-lock.json",
|
|
240
|
+
"yarn.lock",
|
|
241
|
+
"pnpm-lock.yaml"
|
|
242
|
+
];
|
|
243
|
+
var SCANNABLE_EXTENSIONS = [
|
|
244
|
+
".js",
|
|
245
|
+
".jsx",
|
|
246
|
+
".ts",
|
|
247
|
+
".tsx",
|
|
248
|
+
".mjs",
|
|
249
|
+
".cjs",
|
|
250
|
+
".py",
|
|
251
|
+
".rb",
|
|
252
|
+
".go",
|
|
253
|
+
".java",
|
|
254
|
+
".php",
|
|
255
|
+
".env",
|
|
256
|
+
".json",
|
|
257
|
+
".yaml",
|
|
258
|
+
".yml",
|
|
259
|
+
".toml",
|
|
260
|
+
".xml",
|
|
261
|
+
".md",
|
|
262
|
+
".txt",
|
|
263
|
+
".sql",
|
|
264
|
+
".sh",
|
|
265
|
+
".bash",
|
|
266
|
+
".zsh"
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
// src/scanner/index.ts
|
|
270
|
+
function redact(match, showChars = 4) {
|
|
271
|
+
if (match.length <= showChars * 2) {
|
|
272
|
+
return "*".repeat(match.length);
|
|
273
|
+
}
|
|
274
|
+
return match.slice(0, showChars) + "****" + match.slice(-showChars);
|
|
275
|
+
}
|
|
276
|
+
function getPosition(content, index) {
|
|
277
|
+
const lines = content.slice(0, index).split("\n");
|
|
278
|
+
return {
|
|
279
|
+
line: lines.length,
|
|
280
|
+
column: lines[lines.length - 1].length + 1
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function shouldIgnore(filePath) {
|
|
284
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
285
|
+
return IGNORE_PATTERNS.some((pattern) => {
|
|
286
|
+
if (pattern.startsWith("*")) {
|
|
287
|
+
return normalized.endsWith(pattern.slice(1));
|
|
288
|
+
}
|
|
289
|
+
return normalized.includes(pattern);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function isScannable(filePath) {
|
|
293
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
294
|
+
return SCANNABLE_EXTENSIONS.includes(ext);
|
|
295
|
+
}
|
|
296
|
+
function scanFile(filePath, content) {
|
|
297
|
+
const issues = [];
|
|
298
|
+
const relativePath = filePath;
|
|
299
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
300
|
+
pattern.pattern.lastIndex = 0;
|
|
301
|
+
let match;
|
|
302
|
+
while ((match = pattern.pattern.exec(content)) !== null) {
|
|
303
|
+
const pos = getPosition(content, match.index);
|
|
304
|
+
issues.push({
|
|
305
|
+
type: "secret",
|
|
306
|
+
severity: pattern.severity,
|
|
307
|
+
name: pattern.name,
|
|
308
|
+
provider: pattern.provider,
|
|
309
|
+
file: relativePath,
|
|
310
|
+
line: pos.line,
|
|
311
|
+
column: pos.column,
|
|
312
|
+
match: redact(match[0])
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
for (const pattern of PII_PATTERNS) {
|
|
317
|
+
pattern.pattern.lastIndex = 0;
|
|
318
|
+
let match;
|
|
319
|
+
while ((match = pattern.pattern.exec(content)) !== null) {
|
|
320
|
+
const matchStr = match[0];
|
|
321
|
+
if (isLikelyFalsePositive(matchStr, pattern.name)) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const pos = getPosition(content, match.index);
|
|
325
|
+
issues.push({
|
|
326
|
+
type: "pii",
|
|
327
|
+
severity: pattern.severity,
|
|
328
|
+
name: pattern.name,
|
|
329
|
+
file: relativePath,
|
|
330
|
+
line: pos.line,
|
|
331
|
+
column: pos.column,
|
|
332
|
+
match: redact(matchStr, 3)
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
for (const pattern of ROUTE_PATTERNS) {
|
|
337
|
+
pattern.pattern.lastIndex = 0;
|
|
338
|
+
let match;
|
|
339
|
+
while ((match = pattern.pattern.exec(content)) !== null) {
|
|
340
|
+
const pos = getPosition(content, match.index);
|
|
341
|
+
issues.push({
|
|
342
|
+
type: "route",
|
|
343
|
+
severity: pattern.severity,
|
|
344
|
+
name: pattern.name,
|
|
345
|
+
file: relativePath,
|
|
346
|
+
line: pos.line,
|
|
347
|
+
column: pos.column,
|
|
348
|
+
match: match[0],
|
|
349
|
+
description: pattern.description
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const fileName = path.basename(filePath);
|
|
354
|
+
if (fileName.startsWith(".env") && !fileName.includes(".example")) {
|
|
355
|
+
const gitignorePath = path.join(path.dirname(filePath), ".gitignore");
|
|
356
|
+
const gitignoreExists = fs.existsSync(gitignorePath);
|
|
357
|
+
issues.push({
|
|
358
|
+
type: "config",
|
|
359
|
+
severity: "high",
|
|
360
|
+
name: "Environment file in repository",
|
|
361
|
+
file: relativePath,
|
|
362
|
+
line: 1,
|
|
363
|
+
column: 1,
|
|
364
|
+
match: fileName,
|
|
365
|
+
description: gitignoreExists ? "Verify this file is in .gitignore" : "Add .env* to .gitignore"
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return issues;
|
|
369
|
+
}
|
|
370
|
+
function isLikelyFalsePositive(match, patternName) {
|
|
371
|
+
if (patternName === "Email Address") {
|
|
372
|
+
const falseDomains = [
|
|
373
|
+
"example.com",
|
|
374
|
+
"example.org",
|
|
375
|
+
"test.com",
|
|
376
|
+
"localhost",
|
|
377
|
+
"placeholder.com"
|
|
378
|
+
];
|
|
379
|
+
if (falseDomains.some((d) => match.includes(d))) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
const publicPrefixes = [
|
|
383
|
+
"support@",
|
|
384
|
+
"help@",
|
|
385
|
+
"info@",
|
|
386
|
+
"contact@",
|
|
387
|
+
"sales@",
|
|
388
|
+
"admin@",
|
|
389
|
+
"noreply@",
|
|
390
|
+
"no-reply@",
|
|
391
|
+
"hello@",
|
|
392
|
+
"team@",
|
|
393
|
+
"partners@",
|
|
394
|
+
"enterprise@",
|
|
395
|
+
"security@",
|
|
396
|
+
"privacy@",
|
|
397
|
+
"legal@"
|
|
398
|
+
];
|
|
399
|
+
if (publicPrefixes.some((p) => match.toLowerCase().startsWith(p))) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (patternName === "IP Address") {
|
|
404
|
+
const falseIPs = ["0.0.0.0", "127.0.0.1", "192.168.", "10.0.", "172.16."];
|
|
405
|
+
if (falseIPs.some((ip) => match.startsWith(ip))) {
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (patternName.includes("Phone Number")) {
|
|
410
|
+
if (match.includes("555") || match.includes("123-456") || match.includes("000-000")) {
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
function calculateScore(issues) {
|
|
417
|
+
const critical = issues.filter((i) => i.severity === "critical").length;
|
|
418
|
+
const high = issues.filter((i) => i.severity === "high").length;
|
|
419
|
+
const medium = issues.filter((i) => i.severity === "medium").length;
|
|
420
|
+
const total = issues.length;
|
|
421
|
+
if (critical > 0) return "F";
|
|
422
|
+
if (high >= 3) return "F";
|
|
423
|
+
if (high >= 2) return "D";
|
|
424
|
+
if (high >= 1 || medium >= 5) return "C";
|
|
425
|
+
if (medium >= 2) return "B";
|
|
426
|
+
if (total === 0) return "A";
|
|
427
|
+
return "B";
|
|
428
|
+
}
|
|
429
|
+
function getTierDescription(score) {
|
|
430
|
+
switch (score) {
|
|
431
|
+
case "A":
|
|
432
|
+
return "Excellent! No security issues detected.";
|
|
433
|
+
case "B":
|
|
434
|
+
return "Good, but minor improvements recommended.";
|
|
435
|
+
case "C":
|
|
436
|
+
return "Fair. Some security concerns need attention.";
|
|
437
|
+
case "D":
|
|
438
|
+
return "Poor. Significant security issues detected.";
|
|
439
|
+
case "F":
|
|
440
|
+
return "Critical! Your app is leaking secrets.";
|
|
441
|
+
default:
|
|
442
|
+
return "";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async function scan(targetPath) {
|
|
446
|
+
const startTime = Date.now();
|
|
447
|
+
const absolutePath = path.resolve(targetPath);
|
|
448
|
+
const files = await glob("**/*", {
|
|
449
|
+
cwd: absolutePath,
|
|
450
|
+
nodir: true,
|
|
451
|
+
ignore: IGNORE_PATTERNS,
|
|
452
|
+
absolute: true
|
|
453
|
+
});
|
|
454
|
+
const issues = [];
|
|
455
|
+
let filesScanned = 0;
|
|
456
|
+
for (const file of files) {
|
|
457
|
+
if (!isScannable(file) || shouldIgnore(file)) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
462
|
+
const relativePath = path.relative(absolutePath, file);
|
|
463
|
+
const fileIssues = scanFile(relativePath, content);
|
|
464
|
+
issues.push(...fileIssues);
|
|
465
|
+
filesScanned++;
|
|
466
|
+
} catch {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const score = calculateScore(issues);
|
|
471
|
+
const scanDuration = Date.now() - startTime;
|
|
472
|
+
return {
|
|
473
|
+
score,
|
|
474
|
+
tierDescription: getTierDescription(score),
|
|
475
|
+
issues,
|
|
476
|
+
filesScanned,
|
|
477
|
+
scanDuration,
|
|
478
|
+
summary: {
|
|
479
|
+
secrets: issues.filter((i) => i.type === "secret").length,
|
|
480
|
+
pii: issues.filter((i) => i.type === "pii").length,
|
|
481
|
+
routes: issues.filter((i) => i.type === "route").length,
|
|
482
|
+
config: issues.filter((i) => i.type === "config").length,
|
|
483
|
+
critical: issues.filter((i) => i.severity === "critical").length,
|
|
484
|
+
high: issues.filter((i) => i.severity === "high").length,
|
|
485
|
+
medium: issues.filter((i) => i.severity === "medium").length,
|
|
486
|
+
low: issues.filter((i) => i.severity === "low").length
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
export {
|
|
491
|
+
PII_PATTERNS,
|
|
492
|
+
ROUTE_PATTERNS,
|
|
493
|
+
SECRET_PATTERNS,
|
|
494
|
+
scan
|
|
495
|
+
};
|
|
496
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/scanner/index.ts","../src/scanner/patterns.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { glob } from 'glob';\nimport {\n SECRET_PATTERNS,\n PII_PATTERNS,\n ROUTE_PATTERNS,\n IGNORE_PATTERNS,\n SCANNABLE_EXTENSIONS,\n type SecretPattern,\n type PIIPattern,\n type RoutePattern,\n} from './patterns';\n\nexport interface ScanIssue {\n type: 'secret' | 'pii' | 'route' | 'config';\n severity: 'critical' | 'high' | 'medium' | 'low';\n name: string;\n provider?: string;\n file: string;\n line: number;\n column: number;\n match: string; // Redacted version\n description?: string;\n}\n\nexport interface ScanResult {\n score: 'A' | 'B' | 'C' | 'D' | 'F';\n tierDescription: string;\n issues: ScanIssue[];\n filesScanned: number;\n scanDuration: number;\n summary: {\n secrets: number;\n pii: number;\n routes: number;\n config: number;\n critical: number;\n high: number;\n medium: number;\n low: number;\n };\n}\n\n/**\n * Redact sensitive content for display\n */\nfunction redact(match: string, showChars: number = 4): string {\n if (match.length <= showChars * 2) {\n return '*'.repeat(match.length);\n }\n return match.slice(0, showChars) + '****' + match.slice(-showChars);\n}\n\n/**\n * Get line and column number for a match index\n */\nfunction getPosition(content: string, index: number): { line: number; column: number } {\n const lines = content.slice(0, index).split('\\n');\n return {\n line: lines.length,\n column: lines[lines.length - 1].length + 1,\n };\n}\n\n/**\n * Check if a file should be ignored\n */\nfunction shouldIgnore(filePath: string): boolean {\n const normalized = filePath.replace(/\\\\/g, '/');\n return IGNORE_PATTERNS.some(pattern => {\n if (pattern.startsWith('*')) {\n return normalized.endsWith(pattern.slice(1));\n }\n return normalized.includes(pattern);\n });\n}\n\n/**\n * Check if file has scannable extension\n */\nfunction isScannable(filePath: string): boolean {\n const ext = path.extname(filePath).toLowerCase();\n return SCANNABLE_EXTENSIONS.includes(ext);\n}\n\n/**\n * Scan a single file for issues\n */\nfunction scanFile(filePath: string, content: string): ScanIssue[] {\n const issues: ScanIssue[] = [];\n const relativePath = filePath;\n\n // Scan for secrets\n for (const pattern of SECRET_PATTERNS) {\n // Reset regex lastIndex\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'secret',\n severity: pattern.severity,\n name: pattern.name,\n provider: pattern.provider,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: redact(match[0]),\n });\n }\n }\n\n // Scan for PII\n for (const pattern of PII_PATTERNS) {\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n // Skip common false positives\n const matchStr = match[0];\n if (isLikelyFalsePositive(matchStr, pattern.name)) {\n continue;\n }\n\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'pii',\n severity: pattern.severity,\n name: pattern.name,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: redact(matchStr, 3),\n });\n }\n }\n\n // Scan for exposed routes\n for (const pattern of ROUTE_PATTERNS) {\n pattern.pattern.lastIndex = 0;\n let match;\n while ((match = pattern.pattern.exec(content)) !== null) {\n const pos = getPosition(content, match.index);\n issues.push({\n type: 'route',\n severity: pattern.severity,\n name: pattern.name,\n file: relativePath,\n line: pos.line,\n column: pos.column,\n match: match[0],\n description: pattern.description,\n });\n }\n }\n\n // Check for .env files in wrong places\n const fileName = path.basename(filePath);\n if (fileName.startsWith('.env') && !fileName.includes('.example')) {\n // If we're scanning a .env file, it might be committed\n const gitignorePath = path.join(path.dirname(filePath), '.gitignore');\n const gitignoreExists = fs.existsSync(gitignorePath);\n\n issues.push({\n type: 'config',\n severity: 'high',\n name: 'Environment file in repository',\n file: relativePath,\n line: 1,\n column: 1,\n match: fileName,\n description: gitignoreExists\n ? 'Verify this file is in .gitignore'\n : 'Add .env* to .gitignore',\n });\n }\n\n return issues;\n}\n\n/**\n * Filter out likely false positives\n */\nfunction isLikelyFalsePositive(match: string, patternName: string): boolean {\n // Common email false positives\n if (patternName === 'Email Address') {\n const falseDomains = [\n 'example.com',\n 'example.org',\n 'test.com',\n 'localhost',\n 'placeholder.com',\n ];\n if (falseDomains.some(d => match.includes(d))) {\n return true;\n }\n\n // Common public email prefixes (not personal PII)\n const publicPrefixes = [\n 'support@',\n 'help@',\n 'info@',\n 'contact@',\n 'sales@',\n 'admin@',\n 'noreply@',\n 'no-reply@',\n 'hello@',\n 'team@',\n 'partners@',\n 'enterprise@',\n 'security@',\n 'privacy@',\n 'legal@',\n ];\n if (publicPrefixes.some(p => match.toLowerCase().startsWith(p))) {\n return true;\n }\n }\n\n // IP address false positives (version numbers, etc)\n if (patternName === 'IP Address') {\n const falseIPs = ['0.0.0.0', '127.0.0.1', '192.168.', '10.0.', '172.16.'];\n if (falseIPs.some(ip => match.startsWith(ip))) {\n return true;\n }\n }\n\n // Phone number false positives (example numbers in docs)\n if (patternName.includes('Phone Number')) {\n // Common example phone numbers\n if (match.includes('555') || match.includes('123-456') || match.includes('000-000')) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Calculate the fragility score based on issues\n */\nfunction calculateScore(issues: ScanIssue[]): 'A' | 'B' | 'C' | 'D' | 'F' {\n const critical = issues.filter(i => i.severity === 'critical').length;\n const high = issues.filter(i => i.severity === 'high').length;\n const medium = issues.filter(i => i.severity === 'medium').length;\n const total = issues.length;\n\n // Scoring algorithm\n if (critical > 0) return 'F';\n if (high >= 3) return 'F';\n if (high >= 2) return 'D';\n if (high >= 1 || medium >= 5) return 'C';\n if (medium >= 2) return 'B';\n if (total === 0) return 'A';\n return 'B';\n}\n\n/**\n * Get tier description\n */\nfunction getTierDescription(score: string): string {\n switch (score) {\n case 'A':\n return 'Excellent! No security issues detected.';\n case 'B':\n return 'Good, but minor improvements recommended.';\n case 'C':\n return 'Fair. Some security concerns need attention.';\n case 'D':\n return 'Poor. Significant security issues detected.';\n case 'F':\n return 'Critical! Your app is leaking secrets.';\n default:\n return '';\n }\n}\n\n/**\n * Main scan function\n */\nexport async function scan(targetPath: string): Promise<ScanResult> {\n const startTime = Date.now();\n const absolutePath = path.resolve(targetPath);\n\n // Get all files\n const files = await glob('**/*', {\n cwd: absolutePath,\n nodir: true,\n ignore: IGNORE_PATTERNS,\n absolute: true,\n });\n\n const issues: ScanIssue[] = [];\n let filesScanned = 0;\n\n for (const file of files) {\n if (!isScannable(file) || shouldIgnore(file)) {\n continue;\n }\n\n try {\n const content = fs.readFileSync(file, 'utf-8');\n const relativePath = path.relative(absolutePath, file);\n const fileIssues = scanFile(relativePath, content);\n issues.push(...fileIssues);\n filesScanned++;\n } catch {\n // Skip files that can't be read\n continue;\n }\n }\n\n const score = calculateScore(issues);\n const scanDuration = Date.now() - startTime;\n\n return {\n score,\n tierDescription: getTierDescription(score),\n issues,\n filesScanned,\n scanDuration,\n summary: {\n secrets: issues.filter(i => i.type === 'secret').length,\n pii: issues.filter(i => i.type === 'pii').length,\n routes: issues.filter(i => i.type === 'route').length,\n config: issues.filter(i => i.type === 'config').length,\n critical: issues.filter(i => i.severity === 'critical').length,\n high: issues.filter(i => i.severity === 'high').length,\n medium: issues.filter(i => i.severity === 'medium').length,\n low: issues.filter(i => i.severity === 'low').length,\n },\n };\n}\n","/**\n * Secret detection patterns for common API keys and tokens\n */\nexport interface SecretPattern {\n name: string;\n provider: string;\n pattern: RegExp;\n severity: 'critical' | 'high' | 'medium' | 'low';\n}\n\nexport const SECRET_PATTERNS: SecretPattern[] = [\n // OpenAI\n {\n name: 'OpenAI API Key',\n provider: 'OpenAI',\n pattern: /sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20}/g,\n severity: 'critical',\n },\n {\n name: 'OpenAI Project Key',\n provider: 'OpenAI',\n pattern: /sk-proj-[a-zA-Z0-9_-]{80,}/g,\n severity: 'critical',\n },\n // Anthropic\n {\n name: 'Anthropic API Key',\n provider: 'Anthropic',\n pattern: /sk-ant-[a-zA-Z0-9-]{90,}/g,\n severity: 'critical',\n },\n // Google\n {\n name: 'Google API Key',\n provider: 'Google',\n pattern: /AIza[0-9A-Za-z_-]{35}/g,\n severity: 'critical',\n },\n // Supabase\n {\n name: 'Supabase Service Role Key',\n provider: 'Supabase',\n pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_-]+/g,\n severity: 'critical',\n },\n {\n name: 'Supabase Anon Key (if hardcoded)',\n provider: 'Supabase',\n pattern: /SUPABASE_ANON_KEY\\s*[:=]\\s*[\"']eyJ[^\"']+[\"']/g,\n severity: 'medium',\n },\n // Stripe\n {\n name: 'Stripe Secret Key',\n provider: 'Stripe',\n pattern: /sk_live_[0-9a-zA-Z]{24,}/g,\n severity: 'critical',\n },\n {\n name: 'Stripe Test Key',\n provider: 'Stripe',\n pattern: /sk_test_[0-9a-zA-Z]{24,}/g,\n severity: 'medium',\n },\n // AWS\n {\n name: 'AWS Access Key ID',\n provider: 'AWS',\n pattern: /AKIA[0-9A-Z]{16}/g,\n severity: 'critical',\n },\n {\n name: 'AWS Secret Access Key',\n provider: 'AWS',\n pattern: /aws_secret_access_key\\s*[:=]\\s*[\"'][A-Za-z0-9/+=]{40}[\"']/gi,\n severity: 'critical',\n },\n // GitHub\n {\n name: 'GitHub Personal Access Token',\n provider: 'GitHub',\n pattern: /ghp_[a-zA-Z0-9]{36}/g,\n severity: 'critical',\n },\n {\n name: 'GitHub OAuth Token',\n provider: 'GitHub',\n pattern: /gho_[a-zA-Z0-9]{36}/g,\n severity: 'critical',\n },\n // Telegram\n {\n name: 'Telegram Bot Token',\n provider: 'Telegram',\n pattern: /[0-9]{9,10}:[a-zA-Z0-9_-]{35}/g,\n severity: 'high',\n },\n // Discord\n {\n name: 'Discord Bot Token',\n provider: 'Discord',\n pattern: /[MN][A-Za-z\\d]{23,}\\.[\\w-]{6}\\.[\\w-]{27}/g,\n severity: 'high',\n },\n // Slack\n {\n name: 'Slack Bot Token',\n provider: 'Slack',\n pattern: /xoxb-[0-9]{11}-[0-9]{11}-[a-zA-Z0-9]{24}/g,\n severity: 'high',\n },\n // SendGrid\n {\n name: 'SendGrid API Key',\n provider: 'SendGrid',\n pattern: /SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}/g,\n severity: 'high',\n },\n // Twilio\n {\n name: 'Twilio API Key',\n provider: 'Twilio',\n pattern: /SK[a-fA-F0-9]{32}/g,\n severity: 'high',\n },\n // Mailgun\n {\n name: 'Mailgun API Key',\n provider: 'Mailgun',\n pattern: /key-[a-zA-Z0-9]{32}/g,\n severity: 'high',\n },\n // Firebase\n {\n name: 'Firebase Database URL',\n provider: 'Firebase',\n pattern: /https:\\/\\/[a-z0-9-]+\\.firebaseio\\.com/g,\n severity: 'medium',\n },\n // Generic patterns\n {\n name: 'Private Key',\n provider: 'Generic',\n pattern: /-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,\n severity: 'critical',\n },\n {\n name: 'Generic API Key Assignment',\n provider: 'Generic',\n pattern: /(api_key|apikey|api_secret|secret_key)\\s*[:=]\\s*[\"'][a-zA-Z0-9_-]{20,}[\"']/gi,\n severity: 'high',\n },\n {\n name: 'Password Assignment',\n provider: 'Generic',\n pattern: /(password|passwd|pwd)\\s*[:=]\\s*[\"'][^\"']{8,}[\"']/gi,\n severity: 'high',\n },\n // Replicate\n {\n name: 'Replicate API Token',\n provider: 'Replicate',\n pattern: /r8_[a-zA-Z0-9]{38}/g,\n severity: 'critical',\n },\n // Hugging Face\n {\n name: 'Hugging Face Token',\n provider: 'Hugging Face',\n pattern: /hf_[a-zA-Z0-9]{34}/g,\n severity: 'critical',\n },\n // Cohere\n {\n name: 'Cohere API Key',\n provider: 'Cohere',\n pattern: /[a-zA-Z0-9]{40}/g, // Less specific, check context\n severity: 'medium',\n },\n];\n\n/**\n * PII detection patterns\n */\nexport interface PIIPattern {\n name: string;\n pattern: RegExp;\n severity: 'high' | 'medium' | 'low';\n}\n\nexport const PII_PATTERNS: PIIPattern[] = [\n {\n name: 'Email Address',\n pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g,\n severity: 'medium',\n },\n {\n name: 'Phone Number (US)',\n pattern: /(\\+1[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g,\n severity: 'medium',\n },\n {\n name: 'Phone Number (International)',\n pattern: /\\+[1-9]\\d{1,14}/g,\n severity: 'medium',\n },\n {\n name: 'Social Security Number',\n pattern: /\\b\\d{3}-\\d{2}-\\d{4}\\b/g,\n severity: 'high',\n },\n {\n name: 'Credit Card Number',\n pattern: /\\b(?:\\d{4}[-\\s]?){3}\\d{4}\\b/g,\n severity: 'high',\n },\n {\n name: 'IP Address',\n pattern: /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g,\n severity: 'low',\n },\n];\n\n/**\n * Exposed route patterns for common frameworks\n */\nexport interface RoutePattern {\n name: string;\n framework: string;\n pattern: RegExp;\n severity: 'high' | 'medium' | 'low';\n description: string;\n}\n\nexport const ROUTE_PATTERNS: RoutePattern[] = [\n // Next.js API routes without auth\n {\n name: 'Next.js API Route (check for auth)',\n framework: 'Next.js',\n pattern: /export\\s+(async\\s+)?function\\s+(GET|POST|PUT|DELETE|PATCH)\\s*\\(/g,\n severity: 'medium',\n description: 'API route handler - verify authentication is implemented',\n },\n // Express routes\n {\n name: 'Express Route without Auth Middleware',\n framework: 'Express',\n pattern: /app\\.(get|post|put|delete|patch)\\s*\\(\\s*[\"'`][^\"'`]+[\"'`]\\s*,\\s*(?!.*auth)/gi,\n severity: 'medium',\n description: 'Express route - check if auth middleware is applied',\n },\n];\n\n/**\n * Files/patterns to ignore\n */\nexport const IGNORE_PATTERNS = [\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n '.venv',\n '__pycache__',\n '*.min.js',\n '*.min.css',\n '*.map',\n 'package-lock.json',\n 'yarn.lock',\n 'pnpm-lock.yaml',\n];\n\n/**\n * File extensions to scan\n */\nexport const SCANNABLE_EXTENSIONS = [\n '.js',\n '.jsx',\n '.ts',\n '.tsx',\n '.mjs',\n '.cjs',\n '.py',\n '.rb',\n '.go',\n '.java',\n '.php',\n '.env',\n '.json',\n '.yaml',\n '.yml',\n '.toml',\n '.xml',\n '.md',\n '.txt',\n '.sql',\n '.sh',\n '.bash',\n '.zsh',\n];\n"],"mappings":";AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,SAAS,YAAY;;;ACQd,IAAM,kBAAmC;AAAA;AAAA,EAE5C;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA;AAAA,IACT,UAAU;AAAA,EACd;AACJ;AAWO,IAAM,eAA6B;AAAA,EACtC;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AAAA,EACA;AAAA,IACI,MAAM;AAAA,IACN,SAAS;AAAA,IACT,UAAU;AAAA,EACd;AACJ;AAaO,IAAM,iBAAiC;AAAA;AAAA,EAE1C;AAAA,IACI,MAAM;AAAA,IACN,WAAW;AAAA,IACX,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA;AAAA,EAEA;AAAA,IACI,MAAM;AAAA,IACN,WAAW;AAAA,IACX,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AACJ;AAKO,IAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,IAAM,uBAAuB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;;;AD5PA,SAAS,OAAO,OAAe,YAAoB,GAAW;AAC1D,MAAI,MAAM,UAAU,YAAY,GAAG;AAC/B,WAAO,IAAI,OAAO,MAAM,MAAM;AAAA,EAClC;AACA,SAAO,MAAM,MAAM,GAAG,SAAS,IAAI,SAAS,MAAM,MAAM,CAAC,SAAS;AACtE;AAKA,SAAS,YAAY,SAAiB,OAAiD;AACnF,QAAM,QAAQ,QAAQ,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI;AAChD,SAAO;AAAA,IACH,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM,MAAM,SAAS,CAAC,EAAE,SAAS;AAAA,EAC7C;AACJ;AAKA,SAAS,aAAa,UAA2B;AAC7C,QAAM,aAAa,SAAS,QAAQ,OAAO,GAAG;AAC9C,SAAO,gBAAgB,KAAK,aAAW;AACnC,QAAI,QAAQ,WAAW,GAAG,GAAG;AACzB,aAAO,WAAW,SAAS,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC/C;AACA,WAAO,WAAW,SAAS,OAAO;AAAA,EACtC,CAAC;AACL;AAKA,SAAS,YAAY,UAA2B;AAC5C,QAAM,MAAW,aAAQ,QAAQ,EAAE,YAAY;AAC/C,SAAO,qBAAqB,SAAS,GAAG;AAC5C;AAKA,SAAS,SAAS,UAAkB,SAA8B;AAC9D,QAAM,SAAsB,CAAC;AAC7B,QAAM,eAAe;AAGrB,aAAW,WAAW,iBAAiB;AAEnC,YAAQ,QAAQ,YAAY;AAC5B,QAAI;AACJ,YAAQ,QAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AACrD,YAAM,MAAM,YAAY,SAAS,MAAM,KAAK;AAC5C,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,UAAU,QAAQ;AAAA,QAClB,MAAM,QAAQ;AAAA,QACd,UAAU,QAAQ;AAAA,QAClB,MAAM;AAAA,QACN,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,QACZ,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,MAC1B,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,aAAW,WAAW,cAAc;AAChC,YAAQ,QAAQ,YAAY;AAC5B,QAAI;AACJ,YAAQ,QAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AAErD,YAAM,WAAW,MAAM,CAAC;AACxB,UAAI,sBAAsB,UAAU,QAAQ,IAAI,GAAG;AAC/C;AAAA,MACJ;AAEA,YAAM,MAAM,YAAY,SAAS,MAAM,KAAK;AAC5C,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,UAAU,QAAQ;AAAA,QAClB,MAAM,QAAQ;AAAA,QACd,MAAM;AAAA,QACN,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,QACZ,OAAO,OAAO,UAAU,CAAC;AAAA,MAC7B,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,aAAW,WAAW,gBAAgB;AAClC,YAAQ,QAAQ,YAAY;AAC5B,QAAI;AACJ,YAAQ,QAAQ,QAAQ,QAAQ,KAAK,OAAO,OAAO,MAAM;AACrD,YAAM,MAAM,YAAY,SAAS,MAAM,KAAK;AAC5C,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,UAAU,QAAQ;AAAA,QAClB,MAAM,QAAQ;AAAA,QACd,MAAM;AAAA,QACN,MAAM,IAAI;AAAA,QACV,QAAQ,IAAI;AAAA,QACZ,OAAO,MAAM,CAAC;AAAA,QACd,aAAa,QAAQ;AAAA,MACzB,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,QAAM,WAAgB,cAAS,QAAQ;AACvC,MAAI,SAAS,WAAW,MAAM,KAAK,CAAC,SAAS,SAAS,UAAU,GAAG;AAE/D,UAAM,gBAAqB,UAAU,aAAQ,QAAQ,GAAG,YAAY;AACpE,UAAM,kBAAqB,cAAW,aAAa;AAEnD,WAAO,KAAK;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,aAAa,kBACP,sCACA;AAAA,IACV,CAAC;AAAA,EACL;AAEA,SAAO;AACX;AAKA,SAAS,sBAAsB,OAAe,aAA8B;AAExE,MAAI,gBAAgB,iBAAiB;AACjC,UAAM,eAAe;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AACA,QAAI,aAAa,KAAK,OAAK,MAAM,SAAS,CAAC,CAAC,GAAG;AAC3C,aAAO;AAAA,IACX;AAGA,UAAM,iBAAiB;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AACA,QAAI,eAAe,KAAK,OAAK,MAAM,YAAY,EAAE,WAAW,CAAC,CAAC,GAAG;AAC7D,aAAO;AAAA,IACX;AAAA,EACJ;AAGA,MAAI,gBAAgB,cAAc;AAC9B,UAAM,WAAW,CAAC,WAAW,aAAa,YAAY,SAAS,SAAS;AACxE,QAAI,SAAS,KAAK,QAAM,MAAM,WAAW,EAAE,CAAC,GAAG;AAC3C,aAAO;AAAA,IACX;AAAA,EACJ;AAGA,MAAI,YAAY,SAAS,cAAc,GAAG;AAEtC,QAAI,MAAM,SAAS,KAAK,KAAK,MAAM,SAAS,SAAS,KAAK,MAAM,SAAS,SAAS,GAAG;AACjF,aAAO;AAAA,IACX;AAAA,EACJ;AAEA,SAAO;AACX;AAKA,SAAS,eAAe,QAAkD;AACtE,QAAM,WAAW,OAAO,OAAO,OAAK,EAAE,aAAa,UAAU,EAAE;AAC/D,QAAM,OAAO,OAAO,OAAO,OAAK,EAAE,aAAa,MAAM,EAAE;AACvD,QAAM,SAAS,OAAO,OAAO,OAAK,EAAE,aAAa,QAAQ,EAAE;AAC3D,QAAM,QAAQ,OAAO;AAGrB,MAAI,WAAW,EAAG,QAAO;AACzB,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,QAAQ,KAAK,UAAU,EAAG,QAAO;AACrC,MAAI,UAAU,EAAG,QAAO;AACxB,MAAI,UAAU,EAAG,QAAO;AACxB,SAAO;AACX;AAKA,SAAS,mBAAmB,OAAuB;AAC/C,UAAQ,OAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX,KAAK;AACD,aAAO;AAAA,IACX;AACI,aAAO;AAAA,EACf;AACJ;AAKA,eAAsB,KAAK,YAAyC;AAChE,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,eAAoB,aAAQ,UAAU;AAG5C,QAAM,QAAQ,MAAM,KAAK,QAAQ;AAAA,IAC7B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACd,CAAC;AAED,QAAM,SAAsB,CAAC;AAC7B,MAAI,eAAe;AAEnB,aAAW,QAAQ,OAAO;AACtB,QAAI,CAAC,YAAY,IAAI,KAAK,aAAa,IAAI,GAAG;AAC1C;AAAA,IACJ;AAEA,QAAI;AACA,YAAM,UAAa,gBAAa,MAAM,OAAO;AAC7C,YAAM,eAAoB,cAAS,cAAc,IAAI;AACrD,YAAM,aAAa,SAAS,cAAc,OAAO;AACjD,aAAO,KAAK,GAAG,UAAU;AACzB;AAAA,IACJ,QAAQ;AAEJ;AAAA,IACJ;AAAA,EACJ;AAEA,QAAM,QAAQ,eAAe,MAAM;AACnC,QAAM,eAAe,KAAK,IAAI,IAAI;AAElC,SAAO;AAAA,IACH;AAAA,IACA,iBAAiB,mBAAmB,KAAK;AAAA,IACzC;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACL,SAAS,OAAO,OAAO,OAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,MACjD,KAAK,OAAO,OAAO,OAAK,EAAE,SAAS,KAAK,EAAE;AAAA,MAC1C,QAAQ,OAAO,OAAO,OAAK,EAAE,SAAS,OAAO,EAAE;AAAA,MAC/C,QAAQ,OAAO,OAAO,OAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,MAChD,UAAU,OAAO,OAAO,OAAK,EAAE,aAAa,UAAU,EAAE;AAAA,MACxD,MAAM,OAAO,OAAO,OAAK,EAAE,aAAa,MAAM,EAAE;AAAA,MAChD,QAAQ,OAAO,OAAO,OAAK,EAAE,aAAa,QAAQ,EAAE;AAAA,MACpD,KAAK,OAAO,OAAO,OAAK,EAAE,aAAa,KAAK,EAAE;AAAA,IAClD;AAAA,EACJ;AACJ;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cencori/scan",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Security scanner for AI apps. Detect hardcoded secrets, PII leaks, and exposed routes.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"cencori-scan": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.mjs",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"security",
|
|
24
|
+
"scanner",
|
|
25
|
+
"secrets",
|
|
26
|
+
"api-keys",
|
|
27
|
+
"pii",
|
|
28
|
+
"lovable",
|
|
29
|
+
"bolt",
|
|
30
|
+
"ai",
|
|
31
|
+
"cencori",
|
|
32
|
+
"vibe-check"
|
|
33
|
+
],
|
|
34
|
+
"author": "Cencori",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/cencori/cencori.git"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://scan.cencori.com",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"dev": "tsup --watch",
|
|
44
|
+
"start": "node dist/cli.js",
|
|
45
|
+
"test": "vitest"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"chalk": "^5.3.0",
|
|
49
|
+
"commander": "^12.0.0",
|
|
50
|
+
"glob": "^10.3.10",
|
|
51
|
+
"ora": "^8.0.1"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"tsup": "^8.0.0",
|
|
55
|
+
"typescript": "^5.3.0",
|
|
56
|
+
"@types/node": "^20.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|