@sun-asterisk/sunlint 1.3.26 → 1.3.28

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.
Files changed (69) hide show
  1. package/config/rules/enhanced-rules-registry.json +101 -17
  2. package/config/rules/rules-registry-generated.json +22 -22
  3. package/origin-rules/security-en.md +351 -338
  4. package/package.json +1 -1
  5. package/rules/common/C003_no_vague_abbreviations/analyzer.js +73 -21
  6. package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +206 -2
  7. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +553 -58
  8. package/rules/common/C029_catch_block_logging/analyzer.js +47 -12
  9. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +35 -15
  10. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +9 -5
  11. package/rules/security/S003_open_redirect_protection/README.md +371 -0
  12. package/rules/security/S003_open_redirect_protection/analyzer.js +135 -0
  13. package/rules/security/S003_open_redirect_protection/config.json +58 -0
  14. package/rules/security/S003_open_redirect_protection/symbol-based-analyzer.js +884 -0
  15. package/rules/security/S004_sensitive_data_logging/analyzer.js +135 -0
  16. package/rules/security/S004_sensitive_data_logging/config.json +62 -0
  17. package/rules/security/S004_sensitive_data_logging/symbol-based-analyzer.js +592 -0
  18. package/rules/security/S005_no_origin_auth/analyzer.js +97 -148
  19. package/rules/security/S005_no_origin_auth/config.json +28 -67
  20. package/rules/security/S005_no_origin_auth/symbol-based-analyzer.js +708 -0
  21. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +170 -31
  22. package/rules/security/S010_no_insecure_encryption/analyzer.js +8 -2
  23. package/rules/security/S012_hardcoded_secrets/analyzer.js +149 -0
  24. package/rules/security/S012_hardcoded_secrets/config.json +75 -0
  25. package/rules/security/S012_hardcoded_secrets/symbol-based-analyzer.js +1204 -0
  26. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +87 -0
  27. package/rules/security/S017_use_parameterized_queries/analyzer.js +11 -78
  28. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +1146 -1
  29. package/rules/security/S019_smtp_injection_protection/analyzer.js +120 -0
  30. package/rules/security/S019_smtp_injection_protection/config.json +35 -0
  31. package/rules/security/S019_smtp_injection_protection/symbol-based-analyzer.js +687 -0
  32. package/rules/security/S020_no_eval_dynamic_code/analyzer.js +55 -130
  33. package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +4 -19
  34. package/rules/security/S022_escape_output_context/README.md +254 -0
  35. package/rules/security/S022_escape_output_context/analyzer.js +510 -0
  36. package/rules/security/S022_escape_output_context/config.json +229 -0
  37. package/rules/security/S023_no_json_injection/analyzer.js +15 -0
  38. package/rules/security/S023_no_json_injection/ast-analyzer.js +18 -3
  39. package/rules/security/S023_no_json_injection/config.json +133 -0
  40. package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +41 -0
  41. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +67 -8
  42. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +29 -6
  43. package/rules/security/S029_csrf_protection/config.json +127 -0
  44. package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +160 -28
  45. package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +81 -19
  46. package/rules/security/S031_secure_session_cookies/analyzer.js +20 -2
  47. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +100 -0
  48. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +8 -1
  49. package/rules/security/S032_httponly_session_cookies/analyzer.js +2 -2
  50. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +115 -0
  51. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +39 -10
  52. package/rules/security/S036_lfi_rfi_protection/analyzer.js +224 -0
  53. package/rules/security/S036_lfi_rfi_protection/config.json +20 -0
  54. package/rules/security/S040_session_fixation_protection/analyzer.js +153 -0
  55. package/rules/security/S040_session_fixation_protection/config.json +20 -0
  56. package/rules/security/S042_require_re_authentication_for_long_lived/README.md +83 -0
  57. package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +153 -0
  58. package/rules/security/S042_require_re_authentication_for_long_lived/config.json +41 -0
  59. package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +1139 -0
  60. package/rules/security/S043_password_changes_invalidate_all_sessions/README.md +107 -0
  61. package/rules/security/S043_password_changes_invalidate_all_sessions/analyzer.js +153 -0
  62. package/rules/security/S043_password_changes_invalidate_all_sessions/config.json +41 -0
  63. package/rules/security/S043_password_changes_invalidate_all_sessions/symbol-based-analyzer.js +541 -0
  64. package/docs/COMMAND-EXAMPLES.md +0 -390
  65. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +0 -151
  66. package/docs/FOLDER_STRUCTURE.md +0 -59
  67. package/docs/SIMPLIFIED_USAGE_GUIDE.md +0 -208
  68. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +0 -541
  69. package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +0 -307
@@ -39,6 +39,10 @@ class S006SymbolBasedAnalyzer {
39
39
  "responsecode",
40
40
  "agencycode",
41
41
  "companycode",
42
+ "groupcode", // Group identifier (business code)
43
+ "teamcode", // Team identifier
44
+ "organizationcode", // Organization identifier
45
+ "secretariat", // Secretariat role/position name (not a secret code)
42
46
  "eventcode",
43
47
  "productcode",
44
48
  "itemcode",
@@ -331,6 +335,45 @@ class S006SymbolBasedAnalyzer {
331
335
  }
332
336
  }
333
337
 
338
+ // Skip validation schemas returned from React hooks or validation builders
339
+ // Check if this is inside a React hook (useMemo, useCallback, etc.) or validation function
340
+ let currentParent = returnStmt.getParent();
341
+ let isValidationContext = false;
342
+ let depth = 0;
343
+
344
+ while (currentParent && depth < 10) {
345
+ const parentText = currentParent.getText().toLowerCase();
346
+
347
+ // Check for React hooks that typically return configuration/schema objects
348
+ if (
349
+ parentText.startsWith("usememo(") ||
350
+ parentText.startsWith("usecallback(") ||
351
+ parentText.startsWith("useeffect(")
352
+ ) {
353
+ // Check if the returned object contains validation properties
354
+ const returnedText = expression.getText().toLowerCase();
355
+ if (
356
+ returnedText.includes("validate:") ||
357
+ returnedText.includes("validator:") ||
358
+ returnedText.includes("rules:") ||
359
+ returnedText.includes("onblur:") ||
360
+ returnedText.includes("onchange:") ||
361
+ returnedText.includes("checkRequired") ||
362
+ returnedText.includes("checkMinLength")
363
+ ) {
364
+ isValidationContext = true;
365
+ break;
366
+ }
367
+ }
368
+
369
+ currentParent = currentParent.getParent();
370
+ depth++;
371
+ }
372
+
373
+ if (isValidationContext) {
374
+ continue; // Validation schema from React hook - safe
375
+ }
376
+
334
377
  // Skip if return object contains only safe messages (success, message, etc.)
335
378
  const hasOnlySafeProperties = properties.every((prop) => {
336
379
  const name = prop.getName?.() || "";
@@ -404,27 +447,63 @@ class S006SymbolBasedAnalyzer {
404
447
  if (this.isSensitiveIdentifier(normalizedName)) {
405
448
  // Skip error constants, configuration objects, and form states
406
449
  const parentVarName = this.findParentVariableName(objLiteral);
450
+ const parentVarNameUpper = parentVarName?.toUpperCase() || "";
407
451
  if (
408
452
  parentVarName &&
409
- (parentVarName.includes("ERROR") ||
410
- parentVarName.includes("ERRORS") ||
411
- parentVarName.includes("MESSAGE") ||
412
- parentVarName.includes("MESSAGES") ||
413
- parentVarName.includes("MAPPING") ||
414
- parentVarName.includes("FIELDS") ||
415
- parentVarName.includes("CONFIG") ||
416
- parentVarName.includes("CONSTANT") ||
417
- parentVarName.includes("ENDPOINT") ||
418
- parentVarName.includes("API") ||
419
- parentVarName.includes("URL") ||
420
- parentVarName.includes("ROUTE") ||
421
- parentVarName.includes("ROUTES") ||
422
- parentVarName.includes("INITIAL") ||
423
- parentVarName.includes("DEFAULT") ||
424
- parentVarName.includes("STATE") ||
425
- parentVarName.includes("FORM"))
453
+ (parentVarNameUpper.includes("ERROR") ||
454
+ parentVarNameUpper.includes("ERRORS") ||
455
+ parentVarNameUpper.includes("MESSAGE") ||
456
+ parentVarNameUpper.includes("MESSAGES") ||
457
+ parentVarNameUpper.includes("MAPPING") ||
458
+ parentVarNameUpper.includes("FIELDS") ||
459
+ parentVarNameUpper.includes("CONFIG") ||
460
+ parentVarNameUpper.includes("CONSTANT") ||
461
+ parentVarNameUpper.includes("ENDPOINT") ||
462
+ parentVarNameUpper.includes("API") ||
463
+ parentVarNameUpper.includes("URL") ||
464
+ parentVarNameUpper.includes("ROUTE") ||
465
+ parentVarNameUpper.includes("ROUTES") ||
466
+ parentVarNameUpper.includes("INITIAL") ||
467
+ parentVarNameUpper.includes("DEFAULT") ||
468
+ parentVarNameUpper.includes("STATE") ||
469
+ parentVarNameUpper.includes("FORM") ||
470
+ parentVarNameUpper.includes("VALIDATION") ||
471
+ parentVarNameUpper.includes("SCHEMA") ||
472
+ parentVarNameUpper.includes("RULES") ||
473
+ parentVarNameUpper.includes("PATTERN") ||
474
+ parentVarNameUpper.includes("REGEX") ||
475
+ parentVarNameUpper.includes("REGEXP"))
426
476
  ) {
427
- continue; // Error constants, configs, routes, and form states are safe
477
+ continue; // Error constants, configs, routes, form states, validation schemas, and regex patterns are safe
478
+ }
479
+
480
+ // Skip masking/sanitization utility configurations
481
+ // These are security utilities that PROTECT sensitive data, not expose them
482
+ if (
483
+ parentVarName &&
484
+ (parentVarNameUpper.includes("MASK") ||
485
+ parentVarNameUpper.includes("SANITIZE") ||
486
+ parentVarNameUpper.includes("REDACT") ||
487
+ parentVarNameUpper.includes("HIDE") ||
488
+ parentVarNameUpper.includes("OBFUSCATE"))
489
+ ) {
490
+ continue; // Masking/sanitization utilities are safe
491
+ }
492
+
493
+ // Check if inside a masking/sanitization function
494
+ const parentFuncName = this.findParentFunctionName(objLiteral);
495
+ const parentFuncNameUpper = parentFuncName?.toUpperCase() || "";
496
+ if (
497
+ parentFuncName &&
498
+ (parentFuncNameUpper.includes("MASK") ||
499
+ parentFuncNameUpper.includes("SANITIZE") ||
500
+ parentFuncNameUpper.includes("REDACT") ||
501
+ parentFuncNameUpper.includes("HIDE") ||
502
+ parentFuncNameUpper.includes("OBFUSCATE") ||
503
+ parentFuncNameUpper.includes("STRIP") ||
504
+ parentFuncNameUpper.includes("REMOVE"))
505
+ ) {
506
+ continue; // Inside masking/sanitization function - safe
428
507
  }
429
508
 
430
509
  // Skip URL/endpoint configurations (e.g., set_password_url, generate_otp_api)
@@ -437,6 +516,32 @@ class S006SymbolBasedAnalyzer {
437
516
  continue; // URL/API endpoint configurations are not sensitive data
438
517
  }
439
518
 
519
+ // Skip validation schema configurations
520
+ // Check if this property's value contains validation-related properties
521
+ const propValue = prop.getInitializer();
522
+ if (propValue && propValue.getKind() === SyntaxKind.ObjectLiteralExpression) {
523
+ const valueText = propValue.getText().toLowerCase();
524
+ // If the property value contains validation keywords, it's a validation schema
525
+ if (
526
+ valueText.includes("validate:") ||
527
+ valueText.includes("validator:") ||
528
+ valueText.includes("rules:") ||
529
+ valueText.includes("onblur:") ||
530
+ valueText.includes("onchange:") ||
531
+ valueText.includes("checkRequired") ||
532
+ valueText.includes("checkMinLength") ||
533
+ valueText.includes("checkMaxLength")
534
+ ) {
535
+ continue; // Validation schema configuration - safe
536
+ }
537
+ }
538
+
539
+ // Skip regex pattern definitions
540
+ // If the property value is a RegExp literal (e.g., PASSWORD: /^[a-z]+$/), it's a pattern definition
541
+ if (propValue && propValue.getKind() === SyntaxKind.RegularExpressionLiteral) {
542
+ continue; // Regex pattern definition - safe
543
+ }
544
+
440
545
  // Check if in exposure context (e.g., res.json, send, etc.)
441
546
  const parent = this.findParentContext(objLiteral);
442
547
  if (parent && this.isExposureContext(parent)) {
@@ -1026,10 +1131,9 @@ class S006SymbolBasedAnalyzer {
1026
1131
  return false; // Generic "code" alone is not sensitive
1027
1132
  }
1028
1133
 
1029
- // Skip if it's "password" alone in object key context (likely config/route name)
1030
- if (normalizedName === "password") {
1031
- return false; // Will be caught by containsSensitiveCode if it's actual password value
1032
- }
1134
+ // Note: "password" as a property name in data objects IS sensitive
1135
+ // Only skip "password" in very specific safe contexts (handled by safePasswordPatterns)
1136
+ // This is checked below via sensitiveIdentifiers.has()
1033
1137
 
1034
1138
  // Exact match first
1035
1139
  if (this.sensitiveIdentifiers.has(normalizedName)) {
@@ -1203,24 +1307,59 @@ class S006SymbolBasedAnalyzer {
1203
1307
  * Helper: Check if argument contains sensitive code
1204
1308
  */
1205
1309
  containsSensitiveCode(node) {
1206
- const text = node.getText().toLowerCase();
1207
- const normalized = this.normalizeIdentifier(text);
1208
-
1209
- // Check for sensitive identifiers (variable/property access)
1210
- if (this.isSensitiveIdentifier(normalized)) {
1211
- return true;
1212
- }
1213
-
1214
- // Check child nodes (for object literals, etc.)
1310
+ // PRIORITY 1: For object literals, check property names AND values
1311
+ // This prevents false positives from matching keywords in surrounding context
1215
1312
  if (node.getKind() === SyntaxKind.ObjectLiteralExpression) {
1216
1313
  const properties = node.getProperties();
1217
1314
  for (const prop of properties) {
1315
+ // Check property name
1218
1316
  const propName = prop.getName?.() || "";
1219
1317
  const normalizedPropName = this.normalizeIdentifier(propName);
1220
1318
  if (this.isSensitiveIdentifier(normalizedPropName)) {
1221
1319
  return true;
1222
1320
  }
1321
+
1322
+ // ALSO check property value (important for nested structures)
1323
+ // e.g., { htmlBody: mapDataToTemplate({ password }) }
1324
+ if (prop.getKind() === SyntaxKind.PropertyAssignment) {
1325
+ const initializer = prop.getInitializer();
1326
+ if (initializer && this.containsSensitiveCode(initializer)) {
1327
+ return true;
1328
+ }
1329
+ }
1223
1330
  }
1331
+ return false; // Object literal checked, no sensitive props or values
1332
+ }
1333
+
1334
+ // PRIORITY 2: For call expressions, recursively check arguments
1335
+ // This handles cases like mapDataToTemplate({ password })
1336
+ if (node.getKind() === SyntaxKind.CallExpression) {
1337
+ const args = node.getArguments();
1338
+ for (const arg of args) {
1339
+ if (this.containsSensitiveCode(arg)) {
1340
+ return true;
1341
+ }
1342
+ }
1343
+ return false; // Call expression checked, no sensitive args
1344
+ }
1345
+
1346
+ // PRIORITY 3: For simple identifiers, check the identifier name only
1347
+ if (node.getKind() === SyntaxKind.Identifier) {
1348
+ const text = node.getText().toLowerCase();
1349
+ const normalized = this.normalizeIdentifier(text);
1350
+ return this.isSensitiveIdentifier(normalized);
1351
+ }
1352
+
1353
+ // PRIORITY 4: For other node types, only check if text is short (not complex expression)
1354
+ const text = node.getText();
1355
+ if (text.length > 100) {
1356
+ // Long text likely contains surrounding context - skip to avoid false positives
1357
+ return false;
1358
+ }
1359
+
1360
+ const normalized = this.normalizeIdentifier(text.toLowerCase());
1361
+ if (this.isSensitiveIdentifier(normalized)) {
1362
+ return true;
1224
1363
  }
1225
1364
 
1226
1365
  // Check template spans
@@ -519,8 +519,14 @@ class S010Analyzer {
519
519
  return true;
520
520
  }
521
521
 
522
- // Check for performance.now() - High-resolution timestamp (also predictable)
523
- if (/performance\.now\s*\(\)/.test(text)) {
522
+ // Check for performance.now() - ONLY flag if used for random generation, NOT for timing
523
+ // Safe: const start = performance.now(); const duration = performance.now() - start;
524
+ // Unsafe: const token = performance.now().toString();
525
+ if (/performance\.now\s*\(\).*\.(?:toString|toFixed|toPrecision|slice|substr|substring)/.test(text)) {
526
+ return true;
527
+ }
528
+ // Also flag if used with encoding: btoa(performance.now())
529
+ if (/btoa\s*\(.*performance\.now/.test(text)) {
524
530
  return true;
525
531
  }
526
532
 
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Rule S012: Hardcoded Secrets Detection - Analyzer Wrapper
3
+ *
4
+ * This analyzer wraps the symbol-based analyzer to provide a consistent
5
+ * interface for the SunLint engine.
6
+ */
7
+ // Command: node cli.js --rule=S012 --input=examples/rule-test-fixtures/rules/S012_hardcoded_secrets --engine=heuristic
8
+
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+ const { Project } = require("ts-morph");
12
+ const S012SymbolBasedAnalyzer = require("./symbol-based-analyzer");
13
+
14
+ class S012Analyzer {
15
+ constructor(options = {}) {
16
+ this.ruleId = "S012";
17
+ this.semanticEngine = options.semanticEngine || null;
18
+ this.symbolAnalyzer = new S012SymbolBasedAnalyzer(this.semanticEngine);
19
+ }
20
+
21
+ /**
22
+ * Analyze multiple files
23
+ */
24
+ async analyze(files, language, options = {}) {
25
+ const violations = [];
26
+
27
+ for (const filePath of files) {
28
+ try {
29
+ const fileViolations = await this.analyzeFile(filePath, options);
30
+ violations.push(...fileViolations);
31
+ } catch (error) {
32
+ console.error(
33
+ `[S012] Error analyzing file ${filePath}:`,
34
+ error.message
35
+ );
36
+ }
37
+ }
38
+
39
+ return violations;
40
+ }
41
+
42
+ /**
43
+ * Analyze a single file
44
+ */
45
+ async analyzeFile(filePath, options = {}) {
46
+ // Skip files that shouldn't be analyzed
47
+ if (this.shouldSkipFile(filePath)) {
48
+ return [];
49
+ }
50
+
51
+ try {
52
+ // Check if file exists
53
+ if (!fs.existsSync(filePath)) {
54
+ return [];
55
+ }
56
+
57
+ const content = fs.readFileSync(filePath, "utf-8");
58
+
59
+ // Try to get source file from semantic engine
60
+ let sourceFile = this.semanticEngine?.project?.getSourceFile(filePath);
61
+
62
+ // If not available, create a temporary ts-morph source file
63
+ if (!sourceFile) {
64
+ const tempProject = new Project({
65
+ useInMemoryFileSystem: true,
66
+ compilerOptions: {
67
+ allowJs: true,
68
+ noLib: true,
69
+ target: 99, // ESNext
70
+ },
71
+ });
72
+
73
+ sourceFile = tempProject.createSourceFile(
74
+ path.basename(filePath),
75
+ content
76
+ );
77
+ }
78
+
79
+ // Run the symbol-based analyzer
80
+ const symbolViolations = await this.symbolAnalyzer.analyze(
81
+ sourceFile,
82
+ filePath
83
+ );
84
+
85
+ // Add file path to each violation
86
+ return symbolViolations.map((violation) => ({
87
+ ...violation,
88
+ filePath,
89
+ file: filePath,
90
+ }));
91
+ } catch (error) {
92
+ console.error(
93
+ `[S012] Error in analyzeFile for ${filePath}:`,
94
+ error.message
95
+ );
96
+ return [];
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Check if file should be skipped
102
+ */
103
+ shouldSkipFile(filePath) {
104
+ const skipPatterns = [
105
+ /test\//i,
106
+ /tests\//i,
107
+ /__tests__\//i,
108
+ /\.test\./i,
109
+ /\.spec\./i,
110
+ /node_modules\//i,
111
+ /dist\//i,
112
+ /build\//i,
113
+ /\.next\//i,
114
+ /\.nuxt\//i,
115
+ /coverage\//i,
116
+ /\.d\.ts$/i,
117
+ /\.min\.js$/i,
118
+ /\.bundle\.js$/i,
119
+ /\.example\./i,
120
+ /\.sample\./i,
121
+ /\.template\./i,
122
+ /vendor\//i,
123
+ /third[_-]party\//i,
124
+ /\.git\//i,
125
+ ];
126
+
127
+ return skipPatterns.some((pattern) => pattern.test(filePath));
128
+ }
129
+
130
+ /**
131
+ * Get rule metadata
132
+ */
133
+ getRuleMetadata() {
134
+ const configPath = path.join(__dirname, "config.json");
135
+ if (fs.existsSync(configPath)) {
136
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
137
+ return config;
138
+ }
139
+ return {
140
+ ruleId: this.ruleId,
141
+ name: "Hardcoded Secrets Protection",
142
+ description: "Detects hardcoded secrets in source code",
143
+ category: "security",
144
+ severity: "error",
145
+ };
146
+ }
147
+ }
148
+
149
+ module.exports = S012Analyzer;
@@ -0,0 +1,75 @@
1
+ {
2
+ "ruleId": "S012",
3
+ "name": "Hardcoded Secrets Protection",
4
+ "description": "Detects hardcoded secrets, API keys, passwords, tokens, and credentials in source code to prevent accidental exposure through version control",
5
+ "category": "security",
6
+ "severity": "error",
7
+ "languages": ["All languages"],
8
+ "tags": [
9
+ "security",
10
+ "owasp",
11
+ "secrets",
12
+ "credentials",
13
+ "cryptographic-failures",
14
+ "hardcoded-secrets",
15
+ "api-keys",
16
+ "passwords",
17
+ "tokens"
18
+ ],
19
+ "enabled": true,
20
+ "fixable": false,
21
+ "engine": "heuristic",
22
+ "metadata": {
23
+ "owaspCategory": "A02:2021 - Cryptographic Failures",
24
+ "cweId": "CWE-798",
25
+ "cweDescription": "Use of Hard-coded Credentials",
26
+ "description": "Hardcoding secrets in source code is a critical security vulnerability. Secrets can be exposed through version control history, code sharing, or unauthorized access. This rule detects various patterns of hardcoded credentials including API keys, passwords, tokens, private keys, and connection strings.",
27
+ "impact": "Critical - Credential exposure, unauthorized access, data breaches, compliance violations (SOC2, ISO27001, PCI-DSS)",
28
+ "likelihood": "High",
29
+ "remediation": "Use environment variables (process.env), secret management systems (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, GCP Secret Manager), or configuration management tools. Never commit secrets to version control. Use .gitignore for .env files. Consider using git-secrets or similar tools to prevent accidental commits.",
30
+ "examples": {
31
+ "bad": [
32
+ "const API_KEY = 'AIzaSyD-1234567890abcdef';",
33
+ "const password = 'MySecretPassword123!';",
34
+ "const connectionString = 'mongodb://admin:password@localhost:27017';",
35
+ "const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';",
36
+ "const awsKey = 'AKIAIOSFODNN7EXAMPLE';"
37
+ ],
38
+ "good": [
39
+ "const API_KEY = process.env.API_KEY;",
40
+ "const password = config.get('database.password');",
41
+ "const connectionString = process.env.MONGODB_URI;",
42
+ "const token = await secretsManager.getSecret('jwt-secret');",
43
+ "const awsKey = process.env.AWS_ACCESS_KEY_ID;"
44
+ ]
45
+ },
46
+ "references": [
47
+ "https://owasp.org/Top10/A02_2021-Cryptographic_Failures/",
48
+ "https://cwe.mitre.org/data/definitions/798.html",
49
+ "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
50
+ ],
51
+ "frameworks": [
52
+ "Node.js",
53
+ "Express",
54
+ "NestJS",
55
+ "Next.js",
56
+ "React",
57
+ "Vue",
58
+ "Angular"
59
+ ],
60
+ "secretTypes": [
61
+ "API Keys",
62
+ "Passwords",
63
+ "Access Tokens",
64
+ "Private Keys",
65
+ "JWT Secrets",
66
+ "Database Credentials",
67
+ "OAuth Secrets",
68
+ "AWS Keys",
69
+ "GitHub Tokens",
70
+ "Slack Tokens"
71
+ ],
72
+ "detectionPatterns": 50,
73
+ "testCases": 30
74
+ }
75
+ }