@sun-asterisk/sunlint 1.2.1 → 1.2.2

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 (77) hide show
  1. package/config/rule-analysis-strategies.js +18 -2
  2. package/engines/eslint-engine.js +9 -11
  3. package/engines/heuristic-engine.js +55 -31
  4. package/package.json +2 -1
  5. package/rules/README.md +252 -0
  6. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  7. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  8. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  9. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  10. package/rules/common/C006_function_naming/analyzer.js +504 -0
  11. package/rules/common/C006_function_naming/config.json +86 -0
  12. package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
  13. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  14. package/rules/common/C012_command_query_separation/analyzer.js +481 -0
  15. package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
  16. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  17. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  18. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  19. package/rules/common/C019_log_level_usage/analyzer.js +362 -0
  20. package/rules/common/C019_log_level_usage/config.json +121 -0
  21. package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
  22. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
  23. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
  24. package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
  25. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
  26. package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
  27. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
  28. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
  29. package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
  30. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
  31. package/rules/common/C029_catch_block_logging/config.json +59 -0
  32. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
  33. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
  34. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
  35. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
  36. package/rules/common/C031_validation_separation/analyzer.js +186 -0
  37. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  38. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
  39. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  40. package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
  41. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
  42. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  43. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  44. package/rules/docs/C002_no_duplicate_code.md +57 -0
  45. package/rules/docs/C031_validation_separation.md +72 -0
  46. package/rules/index.js +155 -0
  47. package/rules/migration/converter.js +385 -0
  48. package/rules/migration/mapping.json +164 -0
  49. package/rules/parser/constants.js +31 -0
  50. package/rules/parser/file-config.js +80 -0
  51. package/rules/parser/rule-parser-simple.js +305 -0
  52. package/rules/parser/rule-parser.js +527 -0
  53. package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
  54. package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
  55. package/rules/security/S023_no_json_injection/analyzer.js +278 -0
  56. package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
  57. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  58. package/rules/security/S026_json_schema_validation/config.json +27 -0
  59. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
  60. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  61. package/rules/security/S029_csrf_protection/analyzer.js +330 -0
  62. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  63. package/rules/universal/C010/generic.js +0 -0
  64. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  65. package/rules/utils/ast-utils.js +191 -0
  66. package/rules/utils/base-analyzer.js +98 -0
  67. package/rules/utils/pattern-matchers.js +239 -0
  68. package/rules/utils/rule-helpers.js +264 -0
  69. package/rules/utils/severity-constants.js +93 -0
  70. package/scripts/generate_insights.js +188 -0
  71. package/scripts/merge-reports.js +0 -424
  72. package/scripts/test-scripts/README.md +0 -22
  73. package/scripts/test-scripts/test-c041-comparison.js +0 -114
  74. package/scripts/test-scripts/test-c041-eslint.js +0 -67
  75. package/scripts/test-scripts/test-eslint-rules.js +0 -146
  76. package/scripts/test-scripts/test-real-world.js +0 -44
  77. package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
@@ -0,0 +1,436 @@
1
+ /**
2
+ * Heuristic analyzer for: S027 – No Hardcoded Secrets
3
+ * Purpose: Prevent hardcoded passwords, API keys, secrets while avoiding false positives
4
+ * Based on user feedback: avoid flagging state variables, route names, input types
5
+ */
6
+
7
+ class S027Analyzer {
8
+ constructor() {
9
+ this.ruleId = 'S027';
10
+ this.ruleName = 'No Hardcoded Secrets';
11
+ this.description = 'Không để lộ thông tin bảo mật trong mã nguồn và Git';
12
+
13
+ // Enhanced keywords that indicate sensitive information
14
+ this.sensitiveKeywords = [
15
+ 'password', 'pass', 'pwd', 'secret', 'key', 'token',
16
+ 'apikey', 'auth', 'credential', 'seed', 'salt',
17
+ // Enhanced patterns from roadmap
18
+ 'access_token', 'refresh_token', 'id_token', 'bearer',
19
+ 'client_secret', 'client_id', 'private_key', 'public_key',
20
+ 'encryption_key', 'signing_key', 'session_key',
21
+ 'database_password', 'db_password', 'db_pass',
22
+ 'aws_secret', 'aws_key', 'github_token', 'slack_token',
23
+ 'stripe_key', 'paypal_secret', 'oauth_secret'
24
+ ];
25
+
26
+ // Patterns that should NOT be flagged (based on user feedback)
27
+ this.allowedPatterns = [
28
+ // State variables and flags
29
+ /^(is|has|enable|show|display|visible|field|strength|valid)/i,
30
+ /^_(is|has|enable|show|display)/i,
31
+
32
+ // Route/path patterns
33
+ /\/(setup|forgot|reset|change|update)-?password/i,
34
+ /password\//i,
35
+
36
+ // Input type configurations
37
+ /type\s*[=:]\s*['"`]password['"`]/i,
38
+ /inputtype\s*[=:]\s*['"`]password['"`]/i,
39
+
40
+ // Function names and method calls
41
+ /^(validate|check|verify|calculate|generate|get|fetch|create)/i,
42
+
43
+ // Component/config properties
44
+ /^(token|auth|key)type$/i,
45
+ /enabled?$/i,
46
+ ];
47
+
48
+ // Patterns that indicate environment variables or dynamic values
49
+ this.dynamicPatterns = [
50
+ /process\.env\./i,
51
+ /getenv\s*\(/i,
52
+ /config\.get\s*\(/i,
53
+ /\(\)/i, // Function calls
54
+ /await\s+/i,
55
+ /\.then\s*\(/i,
56
+ ];
57
+
58
+ // Enhanced secret patterns based on roadmap
59
+ this.secretPatterns = [
60
+ // API Keys - Enhanced patterns
61
+ /(?:api[_-]?key|apikey)['":\s=]+['"]+([a-zA-Z0-9]{20,})['"]+/i,
62
+ /['"]+[A-Za-z0-9+\/]{40,}={0,2}['"]+/, // Base64 encoded
63
+
64
+ // JWT Tokens
65
+ /eyJ[A-Za-z0-9\-_=]+\.[A-Za-z0-9\-_=]+\.?[A-Za-z0-9\-_.+/=]*/,
66
+
67
+ // AWS Credentials
68
+ /AKIA[0-9A-Z]{16}/,
69
+ /['"]+[A-Za-z0-9\/+=]{40}['"]+/, // AWS Secret Key
70
+
71
+ // Database URLs with credentials
72
+ /(mongodb|mysql|postgres|redis):\/\/[^\/\s'"]+:[^\/\s'"]+@[^\/\s'"]+/,
73
+
74
+ // Private Keys
75
+ /-----BEGIN [A-Z ]+PRIVATE KEY-----/,
76
+
77
+ // GitHub Tokens
78
+ /gh[pousr]_[A-Za-z0-9_]{36}/,
79
+
80
+ // Slack Tokens
81
+ /xox[baprs]-[A-Za-z0-9-]+/,
82
+
83
+ // Bearer tokens
84
+ /^bearer\s+[a-zA-Z0-9+/=]{10,}$/i,
85
+
86
+ // Long alphanumeric strings that look like tokens/keys
87
+ /^[a-zA-Z0-9+/=]{20,}$/,
88
+
89
+ // API key prefixes
90
+ /^(sk|pk|api|key|token)[-_][a-zA-Z0-9]{10,}$/i,
91
+
92
+ // Common weak passwords (more flexible)
93
+ /^(admin|password|123|root|test|user|pass|secret|key|token)\d*$/i,
94
+
95
+ // Mixed alphanumeric secrets (6+ chars with both letters and numbers)
96
+ /^[a-zA-Z0-9]*[a-zA-Z][a-zA-Z0-9]*[0-9][a-zA-Z0-9]*$|^[a-zA-Z0-9]*[0-9][a-zA-Z0-9]*[a-zA-Z][a-zA-Z0-9]*$/,
97
+
98
+ // Secret-like strings with hyphens/underscores
99
+ /^[a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+$/,
100
+ /^[a-zA-Z0-9]+_[a-zA-Z0-9]+_[a-zA-Z0-9]+$/,
101
+
102
+ // Generic password patterns
103
+ /^.{8,}[a-zA-Z0-9@#$%^&*()!]+$/, // Complex passwords 8+ chars
104
+
105
+ // Tokens with specific formats
106
+ /^[a-f0-9]{32,}$/i, // Hex tokens
107
+ /^[A-Z0-9]{16,}$/, // Uppercase alphanumeric
108
+ ];
109
+ }
110
+
111
+ async analyze(files, language, options = {}) {
112
+ const violations = [];
113
+
114
+ for (const filePath of files) {
115
+ // Skip build directories, test files, and node_modules to reduce false positives
116
+ if (filePath.includes('build/') || filePath.includes('dist/') ||
117
+ filePath.includes('node_modules/') || filePath.includes('.next/') ||
118
+ filePath.includes('vendor/') || filePath.includes('coverage/') ||
119
+ filePath.includes('.test.') || filePath.includes('.spec.') ||
120
+ filePath.includes('test/') || filePath.includes('tests/') ||
121
+ filePath.includes('__tests__/') || filePath.includes('.mock.') ||
122
+ filePath.includes('mocks/') || filePath.includes('fixtures/')) {
123
+ continue;
124
+ }
125
+
126
+ try {
127
+ const content = require('fs').readFileSync(filePath, 'utf8');
128
+ const fileViolations = this.analyzeFile(content, filePath);
129
+ violations.push(...fileViolations);
130
+ } catch (error) {
131
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
132
+ }
133
+ }
134
+
135
+ return violations;
136
+ }
137
+
138
+ analyzeFile(content, filePath) {
139
+ const violations = [];
140
+ const lines = content.split('\n');
141
+
142
+ // Find variable declarations and assignments with sensitive names
143
+ const assignments = this.findSensitiveAssignments(lines);
144
+
145
+ // Find hardcoded secrets in string literals (new enhancement)
146
+ const stringSecrets = this.findSecretsInStrings(lines);
147
+
148
+ assignments.forEach(assignment => {
149
+ if (this.isHardcodedSecret(assignment)) {
150
+ violations.push({
151
+ file: filePath,
152
+ line: assignment.line,
153
+ column: assignment.column,
154
+ message: `Avoid hardcoding sensitive information such as '${assignment.variableName}'. Use secure storage instead.`,
155
+ severity: 'warning',
156
+ ruleId: this.ruleId,
157
+ type: 'hardcoded_secret',
158
+ variableName: assignment.variableName,
159
+ value: assignment.value
160
+ });
161
+ }
162
+ });
163
+
164
+ stringSecrets.forEach(secret => {
165
+ violations.push({
166
+ file: filePath,
167
+ line: secret.line,
168
+ column: secret.column,
169
+ message: `Potential hardcoded secret detected: '${secret.pattern}'. Use secure storage instead.`,
170
+ severity: 'warning',
171
+ ruleId: this.ruleId,
172
+ type: 'hardcoded_string_secret',
173
+ pattern: secret.pattern,
174
+ value: secret.value
175
+ });
176
+ });
177
+
178
+ return violations;
179
+ }
180
+
181
+ findSensitiveAssignments(lines) {
182
+ const assignments = [];
183
+ const processedLines = new Set(); // Avoid duplicates
184
+
185
+ lines.forEach((line, index) => {
186
+ const trimmedLine = line.trim();
187
+ const lineKey = `${index}:${trimmedLine}`;
188
+
189
+ // Skip comments, imports, and already processed lines
190
+ if (this.isCommentOrImport(trimmedLine) || processedLines.has(lineKey)) {
191
+ return;
192
+ }
193
+
194
+ // Look for variable declarations: const/let/var name = "value"
195
+ const declMatch = trimmedLine.match(/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(['"`][^'"`]*['"`]|[^;,\n]+)/);
196
+ if (declMatch) {
197
+ const [, variableName, valueExpr] = declMatch;
198
+ if (this.hasSensitiveKeyword(variableName)) {
199
+ assignments.push({
200
+ line: index + 1,
201
+ column: line.indexOf(variableName) + 1,
202
+ variableName,
203
+ valueExpr: valueExpr.trim(),
204
+ value: this.extractStringValue(valueExpr),
205
+ type: 'declaration',
206
+ originalLine: line
207
+ });
208
+ processedLines.add(lineKey);
209
+ }
210
+ }
211
+
212
+ // Look for assignments: name = "value" (but not in declarations)
213
+ else {
214
+ const assignMatch = trimmedLine.match(/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(['"`][^'"`]*['"`]|[^;,\n]+)/);
215
+ if (assignMatch && !trimmedLine.match(/(?:const|let|var)\s/)) {
216
+ const [, variableName, valueExpr] = assignMatch;
217
+ if (this.hasSensitiveKeyword(variableName)) {
218
+ assignments.push({
219
+ line: index + 1,
220
+ column: line.indexOf(variableName) + 1,
221
+ variableName,
222
+ valueExpr: valueExpr.trim(),
223
+ value: this.extractStringValue(valueExpr),
224
+ type: 'assignment',
225
+ originalLine: line
226
+ });
227
+ processedLines.add(lineKey);
228
+ }
229
+ }
230
+ }
231
+ });
232
+
233
+ return assignments;
234
+ }
235
+
236
+ findSecretsInStrings(lines) {
237
+ const secrets = [];
238
+
239
+ lines.forEach((line, index) => {
240
+ const trimmedLine = line.trim();
241
+
242
+ // Skip comments, imports, and test files
243
+ if (this.isCommentOrImport(trimmedLine) || this.isTestFile(line)) {
244
+ return;
245
+ }
246
+
247
+ // Extract all string literals from the line
248
+ const stringLiterals = this.extractStringLiterals(line);
249
+
250
+ stringLiterals.forEach(literal => {
251
+ // Check if string looks like a secret pattern
252
+ const secretPattern = this.detectSecretPattern(literal.value);
253
+ if (secretPattern) {
254
+ secrets.push({
255
+ line: index + 1,
256
+ column: literal.column,
257
+ pattern: secretPattern,
258
+ value: literal.value,
259
+ originalLine: line
260
+ });
261
+ }
262
+ });
263
+ });
264
+
265
+ return secrets;
266
+ }
267
+
268
+ extractStringLiterals(line) {
269
+ const literals = [];
270
+ const stringRegex = /(['"`])([^'"`]*)\1/g;
271
+ let match;
272
+
273
+ while ((match = stringRegex.exec(line)) !== null) {
274
+ const value = match[2];
275
+ if (value.length >= 6) { // Only check strings with reasonable length
276
+ literals.push({
277
+ value: value,
278
+ column: match.index + 1
279
+ });
280
+ }
281
+ }
282
+
283
+ return literals;
284
+ }
285
+
286
+ detectSecretPattern(value) {
287
+ // Enhanced secret detection patterns
288
+ const advancedPatterns = [
289
+ { name: 'JWT Token', pattern: /^eyJ[A-Za-z0-9\-_=]+\.[A-Za-z0-9\-_=]+\.?[A-Za-z0-9\-_.+/=]*$/ },
290
+ { name: 'AWS Access Key', pattern: /^AKIA[0-9A-Z]{16}$/ },
291
+ { name: 'GitHub Token', pattern: /^gh[pousr]_[A-Za-z0-9_]{36}$/ },
292
+ { name: 'Slack Token', pattern: /^xox[baprs]-[A-Za-z0-9-]+$/ },
293
+ { name: 'Base64 Encoded', pattern: /^[A-Za-z0-9+\/]{40,}={0,2}$/ },
294
+ { name: 'Private Key', pattern: /-----BEGIN [A-Z ]+PRIVATE KEY-----/ },
295
+ { name: 'Database URL', pattern: /(mongodb|mysql|postgres|redis):\/\/[^\/\s'"]+:[^\/\s'"]+@[^\/\s'"]+/ },
296
+ { name: 'Long Hex Token', pattern: /^[a-f0-9]{32,}$/i },
297
+ { name: 'API Key Format', pattern: /^(sk|pk|api|key|token)[-_][a-zA-Z0-9]{10,}$/i },
298
+ // Removed overly aggressive Complex Password pattern
299
+ ];
300
+
301
+ for (const {name, pattern} of advancedPatterns) {
302
+ if (pattern.test(value)) {
303
+ return name;
304
+ }
305
+ }
306
+
307
+ return null;
308
+ }
309
+
310
+ isTestFile(line) {
311
+ const testIndicators = ['.spec.', '.test.', '__tests__', 'describe(', 'it(', 'test(', 'expect(', 'jest.', 'mock'];
312
+ return testIndicators.some(indicator => line.includes(indicator));
313
+ }
314
+
315
+ findSecretsInStrings(lines) {
316
+ const secrets = [];
317
+
318
+ lines.forEach((line, index) => {
319
+ const trimmedLine = line.trim();
320
+
321
+ // Skip comments, imports, and test files
322
+ if (this.isCommentOrImport(trimmedLine) || this.isTestFile(trimmedLine)) {
323
+ return;
324
+ }
325
+
326
+ // Extract all string literals from the line
327
+ const stringLiterals = this.extractStringLiterals(line);
328
+
329
+ stringLiterals.forEach(literal => {
330
+ // Check if string looks like a secret pattern
331
+ const secretPattern = this.detectSecretPattern(literal.value);
332
+ if (secretPattern) {
333
+ secrets.push({
334
+ line: index + 1,
335
+ column: literal.column,
336
+ pattern: secretPattern,
337
+ value: literal.value,
338
+ originalLine: line
339
+ });
340
+ }
341
+ });
342
+ });
343
+
344
+ return secrets;
345
+ }
346
+
347
+ hasSensitiveKeyword(variableName) {
348
+ const lowerName = variableName.toLowerCase();
349
+ return this.sensitiveKeywords.some(keyword => lowerName.includes(keyword));
350
+ }
351
+
352
+ isCommentOrImport(line) {
353
+ return line.startsWith('//') || line.startsWith('/*') ||
354
+ line.startsWith('import') || line.startsWith('export') ||
355
+ line.startsWith('*') || line.startsWith('<');
356
+ }
357
+
358
+ extractStringValue(valueExpr) {
359
+ // Extract string literal value
360
+ const stringMatch = valueExpr.match(/^(['"`])([^'"`]*)\1$/);
361
+ if (stringMatch) {
362
+ return stringMatch[2];
363
+ }
364
+ return null;
365
+ }
366
+
367
+ isHardcodedSecret(assignment) {
368
+ const { variableName, value, valueExpr, originalLine } = assignment;
369
+
370
+ // 1. Skip if it looks like an allowed pattern (state variables, routes, etc.)
371
+ if (this.isAllowedPattern(variableName, originalLine)) {
372
+ return false;
373
+ }
374
+
375
+ // 2. Skip if value comes from environment or dynamic source
376
+ if (this.isDynamicValue(valueExpr)) {
377
+ return false;
378
+ }
379
+
380
+ // 3. Skip if no string value (e.g., boolean, function call)
381
+ if (!value) {
382
+ return false;
383
+ }
384
+
385
+ // 4. Check if the value looks like a hardcoded secret
386
+ return this.looksLikeSecret(value);
387
+ }
388
+
389
+ isAllowedPattern(variableName, originalLine) {
390
+ const lowerName = variableName.toLowerCase();
391
+ const lowerLine = originalLine.toLowerCase();
392
+
393
+ // Check against allowed patterns
394
+ if (this.allowedPatterns.some(pattern => pattern.test(lowerName) || pattern.test(lowerLine))) {
395
+ return true;
396
+ }
397
+
398
+ // Remove comments before checking for paths to avoid false matches on "//"
399
+ const codeOnlyLine = lowerLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
400
+
401
+ // Special case: route objects and paths (but not comments with //)
402
+ if (codeOnlyLine.includes('route') || codeOnlyLine.includes('path') ||
403
+ (codeOnlyLine.includes('/') && !lowerLine.includes('//'))) {
404
+ return true;
405
+ }
406
+
407
+ // Special case: React/component props
408
+ if (codeOnlyLine.includes('<') || codeOnlyLine.includes('inputtype') || codeOnlyLine.includes('type=')) {
409
+ return true;
410
+ }
411
+
412
+ return false;
413
+ }
414
+
415
+ isDynamicValue(valueExpr) {
416
+ return this.dynamicPatterns.some(pattern => pattern.test(valueExpr));
417
+ }
418
+
419
+ looksLikeSecret(value) {
420
+ // Skip very short values (likely not secrets)
421
+ if (value.length < 6) {
422
+ return false;
423
+ }
424
+
425
+ // Skip common non-secret values
426
+ const commonValues = ['password', 'bearer', 'basic', 'token', 'key', 'secret', 'admin', 'user'];
427
+ if (commonValues.includes(value.toLowerCase())) {
428
+ return false;
429
+ }
430
+
431
+ // Check against secret patterns
432
+ return this.secretPatterns.some(pattern => pattern.test(value));
433
+ }
434
+ }
435
+
436
+ module.exports = S027Analyzer;
@@ -0,0 +1,29 @@
1
+ {
2
+ "id": "S027",
3
+ "name": "No Hardcoded Secrets",
4
+ "description": "Prevent hardcoded passwords, API keys, secrets while avoiding false positives on state variables and configuration.",
5
+ "category": "security",
6
+ "severity": "warning",
7
+ "enabled": true,
8
+ "engines": ["heuristic"],
9
+ "enginePreference": ["heuristic"],
10
+ "tags": ["security", "secrets", "credentials", "api-keys"],
11
+ "examples": {
12
+ "valid": [
13
+ "const password = process.env.PASSWORD;",
14
+ "const _isEnablePassCode = useState(false);",
15
+ "const passwordFieldVisible = true;",
16
+ "const routes = { setupPassword: '/setup-password' };"
17
+ ],
18
+ "invalid": [
19
+ "const password = 'admin123';",
20
+ "const apiKey = 'sk-1234567890abcdef';",
21
+ "const secret = 'my-secret-token';"
22
+ ]
23
+ },
24
+ "fixable": false,
25
+ "docs": {
26
+ "description": "This rule prevents hardcoded sensitive information like passwords, API keys, and secrets in source code. It avoids false positives on state variables, route names, and input type configurations.",
27
+ "url": "https://owasp.org/Top10/A02_2021-Cryptographic_Failures/"
28
+ }
29
+ }