@sun-asterisk/sunlint 1.1.7 → 1.2.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.
Files changed (74) hide show
  1. package/.sunlint.json +1 -1
  2. package/CHANGELOG.md +83 -0
  3. package/README.md +66 -4
  4. package/config/presets/all.json +125 -0
  5. package/config/presets/beginner.json +16 -8
  6. package/config/presets/ci.json +12 -4
  7. package/config/presets/maintainability.json +38 -0
  8. package/config/presets/performance.json +32 -0
  9. package/config/presets/quality.json +103 -0
  10. package/config/presets/recommended.json +36 -12
  11. package/config/presets/security.json +88 -0
  12. package/config/presets/strict.json +15 -5
  13. package/config/rules/rules-registry-generated.json +6312 -0
  14. package/config/rules-summary.json +1941 -0
  15. package/core/adapters/sunlint-rule-adapter.js +452 -0
  16. package/core/analysis-orchestrator.js +4 -4
  17. package/core/config-manager.js +28 -5
  18. package/core/rule-selection-service.js +52 -55
  19. package/docs/CONFIGURATION.md +111 -3
  20. package/docs/LANGUAGE-SPECIFIC-RULES.md +308 -0
  21. package/docs/README.md +3 -0
  22. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +156 -0
  23. package/engines/eslint-engine.js +92 -2
  24. package/engines/heuristic-engine.js +8 -31
  25. package/origin-rules/common-en.md +1320 -0
  26. package/origin-rules/dart-en.md +289 -0
  27. package/origin-rules/java-en.md +60 -0
  28. package/origin-rules/kotlin-mobile-en.md +453 -0
  29. package/origin-rules/reactjs-en.md +102 -0
  30. package/origin-rules/security-en.md +1055 -0
  31. package/origin-rules/swift-en.md +449 -0
  32. package/origin-rules/typescript-en.md +136 -0
  33. package/package.json +6 -5
  34. package/scripts/copy-rules.js +86 -0
  35. package/rules/README.md +0 -252
  36. package/rules/common/C002_no_duplicate_code/analyzer.js +0 -65
  37. package/rules/common/C002_no_duplicate_code/config.json +0 -23
  38. package/rules/common/C003_no_vague_abbreviations/analyzer.js +0 -418
  39. package/rules/common/C003_no_vague_abbreviations/config.json +0 -35
  40. package/rules/common/C006_function_naming/analyzer.js +0 -349
  41. package/rules/common/C006_function_naming/config.json +0 -86
  42. package/rules/common/C010_limit_block_nesting/analyzer.js +0 -389
  43. package/rules/common/C013_no_dead_code/analyzer.js +0 -206
  44. package/rules/common/C014_dependency_injection/analyzer.js +0 -338
  45. package/rules/common/C017_constructor_logic/analyzer.js +0 -314
  46. package/rules/common/C019_log_level_usage/analyzer.js +0 -362
  47. package/rules/common/C019_log_level_usage/config.json +0 -121
  48. package/rules/common/C029_catch_block_logging/analyzer.js +0 -373
  49. package/rules/common/C029_catch_block_logging/config.json +0 -59
  50. package/rules/common/C031_validation_separation/analyzer.js +0 -186
  51. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +0 -292
  52. package/rules/common/C042_boolean_name_prefix/analyzer.js +0 -300
  53. package/rules/common/C043_no_console_or_print/analyzer.js +0 -304
  54. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +0 -351
  55. package/rules/common/C075_explicit_return_types/analyzer.js +0 -103
  56. package/rules/common/C076_single_test_behavior/analyzer.js +0 -121
  57. package/rules/docs/C002_no_duplicate_code.md +0 -57
  58. package/rules/docs/C031_validation_separation.md +0 -72
  59. package/rules/index.js +0 -149
  60. package/rules/migration/converter.js +0 -385
  61. package/rules/migration/mapping.json +0 -164
  62. package/rules/security/S026_json_schema_validation/analyzer.js +0 -251
  63. package/rules/security/S026_json_schema_validation/config.json +0 -27
  64. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +0 -263
  65. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  66. package/rules/security/S029_csrf_protection/analyzer.js +0 -264
  67. package/rules/tests/C002_no_duplicate_code.test.js +0 -50
  68. package/rules/universal/C010/generic.js +0 -0
  69. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  70. package/rules/utils/ast-utils.js +0 -191
  71. package/rules/utils/base-analyzer.js +0 -98
  72. package/rules/utils/pattern-matchers.js +0 -239
  73. package/rules/utils/rule-helpers.js +0 -264
  74. package/rules/utils/severity-constants.js +0 -93
@@ -1,251 +0,0 @@
1
- /**
2
- * Heuristic analyzer for: S026 – JSON Schema Validation cho dữ liệu đầu vào
3
- * Purpose: Detect unvalidated JSON inputs while avoiding false positives on styles/config objects
4
- */
5
-
6
- class S026Analyzer {
7
- constructor() {
8
- this.ruleId = 'S026';
9
- this.ruleName = 'JSON Schema Validation Required';
10
- this.description = 'Áp dụng JSON Schema Validation cho dữ liệu đầu vào để đảm bảo an toàn';
11
-
12
- // Patterns that indicate actual HTTP/API input (should be validated)
13
- this.httpInputPatterns = [
14
- 'req.body', 'req.query', 'request.body', 'request.query',
15
- 'ctx.body', 'ctx.query', 'context.body', 'context.query',
16
- 'event.body', 'event.queryStringParameters'
17
- ];
18
-
19
- // Patterns that are NOT JSON inputs (should not be flagged)
20
- this.nonInputPatterns = [
21
- 'styles.', 'css.', 'theme.', 'colors.',
22
- 'config.', 'settings.', 'options.',
23
- 'data.', 'props.', 'state.',
24
- 'const.', 'static.', 'default.'
25
- ];
26
-
27
- // Validation patterns that indicate input is being validated
28
- this.validationPatterns = [
29
- 'schema.validate', 'joi.validate', 'ajv.validate',
30
- 'validate(', 'validateInput(', 'validateBody(',
31
- 'isValid(', 'checkSchema(', 'parseSchema(',
32
- '.validate(', '.valid(', '.check('
33
- ];
34
-
35
- // Express/HTTP framework patterns
36
- this.httpFrameworkPatterns = [
37
- 'app.post(', 'app.put(', 'app.patch(',
38
- 'router.post(', 'router.put(', 'router.patch(',
39
- 'express()', '.post(', '.put(', '.patch('
40
- ];
41
- }
42
-
43
- async analyze(files, language, options = {}) {
44
- const violations = [];
45
-
46
- for (const filePath of files) {
47
- if (options.verbose) {
48
- console.log(`🔍 Running S026 analysis on ${require('path').basename(filePath)}`);
49
- }
50
-
51
- try {
52
- const content = require('fs').readFileSync(filePath, 'utf8');
53
- const fileViolations = this.analyzeFile(content, filePath);
54
- violations.push(...fileViolations);
55
- } catch (error) {
56
- console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
57
- }
58
- }
59
-
60
- return violations;
61
- }
62
-
63
- analyzeFile(content, filePath) {
64
- const violations = [];
65
- const lines = content.split('\n');
66
-
67
- // Find all potential JSON inputs
68
- const potentialInputs = this.findPotentialInputs(lines);
69
-
70
- // Check if they're validated
71
- const validatedInputs = this.findValidatedInputs(content);
72
-
73
- // Report unvalidated inputs
74
- potentialInputs.forEach(input => {
75
- if (!this.isInputValidated(input, validatedInputs) &&
76
- this.isActualJSONInput(input, content)) {
77
- violations.push({
78
- file: filePath,
79
- line: input.line,
80
- column: input.column,
81
- message: `JSON input '${input.expression}' should be validated using a JSON schema before use. Consider using schema.validate(), joi.validate(), or similar validation library.`,
82
- severity: 'warning',
83
- ruleId: this.ruleId,
84
- type: 'unvalidated_json_input',
85
- inputExpression: input.expression
86
- });
87
- }
88
- });
89
-
90
- return violations;
91
- }
92
-
93
- findPotentialInputs(lines) {
94
- const inputs = [];
95
-
96
- lines.forEach((line, index) => {
97
- const trimmedLine = line.trim();
98
-
99
- // Skip comments and imports
100
- if (trimmedLine.startsWith('//') || trimmedLine.startsWith('/*') ||
101
- trimmedLine.startsWith('import') || trimmedLine.startsWith('export')) {
102
- return;
103
- }
104
-
105
- // Look for .body or .query patterns
106
- const bodyMatches = [...line.matchAll(/(\w+\.\w*body\w*)/g)];
107
- const queryMatches = [...line.matchAll(/(\w+\.\w*query\w*)/g)];
108
-
109
- [...bodyMatches, ...queryMatches].forEach(match => {
110
- const expression = match[1];
111
- const column = match.index + 1;
112
-
113
- inputs.push({
114
- expression,
115
- line: index + 1,
116
- column,
117
- originalLine: line
118
- });
119
- });
120
- });
121
-
122
- return inputs;
123
- }
124
-
125
- findValidatedInputs(content) {
126
- const validatedInputs = new Set();
127
-
128
- // Find validation calls
129
- this.validationPatterns.forEach(pattern => {
130
- const regex = new RegExp(pattern.replace('(', '\\(') + '\\s*\\(([^)]+)\\)', 'g');
131
- let match;
132
-
133
- while ((match = regex.exec(content)) !== null) {
134
- const validatedInput = match[1].trim();
135
- validatedInputs.add(validatedInput);
136
-
137
- // Also add simplified version (e.g., req.body from schema.validate(req.body))
138
- const simplifiedInput = validatedInput.replace(/^\w+\./, '').replace(/\s+/g, '');
139
- if (simplifiedInput.includes('.')) {
140
- validatedInputs.add(simplifiedInput);
141
- }
142
- }
143
- });
144
-
145
- return validatedInputs;
146
- }
147
-
148
- isInputValidated(input, validatedInputs) {
149
- const expression = input.expression;
150
-
151
- // Check exact match
152
- if (validatedInputs.has(expression)) {
153
- return true;
154
- }
155
-
156
- // Check if any validated input contains this expression
157
- for (const validated of validatedInputs) {
158
- if (validated.includes(expression) || expression.includes(validated)) {
159
- return true;
160
- }
161
- }
162
-
163
- // Check if validation happens in the same line or nearby
164
- const lineContent = input.originalLine.toLowerCase();
165
- if (this.validationPatterns.some(pattern => lineContent.includes(pattern.toLowerCase()))) {
166
- return true;
167
- }
168
-
169
- return false;
170
- }
171
-
172
- isActualJSONInput(input, content) {
173
- const expression = input.expression.toLowerCase();
174
-
175
- // Skip known non-input patterns (user feedback - styles, config, etc.)
176
- if (this.nonInputPatterns.some(pattern => expression.startsWith(pattern.toLowerCase()))) {
177
- return false;
178
- }
179
-
180
- // Skip React/CSS style objects
181
- if (this.isStyleOrConfigObject(input, content)) {
182
- return false;
183
- }
184
-
185
- // Check if it matches HTTP input patterns
186
- if (this.httpInputPatterns.some(pattern => expression.includes(pattern.toLowerCase()))) {
187
- return true;
188
- }
189
-
190
- // Check if it's in HTTP handler context
191
- if (this.isInHTTPHandlerContext(input, content)) {
192
- return true;
193
- }
194
-
195
- // Default to false to avoid false positives
196
- return false;
197
- }
198
-
199
- isStyleOrConfigObject(input, content) {
200
- const expression = input.expression;
201
- const lineContent = input.originalLine.toLowerCase();
202
-
203
- // Check for React/CSS style usage patterns
204
- const styleIndicators = [
205
- 'style=', 'css=', 'theme=', 'styles=',
206
- 'background', 'color:', 'font', 'margin:', 'padding:',
207
- 'import', 'const styles', 'const css', 'const theme'
208
- ];
209
-
210
- if (styleIndicators.some(indicator => lineContent.includes(indicator))) {
211
- return true;
212
- }
213
-
214
- // Check context around the input for style/config patterns
215
- const lines = content.split('\n');
216
- const inputLineIndex = input.line - 1;
217
- const contextStart = Math.max(0, inputLineIndex - 3);
218
- const contextEnd = Math.min(lines.length, inputLineIndex + 3);
219
- const contextLines = lines.slice(contextStart, contextEnd).join('\n').toLowerCase();
220
-
221
- const contextIndicators = [
222
- 'const styles', 'const css', 'const config', 'const theme',
223
- 'styleshet.create', 'react', 'jsx', '<div', '</div>', 'component',
224
- 'export default', 'props', 'state'
225
- ];
226
-
227
- return contextIndicators.some(indicator => contextLines.includes(indicator));
228
- }
229
-
230
- isInHTTPHandlerContext(input, content) {
231
- const lines = content.split('\n');
232
- const inputLineIndex = input.line - 1;
233
-
234
- // Check surrounding context for HTTP framework patterns
235
- const contextStart = Math.max(0, inputLineIndex - 10);
236
- const contextEnd = Math.min(lines.length, inputLineIndex + 5);
237
- const contextLines = lines.slice(contextStart, contextEnd).join('\n').toLowerCase();
238
-
239
- // Look for HTTP handler patterns in context
240
- const httpIndicators = [
241
- 'app.post', 'app.put', 'app.patch', 'app.delete',
242
- 'router.post', 'router.put', 'router.patch',
243
- '(req, res)', 'request, response', 'ctx.body', 'ctx.query',
244
- 'express', 'fastify', 'koa', 'hapi'
245
- ];
246
-
247
- return httpIndicators.some(indicator => contextLines.includes(indicator));
248
- }
249
- }
250
-
251
- module.exports = S026Analyzer;
@@ -1,27 +0,0 @@
1
- {
2
- "id": "S026",
3
- "name": "JSON Schema Validation for Input Data",
4
- "description": "Ensure all user input data (from HTTP requests, APIs) is validated using JSON schemas before processing to prevent injection attacks.",
5
- "category": "security",
6
- "severity": "warning",
7
- "enabled": true,
8
- "engines": ["heuristic"],
9
- "enginePreference": ["heuristic"],
10
- "tags": ["security", "validation", "input", "json-schema", "http"],
11
- "examples": {
12
- "valid": [
13
- "const schema = joi.object({ name: joi.string() }); const { error } = schema.validate(req.body);",
14
- "const ajv = new Ajv(); const valid = ajv.validate(schema, req.body);",
15
- "const styles = { body: { color: 'red' } }; // Style object - OK"
16
- ],
17
- "invalid": [
18
- "const data = req.body; processUser(data); // No validation",
19
- "const query = req.query; database.find(query); // Direct usage without validation"
20
- ]
21
- },
22
- "fixable": false,
23
- "docs": {
24
- "description": "This rule ensures that all user input data from HTTP requests is validated using JSON schemas before processing. Direct usage of req.body, req.query, req.params without validation can lead to injection attacks and data corruption.",
25
- "url": "https://owasp.org/Top10/A03_2021-Injection/"
26
- }
27
- }
@@ -1,263 +0,0 @@
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
- // Keywords that indicate sensitive information
14
- this.sensitiveKeywords = [
15
- 'password', 'pass', 'pwd', 'secret', 'key', 'token',
16
- 'apikey', 'auth', 'credential', 'seed', 'salt'
17
- ];
18
-
19
- // Patterns that should NOT be flagged (based on user feedback)
20
- this.allowedPatterns = [
21
- // State variables and flags
22
- /^(is|has|enable|show|display|visible|field|strength|valid)/i,
23
- /^_(is|has|enable|show|display)/i,
24
-
25
- // Route/path patterns
26
- /\/(setup|forgot|reset|change|update)-?password/i,
27
- /password\//i,
28
-
29
- // Input type configurations
30
- /type\s*[=:]\s*['"`]password['"`]/i,
31
- /inputtype\s*[=:]\s*['"`]password['"`]/i,
32
-
33
- // Function names and method calls
34
- /^(validate|check|verify|calculate|generate|get|fetch|create)/i,
35
-
36
- // Component/config properties
37
- /^(token|auth|key)type$/i,
38
- /enabled?$/i,
39
- ];
40
-
41
- // Patterns that indicate environment variables or dynamic values
42
- this.dynamicPatterns = [
43
- /process\.env\./i,
44
- /getenv\s*\(/i,
45
- /config\.get\s*\(/i,
46
- /\(\)/i, // Function calls
47
- /await\s+/i,
48
- /\.then\s*\(/i,
49
- ];
50
-
51
- // Common hardcoded secret patterns
52
- this.secretPatterns = [
53
- // Long alphanumeric strings that look like tokens/keys
54
- /^[a-zA-Z0-9+/=]{20,}$/,
55
- // API key prefixes
56
- /^(sk|pk|api|key|token)[-_][a-zA-Z0-9]{10,}$/i,
57
- // Bearer tokens
58
- /^bearer\s+[a-zA-Z0-9+/=]{10,}$/i,
59
- // Database connection strings with passwords
60
- /^(mongodb|postgres|mysql):\/\/.*:[^@]+@/i,
61
- // JWT-like patterns
62
- /^eyJ[a-zA-Z0-9+/=]+\.[a-zA-Z0-9+/=]+\.[a-zA-Z0-9+/=]*$/,
63
- // Common weak passwords (more flexible)
64
- /^(admin|password|123|root|test|user|pass|secret|key|token)\d*$/i,
65
- // Passwords with common patterns
66
- /^(admin|root|user|pass|secret)\d*$/i,
67
- // Mixed alphanumeric secrets (6+ chars with both letters and numbers)
68
- /^[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]*$/,
69
- // Secret-like strings with hyphens/underscores
70
- /^[a-zA-Z0-9]+-[a-zA-Z0-9]+-[a-zA-Z0-9]+$/,
71
- /^[a-zA-Z0-9]+_[a-zA-Z0-9]+_[a-zA-Z0-9]+$/,
72
- // Bearer token pattern (more flexible)
73
- /bearer\s+[a-zA-Z0-9]{6,}/i,
74
- ];
75
- }
76
-
77
- async analyze(files, language, options = {}) {
78
- const violations = [];
79
-
80
- for (const filePath of files) {
81
- try {
82
- const content = require('fs').readFileSync(filePath, 'utf8');
83
- const fileViolations = this.analyzeFile(content, filePath);
84
- violations.push(...fileViolations);
85
- } catch (error) {
86
- console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
87
- }
88
- }
89
-
90
- return violations;
91
- }
92
-
93
- analyzeFile(content, filePath) {
94
- const violations = [];
95
- const lines = content.split('\n');
96
-
97
- // Find variable declarations and assignments with sensitive names
98
- const assignments = this.findSensitiveAssignments(lines);
99
-
100
- assignments.forEach(assignment => {
101
- if (this.isHardcodedSecret(assignment)) {
102
- violations.push({
103
- file: filePath,
104
- line: assignment.line,
105
- column: assignment.column,
106
- message: `Avoid hardcoding sensitive information such as '${assignment.variableName}'. Use secure storage instead.`,
107
- severity: 'warning',
108
- ruleId: this.ruleId,
109
- type: 'hardcoded_secret',
110
- variableName: assignment.variableName,
111
- value: assignment.value
112
- });
113
- }
114
- });
115
-
116
- return violations;
117
- }
118
-
119
- findSensitiveAssignments(lines) {
120
- const assignments = [];
121
- const processedLines = new Set(); // Avoid duplicates
122
-
123
- lines.forEach((line, index) => {
124
- const trimmedLine = line.trim();
125
- const lineKey = `${index}:${trimmedLine}`;
126
-
127
- // Skip comments, imports, and already processed lines
128
- if (this.isCommentOrImport(trimmedLine) || processedLines.has(lineKey)) {
129
- return;
130
- }
131
-
132
- // Look for variable declarations: const/let/var name = "value"
133
- const declMatch = trimmedLine.match(/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(['"`][^'"`]*['"`]|[^;,\n]+)/);
134
- if (declMatch) {
135
- const [, variableName, valueExpr] = declMatch;
136
- if (this.hasSensitiveKeyword(variableName)) {
137
- assignments.push({
138
- line: index + 1,
139
- column: line.indexOf(variableName) + 1,
140
- variableName,
141
- valueExpr: valueExpr.trim(),
142
- value: this.extractStringValue(valueExpr),
143
- type: 'declaration',
144
- originalLine: line
145
- });
146
- processedLines.add(lineKey);
147
- }
148
- }
149
-
150
- // Look for assignments: name = "value" (but not in declarations)
151
- else {
152
- const assignMatch = trimmedLine.match(/([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(['"`][^'"`]*['"`]|[^;,\n]+)/);
153
- if (assignMatch && !trimmedLine.match(/(?:const|let|var)\s/)) {
154
- const [, variableName, valueExpr] = assignMatch;
155
- if (this.hasSensitiveKeyword(variableName)) {
156
- assignments.push({
157
- line: index + 1,
158
- column: line.indexOf(variableName) + 1,
159
- variableName,
160
- valueExpr: valueExpr.trim(),
161
- value: this.extractStringValue(valueExpr),
162
- type: 'assignment',
163
- originalLine: line
164
- });
165
- processedLines.add(lineKey);
166
- }
167
- }
168
- }
169
- });
170
-
171
- return assignments;
172
- }
173
-
174
- hasSensitiveKeyword(variableName) {
175
- const lowerName = variableName.toLowerCase();
176
- return this.sensitiveKeywords.some(keyword => lowerName.includes(keyword));
177
- }
178
-
179
- isCommentOrImport(line) {
180
- return line.startsWith('//') || line.startsWith('/*') ||
181
- line.startsWith('import') || line.startsWith('export') ||
182
- line.startsWith('*') || line.startsWith('<');
183
- }
184
-
185
- extractStringValue(valueExpr) {
186
- // Extract string literal value
187
- const stringMatch = valueExpr.match(/^(['"`])([^'"`]*)\1$/);
188
- if (stringMatch) {
189
- return stringMatch[2];
190
- }
191
- return null;
192
- }
193
-
194
- isHardcodedSecret(assignment) {
195
- const { variableName, value, valueExpr, originalLine } = assignment;
196
-
197
- // 1. Skip if it looks like an allowed pattern (state variables, routes, etc.)
198
- if (this.isAllowedPattern(variableName, originalLine)) {
199
- return false;
200
- }
201
-
202
- // 2. Skip if value comes from environment or dynamic source
203
- if (this.isDynamicValue(valueExpr)) {
204
- return false;
205
- }
206
-
207
- // 3. Skip if no string value (e.g., boolean, function call)
208
- if (!value) {
209
- return false;
210
- }
211
-
212
- // 4. Check if the value looks like a hardcoded secret
213
- return this.looksLikeSecret(value);
214
- }
215
-
216
- isAllowedPattern(variableName, originalLine) {
217
- const lowerName = variableName.toLowerCase();
218
- const lowerLine = originalLine.toLowerCase();
219
-
220
- // Check against allowed patterns
221
- if (this.allowedPatterns.some(pattern => pattern.test(lowerName) || pattern.test(lowerLine))) {
222
- return true;
223
- }
224
-
225
- // Remove comments before checking for paths to avoid false matches on "//"
226
- const codeOnlyLine = lowerLine.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
227
-
228
- // Special case: route objects and paths (but not comments with //)
229
- if (codeOnlyLine.includes('route') || codeOnlyLine.includes('path') ||
230
- (codeOnlyLine.includes('/') && !lowerLine.includes('//'))) {
231
- return true;
232
- }
233
-
234
- // Special case: React/component props
235
- if (codeOnlyLine.includes('<') || codeOnlyLine.includes('inputtype') || codeOnlyLine.includes('type=')) {
236
- return true;
237
- }
238
-
239
- return false;
240
- }
241
-
242
- isDynamicValue(valueExpr) {
243
- return this.dynamicPatterns.some(pattern => pattern.test(valueExpr));
244
- }
245
-
246
- looksLikeSecret(value) {
247
- // Skip very short values (likely not secrets)
248
- if (value.length < 6) {
249
- return false;
250
- }
251
-
252
- // Skip common non-secret values
253
- const commonValues = ['password', 'bearer', 'basic', 'token', 'key', 'secret', 'admin', 'user'];
254
- if (commonValues.includes(value.toLowerCase())) {
255
- return false;
256
- }
257
-
258
- // Check against secret patterns
259
- return this.secretPatterns.some(pattern => pattern.test(value));
260
- }
261
- }
262
-
263
- module.exports = S027Analyzer;
@@ -1,29 +0,0 @@
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
- }