@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.
@@ -354,6 +354,175 @@ export const securityRules: SecurityRule[] = [
354
354
  fix: 'Always specify allowed algorithms explicitly and never include "none"',
355
355
  },
356
356
 
357
+ // CRITICAL (Free tier) - New rules
358
+ {
359
+ id: 'insecure-randomness',
360
+ name: 'Insecure Randomness for Security',
361
+ description: 'Using Math.random() or random module for security-sensitive operations (tokens, IDs, passwords) is predictable',
362
+ severity: 'critical',
363
+ tier: 'free',
364
+ languages: ['javascript', 'typescript', 'python'],
365
+ patterns: [
366
+ // JS: Math.random() used for token/key/secret/session/id generation
367
+ /(?:token|key|secret|session|nonce|salt|otp|password|uuid|id)\s*(?:=|:)\s*(?:.*)?Math\.random\s*\(/i,
368
+ /Math\.random\s*\(\s*\)\.toString\s*\(\s*(?:16|36)\s*\)/,
369
+ // Python: random module for security
370
+ /(?:token|key|secret|session|nonce|salt|otp|password)\s*=\s*.*random\.(?:random|randint|choice|randrange|getrandbits)\s*\(/i,
371
+ ],
372
+ fix: 'Use crypto.randomBytes()/crypto.randomUUID() in Node.js or secrets module in Python',
373
+ },
374
+ {
375
+ id: 'weak-cryptography',
376
+ name: 'Weak Cryptographic Algorithm',
377
+ description: 'MD5 and SHA1 are cryptographically broken and should not be used for security purposes',
378
+ severity: 'critical',
379
+ tier: 'free',
380
+ languages: ['javascript', 'typescript', 'python'],
381
+ patterns: [
382
+ // JS: crypto.createHash with weak algo
383
+ /crypto\.createHash\s*\(\s*['"`](?:md5|sha1|md4|ripemd160)['"`]\s*\)/i,
384
+ // Python: hashlib with weak algo
385
+ /hashlib\.(?:md5|sha1|new\s*\(\s*['"`](?:md5|sha1)['"`])\s*\(/,
386
+ // Direct MD5/SHA1 imports in Python
387
+ /from\s+hashlib\s+import\s+(?:md5|sha1)/,
388
+ ],
389
+ fix: 'Use SHA-256+ for hashing, bcrypt/scrypt/argon2 for passwords. Replace MD5/SHA1 with stronger alternatives',
390
+ },
391
+ {
392
+ id: 'nosql-injection',
393
+ name: 'NoSQL Injection Vulnerability',
394
+ description: 'User input passed directly to NoSQL queries can allow query manipulation via operators like $gt, $ne',
395
+ severity: 'critical',
396
+ tier: 'free',
397
+ languages: ['javascript', 'typescript', 'python'],
398
+ patterns: [
399
+ // MongoDB find/update with req.body directly
400
+ /\.(?:find|findOne|findOneAndUpdate|updateOne|updateMany|deleteOne|deleteMany)\s*\(\s*(?:req\.body|req\.query|req\.params)/,
401
+ // MongoDB where clause with user input
402
+ /\$where\s*:\s*(?:req\.|params\.|query\.|body\.)/,
403
+ // Direct user input in MongoDB query object
404
+ /\.(?:find|findOne)\s*\(\s*\{[^}]*:\s*(?:req\.body|req\.query|req\.params)\s*\./,
405
+ ],
406
+ fix: 'Sanitize user input with mongo-sanitize, validate types explicitly, never pass req.body directly to queries',
407
+ },
408
+
409
+ // HIGH (Free tier) - New rules
410
+ {
411
+ id: 'disabled-tls-verification',
412
+ name: 'TLS Certificate Verification Disabled',
413
+ description: 'Disabling SSL/TLS certificate verification makes connections vulnerable to man-in-the-middle attacks',
414
+ severity: 'high',
415
+ tier: 'free',
416
+ languages: ['javascript', 'typescript', 'python'],
417
+ patterns: [
418
+ // Node.js
419
+ /NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"`]0['"`]/,
420
+ /rejectUnauthorized\s*:\s*false/,
421
+ // Python requests
422
+ /requests\.(?:get|post|put|delete|patch|head)\s*\([^)]*verify\s*=\s*False/,
423
+ // Python urllib3
424
+ /urllib3\.disable_warnings/,
425
+ /ssl\._create_unverified_context/,
426
+ // Generic SSL verification disable
427
+ /VERIFY_SSL\s*[=:]\s*(?:false|False|0)/i,
428
+ ],
429
+ fix: 'Never disable TLS certificate verification in production. Use proper certificates instead',
430
+ },
431
+ {
432
+ id: 'unsafe-regex-construction',
433
+ name: 'Unsafe Regex from User Input',
434
+ description: 'Constructing RegExp from user input can cause ReDoS (Regular Expression Denial of Service)',
435
+ severity: 'high',
436
+ tier: 'free',
437
+ languages: ['javascript', 'typescript'],
438
+ patterns: [
439
+ /new\s+RegExp\s*\(\s*(?:req\.(?:body|query|params)|params\.|query\.|body\.)/,
440
+ /new\s+RegExp\s*\(\s*(?:searchTerm|userInput|input|pattern|filter|search|term|keyword)/i,
441
+ ],
442
+ fix: 'Escape user input before using in RegExp, or use a fixed set of allowed patterns',
443
+ },
444
+ {
445
+ id: 'postmessage-no-origin',
446
+ name: 'postMessage Without Origin Validation',
447
+ description: 'Listening to postMessage events without checking origin accepts messages from any domain',
448
+ severity: 'high',
449
+ tier: 'free',
450
+ languages: ['javascript', 'typescript'],
451
+ patterns: [
452
+ // addEventListener for message without origin check nearby
453
+ /addEventListener\s*\(\s*['"`]message['"`]\s*,\s*(?:function|\([^)]*\)\s*=>|\w+\s*=>)\s*\{(?![^}]{0,200}(?:origin|source))/,
454
+ /\.on\s*\(\s*['"`]message['"`]\s*,\s*(?:function|\([^)]*\)\s*=>)\s*\{(?![^}]{0,200}(?:origin|source))/,
455
+ ],
456
+ fix: 'Always validate event.origin against a whitelist of trusted domains',
457
+ },
458
+ {
459
+ id: 'hardcoded-db-credentials',
460
+ name: 'Hardcoded Database Connection String',
461
+ description: 'Database connection strings with embedded credentials can be extracted from source code',
462
+ severity: 'high',
463
+ tier: 'free',
464
+ languages: ['javascript', 'typescript', 'python'],
465
+ patterns: [
466
+ // MongoDB connection string with credentials
467
+ /['"`]mongodb(?:\+srv)?:\/\/[^:]+:[^@]+@[^'"`]+['"`]/,
468
+ // PostgreSQL connection string with credentials
469
+ /['"`]postgres(?:ql)?:\/\/[^:]+:[^@]+@[^'"`]+['"`]/,
470
+ // MySQL connection string with credentials
471
+ /['"`]mysql:\/\/[^:]+:[^@]+@[^'"`]+['"`]/,
472
+ // Redis with password
473
+ /['"`]redis:\/\/[^:]*:[^@]+@[^'"`]+['"`]/,
474
+ ],
475
+ fix: 'Use environment variables for database connection strings. Never embed credentials in code',
476
+ },
477
+ {
478
+ id: 'ssti-vulnerability',
479
+ name: 'Server-Side Template Injection (SSTI)',
480
+ description: 'Rendering user-supplied template strings can lead to remote code execution',
481
+ severity: 'high',
482
+ tier: 'free',
483
+ languages: ['javascript', 'typescript', 'python'],
484
+ patterns: [
485
+ // Python: render_template_string with user input
486
+ /render_template_string\s*\(\s*(?:request\.|req\.|data\[)/,
487
+ // Python: Jinja2 Template from user input
488
+ /Template\s*\(\s*(?:request\.|req\.|data\[|user_input)/,
489
+ // JS: ejs/pug render with user-controlled template
490
+ /(?:ejs|pug)\.render\s*\(\s*(?:req\.(?:body|query|params)|body\.|params\.)/,
491
+ // Generic template rendering with user input
492
+ /\.render(?:String)?\s*\(\s*(?:req\.body|req\.query|req\.params)\./,
493
+ ],
494
+ fix: 'Never render user-supplied template strings. Use pre-defined templates with variable substitution only',
495
+ },
496
+
497
+ // MEDIUM (Free tier) - New rules
498
+ {
499
+ id: 'insecure-websocket',
500
+ name: 'Insecure WebSocket Connection (ws://)',
501
+ description: 'Unencrypted WebSocket connections can be intercepted, similar to HTTP vs HTTPS',
502
+ severity: 'medium',
503
+ tier: 'free',
504
+ languages: ['javascript', 'typescript', 'python'],
505
+ patterns: [
506
+ /['"`]ws:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)[^'"`]+['"`]/,
507
+ /WebSocket\s*\(\s*['"`]ws:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)/,
508
+ ],
509
+ fix: 'Use wss:// for encrypted WebSocket connections',
510
+ },
511
+ {
512
+ id: 'timing-attack',
513
+ name: 'Timing Attack on Secret Comparison',
514
+ description: 'Using === to compare secrets allows timing attacks that can leak token values byte by byte',
515
+ severity: 'medium',
516
+ tier: 'free',
517
+ languages: ['javascript', 'typescript', 'python'],
518
+ patterns: [
519
+ // JS: direct comparison of tokens/secrets
520
+ /(?:token|secret|apiKey|api_key|password|hash|signature|hmac|digest)\s*(?:===|!==)\s*(?:req\.|body\.|params\.|query\.|expected|stored|saved)/i,
521
+ /(?:req\.|body\.|params\.|query\.)\w*(?:token|secret|key|password|hash|signature)\s*(?:===|!==)/i,
522
+ ],
523
+ fix: 'Use crypto.timingSafeEqual() in Node.js or hmac.compare_digest() in Python for constant-time comparison',
524
+ },
525
+
357
526
  // LOW (Free tier)
358
527
  {
359
528
  id: 'verbose-errors',
@@ -691,6 +860,319 @@ export const securityRules: SecurityRule[] = [
691
860
  ],
692
861
  fix: 'Configure session with secure options: { cookie: { secure: true, httpOnly: true, sameSite: "strict" } }',
693
862
  },
863
+
864
+ // --- Flask ---
865
+ {
866
+ id: 'flask-secret-key-exposed',
867
+ name: 'Flask SECRET_KEY Hardcoded',
868
+ description: 'Hardcoded Flask SECRET_KEY can be used to forge session cookies and CSRF tokens',
869
+ severity: 'critical',
870
+ tier: 'pro',
871
+ languages: ['python'],
872
+ patterns: [
873
+ /app\.secret_key\s*=\s*['"`][^'"`]{8,}['"`]/,
874
+ /SECRET_KEY\s*=\s*['"`][^'"`]{8,}['"`](?!.*(?:os\.environ|os\.getenv|env\())/,
875
+ ],
876
+ fix: 'Load SECRET_KEY from environment variable: app.secret_key = os.environ.get("SECRET_KEY")',
877
+ },
878
+
879
+ // --- Prisma ---
880
+ {
881
+ id: 'prisma-raw-query',
882
+ name: 'Prisma Raw Query with User Input',
883
+ description: 'Raw SQL in Prisma with template literals can lead to SQL injection',
884
+ severity: 'high',
885
+ tier: 'pro',
886
+ languages: ['javascript', 'typescript'],
887
+ patterns: [
888
+ /\$queryRaw\s*`[^`]*\$\{/,
889
+ /\$executeRaw\s*`[^`]*\$\{/,
890
+ /\$queryRawUnsafe\s*\(/,
891
+ /\$executeRawUnsafe\s*\(/,
892
+ ],
893
+ fix: 'Use Prisma.$queryRaw with Prisma.sql tagged template or parameterized queries',
894
+ },
895
+
896
+ // --- Electron ---
897
+ {
898
+ id: 'electron-insecure-config',
899
+ name: 'Electron Insecure Configuration',
900
+ description: 'Insecure Electron settings can allow remote code execution via web content',
901
+ severity: 'high',
902
+ tier: 'pro',
903
+ languages: ['javascript', 'typescript'],
904
+ patterns: [
905
+ /nodeIntegration\s*:\s*true/,
906
+ /contextIsolation\s*:\s*false/,
907
+ /webSecurity\s*:\s*false/,
908
+ /allowRunningInsecureContent\s*:\s*true/,
909
+ ],
910
+ fix: 'Keep nodeIntegration: false, contextIsolation: true, webSecurity: true in BrowserWindow options',
911
+ },
912
+
913
+ // --- GraphQL ---
914
+ {
915
+ id: 'graphql-introspection-enabled',
916
+ name: 'GraphQL Introspection Enabled',
917
+ description: 'GraphQL introspection exposes your entire API schema, aiding attackers in discovering endpoints',
918
+ severity: 'medium',
919
+ tier: 'pro',
920
+ languages: ['javascript', 'typescript', 'python'],
921
+ patterns: [
922
+ /introspection\s*:\s*true/,
923
+ // Apollo server without explicitly disabling introspection in production
924
+ /new\s+ApolloServer\s*\(\s*\{(?![^}]*introspection\s*:\s*false)/,
925
+ ],
926
+ fix: 'Disable introspection in production: introspection: process.env.NODE_ENV !== "production"',
927
+ },
928
+
929
+ // --- Python-specific ---
930
+ {
931
+ id: 'python-assert-security',
932
+ name: 'Python Assert for Security Check',
933
+ description: 'Assert statements are stripped with -O flag, making security checks ineffective in optimized mode',
934
+ severity: 'medium',
935
+ tier: 'pro',
936
+ languages: ['python'],
937
+ patterns: [
938
+ /assert\s+(?:request\.user|current_user|user)\.(?:is_admin|is_authenticated|is_staff|is_superuser|has_perm)/,
939
+ /assert\s+(?:is_authenticated|is_authorized|has_permission|check_permission)\s*\(/,
940
+ ],
941
+ fix: 'Use if/raise instead of assert for security checks: if not user.is_admin: raise PermissionError()',
942
+ },
943
+ {
944
+ id: 'unsafe-tempfile',
945
+ name: 'Unsafe Temporary File Creation',
946
+ description: 'tempfile.mktemp() is vulnerable to race conditions (TOCTOU). An attacker can create a file at the path between creation and use',
947
+ severity: 'medium',
948
+ tier: 'pro',
949
+ languages: ['python'],
950
+ patterns: [
951
+ /tempfile\.mktemp\s*\(/,
952
+ ],
953
+ fix: 'Use tempfile.mkstemp() or tempfile.NamedTemporaryFile() instead',
954
+ },
955
+
956
+ // --- Mass Assignment ---
957
+ {
958
+ id: 'mass-assignment',
959
+ name: 'Mass Assignment / Over-posting Vulnerability',
960
+ description: 'Passing user input directly to ORM create/update allows attackers to set unintended fields (e.g., isAdmin)',
961
+ severity: 'medium',
962
+ tier: 'pro',
963
+ languages: ['javascript', 'typescript', 'python'],
964
+ patterns: [
965
+ // Sequelize/Mongoose/Prisma create with req.body
966
+ /\.create\s*\(\s*(?:req\.body|request\.data|request\.POST)\s*\)/,
967
+ /\.update\s*\(\s*(?:req\.body|request\.data|request\.POST)\s*\)/,
968
+ // Spread into create/update (also risky)
969
+ /\.create\s*\(\s*\{\s*\.\.\.(?:req\.body|request\.data)\s*\}/,
970
+ /\.update\s*\(\s*\{\s*\.\.\.(?:req\.body|request\.data)\s*\}/,
971
+ ],
972
+ fix: 'Explicitly whitelist allowed fields instead of passing user input directly to ORM operations',
973
+ },
974
+
975
+ // --- File Upload ---
976
+ {
977
+ id: 'unvalidated-file-upload',
978
+ name: 'Unvalidated File Upload',
979
+ description: 'Accepting file uploads without type/size validation can lead to arbitrary file upload attacks',
980
+ severity: 'high',
981
+ tier: 'pro',
982
+ languages: ['javascript', 'typescript', 'python'],
983
+ patterns: [
984
+ // Multer without file filter or limits
985
+ /multer\s*\(\s*\{\s*(?:dest|storage)\s*:[^}]*\}\s*\)(?![^;]*(?:fileFilter|limits))/,
986
+ // Express file upload without checks
987
+ /upload\.(?:single|array|fields)\s*\([^)]*\)\s*(?:,|\))\s*(?:async\s*)?\([^)]*\)\s*(?:=>|\{)(?![^}]{0,300}(?:mimetype|type|size|extension|ext))/,
988
+ ],
989
+ fix: 'Validate file type (MIME type), size, and extension. Store uploads outside the webroot',
990
+ },
991
+
992
+ // --- Log Injection ---
993
+ {
994
+ id: 'log-injection',
995
+ name: 'Log Injection / CRLF Injection',
996
+ description: 'User input written directly to logs can forge log entries or inject malicious content',
997
+ severity: 'medium',
998
+ tier: 'pro',
999
+ languages: ['javascript', 'typescript', 'python'],
1000
+ patterns: [
1001
+ // Logger with user input that could contain newlines
1002
+ /(?:logger|log)\.(?:info|warn|error|debug)\s*\(\s*(?:`[^`]*\$\{(?:req\.|body\.|params\.|query\.)|['"][^'"]*['"\s]*\+\s*(?:req\.|body\.|params\.|query\.))/,
1003
+ /(?:console|logging)\.(?:log|info|warn|error|debug)\s*\(\s*f?['"`][^'"`]*\{(?:request\.|req\.)/,
1004
+ ],
1005
+ fix: 'Sanitize user input before logging: strip newlines, control characters, and limit length',
1006
+ },
1007
+
1008
+ // ============================================
1009
+ // NEW RULES - Security Gaps Backlog
1010
+ // ============================================
1011
+
1012
+ // --- JWT Hardening ---
1013
+ {
1014
+ id: 'jwt-missing-exp',
1015
+ name: 'JWT Token Without Expiration',
1016
+ description: 'JWT tokens signed without an expiration claim can be used indefinitely if compromised',
1017
+ severity: 'high',
1018
+ tier: 'free',
1019
+ languages: ['javascript', 'typescript', 'python'],
1020
+ patterns: [
1021
+ // JS: jwt.sign() without expiresIn option
1022
+ /jwt\.sign\s*\(\s*(?:\{[^}]*\}|\w+)\s*,\s*(?:[^,)]+)\s*\)(?!\s*;?\s*\/\/\s*has\s*exp)/,
1023
+ // JS: jwt.sign with options object but no expiresIn
1024
+ /jwt\.sign\s*\(\s*(?:\{[^}]*\}|\w+)\s*,\s*[^,)]+\s*,\s*\{(?![^}]*expiresIn)[^}]*\}\s*\)/,
1025
+ ],
1026
+ fix: 'Always set an expiration: jwt.sign(payload, secret, { expiresIn: "1h" }) or include exp claim in payload',
1027
+ },
1028
+ {
1029
+ id: 'jwt-weak-secret',
1030
+ name: 'JWT Signed with Weak/Short Secret',
1031
+ description: 'JWT signing with a short hardcoded secret makes tokens easy to brute-force',
1032
+ severity: 'high',
1033
+ tier: 'free',
1034
+ languages: ['javascript', 'typescript', 'python'],
1035
+ patterns: [
1036
+ // JS: jwt.sign with short string literal secret (< 16 chars)
1037
+ /jwt\.sign\s*\([^,]+,\s*['"`][^'"`]{1,15}['"`]/,
1038
+ // JS: jwt.verify with short string literal secret
1039
+ /jwt\.verify\s*\([^,]+,\s*['"`][^'"`]{1,15}['"`]/,
1040
+ ],
1041
+ fix: 'Use a strong secret (32+ characters) from environment variables: jwt.sign(payload, process.env.JWT_SECRET)',
1042
+ },
1043
+
1044
+ // --- Security Headers ---
1045
+ {
1046
+ id: 'missing-security-headers',
1047
+ name: 'Express App Without Security Headers',
1048
+ description: 'Express apps without security headers (CSP, HSTS, X-Frame-Options) are vulnerable to various attacks',
1049
+ severity: 'medium',
1050
+ tier: 'free',
1051
+ languages: ['javascript', 'typescript'],
1052
+ patterns: [
1053
+ // Express app created without helmet or manual header setup in same file
1054
+ /const\s+\w+\s*=\s*express\s*\(\s*\)(?![^]*(?:helmet|Content-Security-Policy|X-Frame-Options|Strict-Transport-Security))/,
1055
+ ],
1056
+ fix: 'Use helmet middleware: app.use(helmet()) or set security headers manually (CSP, HSTS, X-Frame-Options)',
1057
+ },
1058
+ {
1059
+ id: 'csp-unsafe-inline',
1060
+ name: 'CSP Allows unsafe-inline or unsafe-eval',
1061
+ description: 'Content Security Policy with unsafe-inline or unsafe-eval defeats the purpose of CSP and allows XSS',
1062
+ severity: 'medium',
1063
+ tier: 'pro',
1064
+ languages: ['javascript', 'typescript'],
1065
+ patterns: [
1066
+ /Content-Security-Policy[^'"]*['"`][^'"`]*unsafe-inline[^'"`]*['"`]/,
1067
+ /Content-Security-Policy[^'"]*['"`][^'"`]*unsafe-eval[^'"`]*['"`]/,
1068
+ /contentSecurityPolicy[^}]*['"`][^'"`]*unsafe-inline[^'"`]*['"`]/,
1069
+ /contentSecurityPolicy[^}]*['"`][^'"`]*unsafe-eval[^'"`]*['"`]/,
1070
+ ],
1071
+ fix: 'Remove unsafe-inline/unsafe-eval from CSP. Use nonces or hashes for inline scripts instead',
1072
+ },
1073
+
1074
+ // --- CORS with Credentials ---
1075
+ {
1076
+ id: 'cors-credentials-wildcard',
1077
+ name: 'CORS Wildcard Origin with Credentials',
1078
+ description: 'Allowing all origins with credentials enabled lets any site make authenticated requests on behalf of users',
1079
+ severity: 'high',
1080
+ tier: 'free',
1081
+ languages: ['javascript', 'typescript', 'python'],
1082
+ patterns: [
1083
+ // Express: cors({ origin: '*', credentials: true }) or cors({ origin: true, credentials: true })
1084
+ /cors\s*\(\s*\{[^}]*origin\s*:\s*(?:['"`]\*['"`]|true)[^}]*credentials\s*:\s*true[^}]*\}/,
1085
+ /cors\s*\(\s*\{[^}]*credentials\s*:\s*true[^}]*origin\s*:\s*(?:['"`]\*['"`]|true)[^}]*\}/,
1086
+ ],
1087
+ fix: 'Specify allowed origins explicitly instead of using wildcard when credentials are enabled',
1088
+ },
1089
+
1090
+ // --- Password Hashing ---
1091
+ {
1092
+ id: 'password-hash-weak',
1093
+ name: 'Weak Password Hashing Algorithm',
1094
+ description: 'Using MD5, SHA1, or raw SHA256 without salt/KDF for passwords allows fast brute-force attacks',
1095
+ severity: 'high',
1096
+ tier: 'free',
1097
+ languages: ['javascript', 'typescript', 'python'],
1098
+ patterns: [
1099
+ // JS: crypto.createHash with password variable
1100
+ /crypto\.createHash\s*\(\s*['"`](?:md5|sha1|sha256)['"`]\s*\)\.update\s*\(\s*(?:password|passwd|pass|pwd)/i,
1101
+ // Generic: hash(password) without bcrypt/scrypt/argon2
1102
+ /(?:md5|sha1|sha256)\s*\(\s*(?:password|passwd|pass|pwd)\s*\)/i,
1103
+ ],
1104
+ fix: 'Use bcrypt, scrypt, or argon2 for password hashing: await bcrypt.hash(password, 12)',
1105
+ },
1106
+ {
1107
+ id: 'password-plaintext-storage',
1108
+ name: 'Password Stored Without Hashing',
1109
+ description: 'Storing passwords directly in the database without hashing exposes all users if the database is compromised',
1110
+ severity: 'critical',
1111
+ tier: 'free',
1112
+ languages: ['javascript', 'typescript', 'python'],
1113
+ patterns: [
1114
+ // ORM create/insert with password from request body directly
1115
+ /\.create\s*\(\s*\{[^}]*password\s*:\s*(?:req\.body\.password|request\.(?:data|POST)\[?['"`]?password)/,
1116
+ /\.insert\s*\(\s*\{[^}]*password\s*:\s*(?:req\.body\.password|request\.(?:data|POST)\[?['"`]?password)/,
1117
+ // Direct DB insert with password field from user input
1118
+ /\.(?:insertOne|save)\s*\(\s*\{[^}]*password\s*:\s*(?:req\.body|data|body)\.password/,
1119
+ ],
1120
+ fix: 'Always hash passwords before storage: const hashed = await bcrypt.hash(req.body.password, 12)',
1121
+ },
1122
+
1123
+ // --- Zip Slip ---
1124
+ {
1125
+ id: 'zip-slip',
1126
+ name: 'Archive Extraction Without Path Validation (Zip Slip)',
1127
+ description: 'Extracting archives without validating file paths allows attackers to write files outside the target directory',
1128
+ severity: 'high',
1129
+ tier: 'pro',
1130
+ languages: ['javascript', 'typescript', 'python'],
1131
+ patterns: [
1132
+ // JS: extract/unzip without path validation
1133
+ /\.extract\s*\(\s*(?:req\.|body\.|params\.|query\.|upload|file|input)/,
1134
+ /tar\.(?:x|extract)\s*\(\s*\{[^}]*(?:file|cwd)\s*:[^}]*(?:req\.|body\.|params\.|upload|input)/,
1135
+ /unzipper\.Extract\s*\(/,
1136
+ /adm-zip.*extractAllTo\s*\(/,
1137
+ ],
1138
+ fix: 'Validate extracted file paths: ensure path.resolve(dest, entry) starts with path.resolve(dest)',
1139
+ },
1140
+
1141
+ // --- HTTP Client Timeout ---
1142
+ {
1143
+ id: 'http-client-no-timeout',
1144
+ name: 'HTTP Client Without Timeout',
1145
+ description: 'Outbound HTTP requests without a timeout can hang indefinitely, causing resource exhaustion',
1146
+ severity: 'medium',
1147
+ tier: 'free',
1148
+ languages: ['javascript', 'typescript', 'python'],
1149
+ patterns: [
1150
+ // Python: requests without timeout
1151
+ /requests\.(?:get|post|put|delete|patch|head)\s*\([^)]*\)(?<![^)]*timeout)/,
1152
+ ],
1153
+ fix: 'Always set a timeout: requests.get(url, timeout=10) or use AbortController with fetch',
1154
+ },
1155
+
1156
+ // --- S3 Public Access ---
1157
+ {
1158
+ id: 's3-public-read',
1159
+ name: 'S3 Bucket with Public Access',
1160
+ description: 'S3 bucket policies granting public access can expose sensitive data to the internet',
1161
+ severity: 'high',
1162
+ tier: 'pro',
1163
+ languages: ['javascript', 'typescript', 'python'],
1164
+ patterns: [
1165
+ // S3 ACL set to public-read
1166
+ /ACL\s*:\s*['"`]public-read(?:-write)?['"`]/,
1167
+ /acl\s*=\s*['"`]public-read(?:-write)?['"`]/,
1168
+ // S3 policy with Principal: *
1169
+ /Principal['"`:]\s*['"`]\*['"`][\s\S]{0,200}s3:(?:GetObject|\*)/,
1170
+ // Public bucket configuration
1171
+ /BlockPublicAcls\s*:\s*false/,
1172
+ /BlockPublicPolicy\s*:\s*false/,
1173
+ ],
1174
+ fix: 'Remove public access. Use BlockPublicAccess, restrict bucket policies to specific IAM roles/accounts',
1175
+ },
694
1176
  ];
695
1177
 
696
1178
  export function getRuleById(id: string): SecurityRule | undefined {