@indicated/vibeguard 1.5.2 → 1.7.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/.claude/hooks/agentspace-notify.sh +103 -0
- package/.claude/settings.local.json +40 -1
- package/README.md +51 -3
- package/SECURITY_GAPS.md +160 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +238 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/scanner/parsers/python.d.ts.map +1 -1
- package/dist/scanner/parsers/python.js +110 -0
- package/dist/scanner/parsers/python.js.map +1 -1
- package/dist/scanner/rules/definitions.d.ts.map +1 -1
- package/dist/scanner/rules/definitions.js +463 -0
- package/dist/scanner/rules/definitions.js.map +1 -1
- package/package.json +16 -3
- package/src/mcp/server.ts +250 -0
- package/src/scanner/parsers/python.ts +117 -0
- package/src/scanner/rules/definitions.ts +482 -0
package/src/mcp/server.ts
CHANGED
|
@@ -245,6 +245,256 @@ function analyzeContext(finding: Finding, cwd: string): { signals: ContextSignal
|
|
|
245
245
|
question = 'Is this a public key (anon/publishable) or an actual secret? Supabase anon keys are safe to expose.';
|
|
246
246
|
break;
|
|
247
247
|
|
|
248
|
+
case 'insecure-randomness':
|
|
249
|
+
// Check if used for non-security purposes
|
|
250
|
+
if (finding.code.includes('color') || finding.code.includes('animation') ||
|
|
251
|
+
finding.code.includes('shuffle') || finding.code.includes('sample') ||
|
|
252
|
+
finding.code.includes('placeholder') || finding.code.includes('display')) {
|
|
253
|
+
signals.push({ signal: 'Appears to be used for non-security purposes (UI/display)', type: 'positive' });
|
|
254
|
+
confidence = 'low';
|
|
255
|
+
}
|
|
256
|
+
if (finding.code.includes('token') || finding.code.includes('secret') ||
|
|
257
|
+
finding.code.includes('password') || finding.code.includes('session') ||
|
|
258
|
+
finding.code.includes('nonce') || finding.code.includes('otp')) {
|
|
259
|
+
signals.push({ signal: 'Used for security-sensitive value generation', type: 'negative' });
|
|
260
|
+
confidence = 'high';
|
|
261
|
+
}
|
|
262
|
+
question = 'Is Math.random()/random used for security tokens or just UI/cosmetic purposes?';
|
|
263
|
+
break;
|
|
264
|
+
|
|
265
|
+
case 'weak-cryptography':
|
|
266
|
+
// Check if used for password hashing vs checksums
|
|
267
|
+
if (finding.code.includes('password') || finding.code.includes('secret') ||
|
|
268
|
+
finding.code.includes('token') || finding.code.includes('auth')) {
|
|
269
|
+
signals.push({ signal: 'Weak hash used for security-sensitive data', type: 'negative' });
|
|
270
|
+
confidence = 'high';
|
|
271
|
+
}
|
|
272
|
+
if (finding.code.includes('checksum') || finding.code.includes('etag') ||
|
|
273
|
+
finding.code.includes('cache') || finding.code.includes('fingerprint')) {
|
|
274
|
+
signals.push({ signal: 'May be used for non-security checksum/cache key', type: 'positive' });
|
|
275
|
+
confidence = 'low';
|
|
276
|
+
}
|
|
277
|
+
question = 'Is MD5/SHA1 used for security (bad) or for checksums/cache keys (acceptable)?';
|
|
278
|
+
break;
|
|
279
|
+
|
|
280
|
+
case 'nosql-injection':
|
|
281
|
+
// Check for input sanitization
|
|
282
|
+
if (fileContent.includes('mongo-sanitize') || fileContent.includes('express-mongo-sanitize') ||
|
|
283
|
+
fileContent.includes('sanitize') || fileContent.includes('validator')) {
|
|
284
|
+
signals.push({ signal: 'File uses input sanitization library', type: 'positive' });
|
|
285
|
+
confidence = 'low';
|
|
286
|
+
}
|
|
287
|
+
if (finding.code.includes('req.body') || finding.code.includes('req.query')) {
|
|
288
|
+
signals.push({ signal: 'User input passed directly to query', type: 'negative' });
|
|
289
|
+
confidence = 'high';
|
|
290
|
+
}
|
|
291
|
+
question = 'Is user input sanitized before being used in the NoSQL query?';
|
|
292
|
+
break;
|
|
293
|
+
|
|
294
|
+
case 'disabled-tls-verification':
|
|
295
|
+
if (fileContent.includes('development') || fileContent.includes('dev') ||
|
|
296
|
+
fileContent.includes('node_env') || fileContent.includes('test')) {
|
|
297
|
+
signals.push({ signal: 'May be conditionally enabled for development only', type: 'positive' });
|
|
298
|
+
confidence = 'medium';
|
|
299
|
+
} else {
|
|
300
|
+
signals.push({ signal: 'TLS verification unconditionally disabled', type: 'negative' });
|
|
301
|
+
confidence = 'high';
|
|
302
|
+
}
|
|
303
|
+
question = 'Is TLS verification only disabled in development, or also in production?';
|
|
304
|
+
break;
|
|
305
|
+
|
|
306
|
+
case 'unsafe-regex-construction':
|
|
307
|
+
if (fileContent.includes('escaperegex') || fileContent.includes('escape-string-regexp') ||
|
|
308
|
+
fileContent.includes('escaperegexp') || fileContent.includes('lodash') && fileContent.includes('escaperegexp')) {
|
|
309
|
+
signals.push({ signal: 'File imports regex escaping utility', type: 'positive' });
|
|
310
|
+
confidence = 'low';
|
|
311
|
+
}
|
|
312
|
+
if (finding.code.includes('req.') || finding.code.includes('query.') ||
|
|
313
|
+
finding.code.includes('params.') || finding.code.includes('body.')) {
|
|
314
|
+
signals.push({ signal: 'User input used directly in RegExp constructor', type: 'negative' });
|
|
315
|
+
confidence = 'high';
|
|
316
|
+
}
|
|
317
|
+
question = 'Is the user input escaped before being used in the RegExp constructor?';
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case 'postmessage-no-origin':
|
|
321
|
+
if (fileContent.includes('event.origin') || fileContent.includes('e.origin') ||
|
|
322
|
+
fileContent.includes('msg.origin')) {
|
|
323
|
+
signals.push({ signal: 'File checks origin elsewhere (may not be in handler)', type: 'positive' });
|
|
324
|
+
confidence = 'medium';
|
|
325
|
+
} else {
|
|
326
|
+
signals.push({ signal: 'No origin check found in file', type: 'negative' });
|
|
327
|
+
confidence = 'high';
|
|
328
|
+
}
|
|
329
|
+
question = 'Does the message event handler validate event.origin before processing data?';
|
|
330
|
+
break;
|
|
331
|
+
|
|
332
|
+
case 'hardcoded-db-credentials':
|
|
333
|
+
if (finding.code.includes('localhost') || finding.code.includes('127.0.0.1') ||
|
|
334
|
+
finding.code.includes('example.com') || finding.code.includes('test')) {
|
|
335
|
+
signals.push({ signal: 'Connection string points to localhost/test (likely development)', type: 'positive' });
|
|
336
|
+
confidence = 'low';
|
|
337
|
+
}
|
|
338
|
+
if (finding.code.includes('.env') || fileContent.includes('process.env') ||
|
|
339
|
+
fileContent.includes('os.environ')) {
|
|
340
|
+
signals.push({ signal: 'File also uses environment variables (may be fallback)', type: 'positive' });
|
|
341
|
+
confidence = 'medium';
|
|
342
|
+
}
|
|
343
|
+
question = 'Is this a development-only connection string, or does it contain production credentials?';
|
|
344
|
+
break;
|
|
345
|
+
|
|
346
|
+
case 'ssti-vulnerability':
|
|
347
|
+
signals.push({ signal: 'User-controlled template rendering is almost always dangerous', type: 'negative' });
|
|
348
|
+
confidence = 'high';
|
|
349
|
+
question = 'Is user input being rendered as a template? This is nearly always a critical vulnerability.';
|
|
350
|
+
break;
|
|
351
|
+
|
|
352
|
+
case 'timing-attack':
|
|
353
|
+
if (finding.code.includes('timingsafeequal') || finding.code.includes('compare_digest') ||
|
|
354
|
+
fileContent.includes('timingsafeequal') || fileContent.includes('compare_digest')) {
|
|
355
|
+
signals.push({ signal: 'File uses constant-time comparison elsewhere', type: 'positive' });
|
|
356
|
+
confidence = 'low';
|
|
357
|
+
}
|
|
358
|
+
if (finding.code.includes('===') && (finding.code.includes('token') || finding.code.includes('secret'))) {
|
|
359
|
+
signals.push({ signal: 'Direct === comparison of secret values', type: 'negative' });
|
|
360
|
+
confidence = 'medium';
|
|
361
|
+
}
|
|
362
|
+
question = 'Is this comparing secrets/tokens? If so, use constant-time comparison.';
|
|
363
|
+
break;
|
|
364
|
+
|
|
365
|
+
case 'electron-insecure-config':
|
|
366
|
+
if (finding.code.includes('nodeIntegration') && finding.code.includes('true')) {
|
|
367
|
+
signals.push({ signal: 'nodeIntegration enabled exposes Node.js APIs to web content', type: 'negative' });
|
|
368
|
+
confidence = 'high';
|
|
369
|
+
}
|
|
370
|
+
if (finding.code.includes('contextIsolation') && finding.code.includes('false')) {
|
|
371
|
+
signals.push({ signal: 'contextIsolation disabled allows prototype pollution from web content', type: 'negative' });
|
|
372
|
+
confidence = 'high';
|
|
373
|
+
}
|
|
374
|
+
question = 'Are these Electron security settings intentionally relaxed? This is dangerous for apps loading remote content.';
|
|
375
|
+
break;
|
|
376
|
+
|
|
377
|
+
case 'mass-assignment':
|
|
378
|
+
if (fileContent.includes('whitelist') || fileContent.includes('allowedfields') ||
|
|
379
|
+
fileContent.includes('pick') || fileContent.includes('only')) {
|
|
380
|
+
signals.push({ signal: 'File may use field whitelisting', type: 'positive' });
|
|
381
|
+
confidence = 'medium';
|
|
382
|
+
}
|
|
383
|
+
if (finding.code.includes('req.body') || finding.code.includes('request.data')) {
|
|
384
|
+
signals.push({ signal: 'Full request body passed to ORM operation', type: 'negative' });
|
|
385
|
+
confidence = 'high';
|
|
386
|
+
}
|
|
387
|
+
question = 'Is the user input filtered/whitelisted before being passed to the ORM create/update?';
|
|
388
|
+
break;
|
|
389
|
+
|
|
390
|
+
case 'jwt-missing-exp':
|
|
391
|
+
if (fileContent.includes('expiresIn') || fileContent.includes('exp:') || fileContent.includes("'exp'")) {
|
|
392
|
+
signals.push({ signal: 'File sets expiration elsewhere', type: 'positive' });
|
|
393
|
+
confidence = 'low';
|
|
394
|
+
} else {
|
|
395
|
+
signals.push({ signal: 'No expiration configuration found in file', type: 'negative' });
|
|
396
|
+
confidence = 'high';
|
|
397
|
+
}
|
|
398
|
+
question = 'Does the JWT payload include an exp claim, or is expiresIn set elsewhere?';
|
|
399
|
+
break;
|
|
400
|
+
|
|
401
|
+
case 'jwt-weak-secret':
|
|
402
|
+
if (finding.code.includes('process.env') || finding.code.includes('os.environ') ||
|
|
403
|
+
finding.code.includes('config.')) {
|
|
404
|
+
signals.push({ signal: 'Secret loaded from environment/config', type: 'positive' });
|
|
405
|
+
confidence = 'low';
|
|
406
|
+
}
|
|
407
|
+
if (/['"`][^'"`]{1,15}['"`]/.test(finding.code)) {
|
|
408
|
+
signals.push({ signal: 'Short hardcoded string used as signing secret', type: 'negative' });
|
|
409
|
+
confidence = 'high';
|
|
410
|
+
}
|
|
411
|
+
question = 'Is the JWT signing secret loaded from environment variables or hardcoded?';
|
|
412
|
+
break;
|
|
413
|
+
|
|
414
|
+
case 'csp-unsafe-inline':
|
|
415
|
+
if (fileContent.includes('nonce') || fileContent.includes('hash-')) {
|
|
416
|
+
signals.push({ signal: 'File uses nonce or hash-based CSP (may be transitioning)', type: 'positive' });
|
|
417
|
+
confidence = 'medium';
|
|
418
|
+
}
|
|
419
|
+
if (finding.code.includes('unsafe-eval')) {
|
|
420
|
+
signals.push({ signal: 'unsafe-eval allows arbitrary script execution', type: 'negative' });
|
|
421
|
+
confidence = 'high';
|
|
422
|
+
}
|
|
423
|
+
if (finding.code.includes('unsafe-inline')) {
|
|
424
|
+
signals.push({ signal: 'unsafe-inline defeats XSS protection from CSP', type: 'negative' });
|
|
425
|
+
confidence = 'high';
|
|
426
|
+
}
|
|
427
|
+
question = 'Is unsafe-inline/unsafe-eval required for third-party scripts, or can nonces/hashes be used instead?';
|
|
428
|
+
break;
|
|
429
|
+
|
|
430
|
+
case 'cors-credentials-wildcard':
|
|
431
|
+
signals.push({ signal: 'Wildcard origin with credentials allows any site to make authenticated requests', type: 'negative' });
|
|
432
|
+
confidence = 'high';
|
|
433
|
+
question = 'Should this API be accessible from any origin with credentials? Restrict to specific trusted origins.';
|
|
434
|
+
break;
|
|
435
|
+
|
|
436
|
+
case 'password-hash-weak':
|
|
437
|
+
if (fileContent.includes('bcrypt') || fileContent.includes('scrypt') || fileContent.includes('argon2')) {
|
|
438
|
+
signals.push({ signal: 'File also uses a proper KDF (may be migrating)', type: 'positive' });
|
|
439
|
+
confidence = 'medium';
|
|
440
|
+
} else {
|
|
441
|
+
signals.push({ signal: 'No proper password KDF found in file', type: 'negative' });
|
|
442
|
+
confidence = 'high';
|
|
443
|
+
}
|
|
444
|
+
question = 'Is this hashing passwords for storage? Use bcrypt/scrypt/argon2 instead of raw hash functions.';
|
|
445
|
+
break;
|
|
446
|
+
|
|
447
|
+
case 'password-plaintext-storage':
|
|
448
|
+
if (fileContent.includes('bcrypt') || fileContent.includes('hash') ||
|
|
449
|
+
fileContent.includes('scrypt') || fileContent.includes('argon2')) {
|
|
450
|
+
signals.push({ signal: 'File contains hashing logic (may be applied before this line)', type: 'positive' });
|
|
451
|
+
confidence = 'medium';
|
|
452
|
+
} else {
|
|
453
|
+
signals.push({ signal: 'No password hashing found in file', type: 'negative' });
|
|
454
|
+
confidence = 'high';
|
|
455
|
+
}
|
|
456
|
+
question = 'Is the password hashed before being stored? Check if bcrypt.hash() or similar is called upstream.';
|
|
457
|
+
break;
|
|
458
|
+
|
|
459
|
+
case 'zip-slip':
|
|
460
|
+
if (fileContent.includes('path.resolve') || fileContent.includes('path.normalize') ||
|
|
461
|
+
fileContent.includes('os.path.abspath') || fileContent.includes('startswith')) {
|
|
462
|
+
signals.push({ signal: 'File has path validation logic', type: 'positive' });
|
|
463
|
+
confidence = 'low';
|
|
464
|
+
} else {
|
|
465
|
+
signals.push({ signal: 'No path validation found before extraction', type: 'negative' });
|
|
466
|
+
confidence = 'high';
|
|
467
|
+
}
|
|
468
|
+
question = 'Are extracted file paths validated to prevent writing outside the target directory?';
|
|
469
|
+
break;
|
|
470
|
+
|
|
471
|
+
case 'http-client-no-timeout':
|
|
472
|
+
if (fileContent.includes('timeout') || fileContent.includes('AbortController') ||
|
|
473
|
+
fileContent.includes('signal')) {
|
|
474
|
+
signals.push({ signal: 'File configures timeout elsewhere (may be set globally)', type: 'positive' });
|
|
475
|
+
confidence = 'low';
|
|
476
|
+
} else {
|
|
477
|
+
signals.push({ signal: 'No timeout configuration found in file', type: 'negative' });
|
|
478
|
+
confidence = 'medium';
|
|
479
|
+
}
|
|
480
|
+
question = 'Is a timeout configured globally (e.g., via session defaults) or should one be added per-request?';
|
|
481
|
+
break;
|
|
482
|
+
|
|
483
|
+
case 's3-public-read':
|
|
484
|
+
if (relativePath.includes('test') || relativePath.includes('example') || relativePath.includes('sample')) {
|
|
485
|
+
signals.push({ signal: 'File appears to be test/example code', type: 'positive' });
|
|
486
|
+
confidence = 'low';
|
|
487
|
+
}
|
|
488
|
+
if (fileContent.includes('public') && (fileContent.includes('static') || fileContent.includes('assets') || fileContent.includes('cdn'))) {
|
|
489
|
+
signals.push({ signal: 'May be intentional public bucket for static assets/CDN', type: 'positive' });
|
|
490
|
+
confidence = 'medium';
|
|
491
|
+
} else {
|
|
492
|
+
signals.push({ signal: 'S3 bucket with public access policy', type: 'negative' });
|
|
493
|
+
confidence = 'high';
|
|
494
|
+
}
|
|
495
|
+
question = 'Is this S3 bucket intentionally public (static assets/CDN) or should access be restricted?';
|
|
496
|
+
break;
|
|
497
|
+
|
|
248
498
|
default:
|
|
249
499
|
question = `Verify if this ${finding.rule.name} finding is a real security issue in your specific context.`;
|
|
250
500
|
}
|
|
@@ -34,6 +34,123 @@ const pythonPatterns: { ruleId: string; pattern: RegExp }[] = [
|
|
|
34
34
|
ruleId: 'verbose-errors',
|
|
35
35
|
pattern: /app\.run\s*\([^)]*debug\s*=\s*True/i,
|
|
36
36
|
},
|
|
37
|
+
// Insecure randomness - Python specific
|
|
38
|
+
{
|
|
39
|
+
ruleId: 'insecure-randomness',
|
|
40
|
+
pattern: /(?:token|key|secret|session|nonce|salt|otp|password)\s*=\s*['"`]?.*?random\.(?:choice|randint|sample)\s*\(/i,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
ruleId: 'insecure-randomness',
|
|
44
|
+
pattern: /random\.(?:random|randint|choice|getrandbits)\s*\(\s*\).*(?:token|key|secret|session|password|nonce)/i,
|
|
45
|
+
},
|
|
46
|
+
// Weak cryptography - Python hashlib
|
|
47
|
+
{
|
|
48
|
+
ruleId: 'weak-cryptography',
|
|
49
|
+
pattern: /hashlib\.(?:md5|sha1)\s*\(\s*(?:password|secret|token)/i,
|
|
50
|
+
},
|
|
51
|
+
// Python SSTI - render_template_string
|
|
52
|
+
{
|
|
53
|
+
ruleId: 'ssti-vulnerability',
|
|
54
|
+
pattern: /render_template_string\s*\(\s*(?:request\.(?:args|form|data|values)|f['"`])/,
|
|
55
|
+
},
|
|
56
|
+
// Python assert for security
|
|
57
|
+
{
|
|
58
|
+
ruleId: 'python-assert-security',
|
|
59
|
+
pattern: /^\s*assert\s+.*(?:is_admin|is_authenticated|is_staff|has_perm|permission|authorized)/m,
|
|
60
|
+
},
|
|
61
|
+
// Unsafe tempfile
|
|
62
|
+
{
|
|
63
|
+
ruleId: 'unsafe-tempfile',
|
|
64
|
+
pattern: /tempfile\.mktemp\s*\(/,
|
|
65
|
+
},
|
|
66
|
+
// Python timing attack
|
|
67
|
+
{
|
|
68
|
+
ruleId: 'timing-attack',
|
|
69
|
+
pattern: /(?:token|secret|password|api_key|signature)\s*==\s*(?:request\.|data\[|params\[)/i,
|
|
70
|
+
},
|
|
71
|
+
// Python NoSQL injection (PyMongo)
|
|
72
|
+
{
|
|
73
|
+
ruleId: 'nosql-injection',
|
|
74
|
+
pattern: /\.find(?:_one)?\s*\(\s*(?:request\.(?:json|data|form|args)|json\.loads)/,
|
|
75
|
+
},
|
|
76
|
+
// Python mass assignment
|
|
77
|
+
{
|
|
78
|
+
ruleId: 'mass-assignment',
|
|
79
|
+
pattern: /\.objects\.create\s*\(\s*\*\*request\.(?:data|POST)/,
|
|
80
|
+
},
|
|
81
|
+
// Python log injection
|
|
82
|
+
{
|
|
83
|
+
ruleId: 'log-injection',
|
|
84
|
+
pattern: /(?:logger|logging)\.(?:info|warn|warning|error|debug)\s*\(\s*f?['"`][^'"`]*\{(?:request\.|req\.|user_input|data\[)/,
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// JWT missing expiration - Python (PyJWT)
|
|
88
|
+
{
|
|
89
|
+
ruleId: 'jwt-missing-exp',
|
|
90
|
+
pattern: /jwt\.encode\s*\(\s*\{(?![^}]*['"`]exp['"`])[^}]*\}\s*,/,
|
|
91
|
+
},
|
|
92
|
+
// JWT weak secret - Python
|
|
93
|
+
{
|
|
94
|
+
ruleId: 'jwt-weak-secret',
|
|
95
|
+
pattern: /jwt\.encode\s*\([^,]+,\s*['"`][^'"`]{1,15}['"`]/,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
ruleId: 'jwt-weak-secret',
|
|
99
|
+
pattern: /jwt\.decode\s*\([^,]+,\s*['"`][^'"`]{1,15}['"`]/,
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
// CORS credentials wildcard - FastAPI/Flask
|
|
103
|
+
{
|
|
104
|
+
ruleId: 'cors-credentials-wildcard',
|
|
105
|
+
pattern: /allow_origins\s*=\s*\[\s*['"`]\*['"`]\s*\][\s\S]{0,100}allow_credentials\s*=\s*True/,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
ruleId: 'cors-credentials-wildcard',
|
|
109
|
+
pattern: /allow_credentials\s*=\s*True[\s\S]{0,100}allow_origins\s*=\s*\[\s*['"`]\*['"`]\s*\]/,
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// Password hash weak - Python hashlib for passwords
|
|
113
|
+
{
|
|
114
|
+
ruleId: 'password-hash-weak',
|
|
115
|
+
pattern: /hashlib\.(?:md5|sha1|sha256)\s*\(\s*(?:password|passwd|pass|pwd)/i,
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// Password plaintext storage - Django/SQLAlchemy
|
|
119
|
+
{
|
|
120
|
+
ruleId: 'password-plaintext-storage',
|
|
121
|
+
pattern: /\.(?:create|create_user)\s*\([^)]*password\s*=\s*(?:request\.(?:data|POST)\[?['"`]?password|data\[['"`]password)/,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
ruleId: 'password-plaintext-storage',
|
|
125
|
+
pattern: /\.password\s*=\s*(?:request\.(?:data|POST|form)\[?['"`]?password|data\[['"`]password)/,
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// Zip slip - Python zipfile/tarfile
|
|
129
|
+
{
|
|
130
|
+
ruleId: 'zip-slip',
|
|
131
|
+
pattern: /(?:ZipFile|zipfile\.ZipFile)\s*\([^)]*\)\.extractall\s*\(/,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
ruleId: 'zip-slip',
|
|
135
|
+
pattern: /tarfile\.open\s*\([^)]*\)\.extractall\s*\(/,
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// HTTP client no timeout - Python requests
|
|
139
|
+
{
|
|
140
|
+
ruleId: 'http-client-no-timeout',
|
|
141
|
+
pattern: /requests\.(?:get|post|put|delete|patch|head)\s*\(\s*[^)]*\)(?<![^)]*timeout)/,
|
|
142
|
+
},
|
|
143
|
+
// urllib without timeout
|
|
144
|
+
{
|
|
145
|
+
ruleId: 'http-client-no-timeout',
|
|
146
|
+
pattern: /urllib\.request\.urlopen\s*\(\s*[^)]*\)(?<![^)]*timeout)/,
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// S3 public read - boto3
|
|
150
|
+
{
|
|
151
|
+
ruleId: 's3-public-read',
|
|
152
|
+
pattern: /ACL\s*=\s*['"`]public-read(?:-write)?['"`]/,
|
|
153
|
+
},
|
|
37
154
|
];
|
|
38
155
|
|
|
39
156
|
export function scanPythonWithPatterns(
|