@sun-asterisk/sunlint 1.3.36 → 1.3.38

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 (113) hide show
  1. package/cli.js +34 -0
  2. package/config/rules/enhanced-rules-registry.json +387 -98
  3. package/config/rules/rules-registry-generated.json +202 -174
  4. package/config/rules-summary.json +1 -1
  5. package/core/architecture-integration.js +115 -17
  6. package/core/cli-action-handler.js +103 -28
  7. package/core/cli-program.js +7 -2
  8. package/core/github-annotate-service.js +62 -0
  9. package/core/impact-integration.js +31 -16
  10. package/core/init-command.js +261 -0
  11. package/core/output-service.js +64 -10
  12. package/core/performance-optimizer.js +1 -1
  13. package/core/summary-report-service.js +46 -0
  14. package/core/unified-rule-registry.js +4 -3
  15. package/docs/DART_RULE_EXECUTION_FLOW.md +1 -1
  16. package/docs/REGISTRY_GENERATION_DIAGRAM.md +289 -0
  17. package/docs/REGISTRY_GENERATION_FLOW.md +486 -0
  18. package/docs/skills/CREATE_NEW_DART_RULE.md +932 -0
  19. package/engines/eslint-engine.js +6 -0
  20. package/engines/heuristic-engine.js +23 -10
  21. package/engines/impact/core/detectors/database-detector.js +1 -1
  22. package/engines/impact/core/detectors/endpoint-detector.js +1 -1
  23. package/engines/impact/core/report-generator.js +235 -73
  24. package/origin-rules/dart-en.md +4 -4
  25. package/origin-rules/security-en.md +470 -282
  26. package/package.json +1 -1
  27. package/rules/dart/D001_recommended_lint_rules/config.json +134 -0
  28. package/rules/index.js +6 -4
  29. package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
  30. package/rules/security/S001_backend_auth_communications/index.js +87 -0
  31. package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
  32. package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
  33. package/rules/security/S002_os_command_injection/index.js +87 -0
  34. package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
  35. package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
  36. package/rules/security/S008_svg_content_validation/index.js +87 -0
  37. package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
  38. package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
  39. package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
  40. package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
  41. package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
  42. package/rules/security/S021_referrer_policy/index.js +86 -0
  43. package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
  44. package/rules/security/S023_no_json_injection/config.json +133 -44
  45. package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
  46. package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
  47. package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
  48. package/rules/security/S026_tls_all_connections/config.json +30 -0
  49. package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
  50. package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
  51. package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
  52. package/rules/security/S035_separate_app_hostnames/config.json +28 -0
  53. package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
  54. package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
  55. package/rules/security/S039_tls_certificate_validation/config.json +29 -0
  56. package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
  57. package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
  58. package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
  59. package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
  60. package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
  61. package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
  62. package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
  63. package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
  64. package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
  65. package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
  66. package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
  67. package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
  68. package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
  69. package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
  70. package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
  71. package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
  72. package/rules/security/S053_generic_error_messages/config.json +28 -0
  73. package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
  74. package/rules/security/S053_generic_error_messages/index.js +86 -0
  75. package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
  76. package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
  77. package/rules/security/S059_disable_debug_mode/config.json +28 -0
  78. package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
  79. package/rules/security/S059_disable_debug_mode/index.js +86 -0
  80. package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
  81. package/rules/security/S060_password_minimum_length/config.json +28 -0
  82. package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
  83. package/rules/security/S060_password_minimum_length/index.js +86 -0
  84. package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
  85. package/rules/security/S026_json_schema_validation/config.json +0 -27
  86. package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
  87. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  88. package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
  89. package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
  90. package/rules/security/S035_path_session_cookies/config.json +0 -99
  91. package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
  92. package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
  93. package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
  94. package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
  95. package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
  96. package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
  97. package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
  98. package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
  99. package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
  100. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
  101. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
  102. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
  103. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
  104. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
  105. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
  106. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
  107. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
  108. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
  109. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
  110. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
  111. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
  112. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
  113. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/typescript/README.md +0 -0
@@ -0,0 +1,278 @@
1
+ /**
2
+ * S048 – Validate OAuth redirect URIs with exact string comparison
3
+ *
4
+ * Detects insecure OAuth redirect URI validation:
5
+ * - Wildcard redirects (*.example.com)
6
+ * - Partial string matching (startsWith, includes, contains)
7
+ * - Regex pattern matching instead of exact comparison
8
+ * - Missing redirect URI validation entirely
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ class S048Analyzer {
15
+ constructor() {
16
+ this.ruleId = 'S048';
17
+ this.ruleName = 'Validate OAuth redirect URIs with exact string comparison';
18
+ this.description = 'Prevent OAuth redirect attacks by using exact string comparison for redirect URIs';
19
+
20
+ // Patterns for insecure redirect URI validation
21
+ this.insecurePatterns = [
22
+ // Partial string matching (dangerous)
23
+ {
24
+ pattern: /redirect[_-]?uri.*\.startsWith\s*\(/gi,
25
+ message: 'Redirect URI validated with startsWith() - use exact string comparison instead',
26
+ type: 'startswith_validation'
27
+ },
28
+ {
29
+ pattern: /redirect[_-]?uri.*\.includes\s*\(/gi,
30
+ message: 'Redirect URI validated with includes() - use exact string comparison instead',
31
+ type: 'includes_validation'
32
+ },
33
+ {
34
+ pattern: /redirect[_-]?uri.*\.indexOf\s*\([^)]+\)\s*[>!=]/gi,
35
+ message: 'Redirect URI validated with indexOf() - use exact string comparison instead',
36
+ type: 'indexof_validation'
37
+ },
38
+ // Regex pattern matching
39
+ {
40
+ pattern: /redirect[_-]?uri.*\.match\s*\(/gi,
41
+ message: 'Redirect URI validated with regex match() - use exact string comparison instead',
42
+ type: 'regex_match'
43
+ },
44
+ {
45
+ pattern: /redirect[_-]?uri.*\.test\s*\(/gi,
46
+ message: 'Redirect URI validated with regex test() - use exact string comparison instead',
47
+ type: 'regex_test'
48
+ },
49
+ {
50
+ pattern: /new RegExp\([^)]*redirect/gi,
51
+ message: 'Dynamic regex for redirect URI validation - use exact string comparison',
52
+ type: 'dynamic_regex'
53
+ },
54
+ // Wildcard patterns in strings
55
+ {
56
+ pattern: /['"`]\*\.[\w.-]+['"`].*redirect/gi,
57
+ message: 'Wildcard domain pattern for redirect URI detected. Use exact URIs only',
58
+ type: 'wildcard_domain'
59
+ },
60
+ {
61
+ pattern: /redirect.*['"`]\*\.[\w.-]+['"`]/gi,
62
+ message: 'Wildcard domain pattern for redirect URI detected. Use exact URIs only',
63
+ type: 'wildcard_domain'
64
+ },
65
+ // URL parsing without exact comparison
66
+ {
67
+ pattern: /new URL\([^)]*redirect[^)]*\).*\.host(name)?.*===?\s*[^=]/gi,
68
+ message: 'Comparing only hostname of redirect URI - validate full URI with exact comparison',
69
+ type: 'hostname_only'
70
+ },
71
+ {
72
+ pattern: /\.origin\s*===?\s*['"`][^'"`]+['"`].*redirect/gi,
73
+ message: 'Comparing only origin - validate full redirect URI with exact comparison',
74
+ type: 'origin_only'
75
+ }
76
+ ];
77
+
78
+ // Warning patterns
79
+ this.warningPatterns = [
80
+ // Dynamic/configurable redirect without validation mention
81
+ {
82
+ pattern: /redirect[_-]?uri\s*=\s*(?:req|request|params|query|body)\./gi,
83
+ message: 'Redirect URI from user input - ensure validation against pre-registered allowlist',
84
+ type: 'user_input_redirect'
85
+ },
86
+ // Redirect without any validation check nearby
87
+ {
88
+ pattern: /callback[_-]?url\s*=\s*(?:req|request|params|query|body)\./gi,
89
+ message: 'OAuth callback URL from user input - ensure validation against pre-registered allowlist',
90
+ type: 'user_input_callback'
91
+ }
92
+ ];
93
+
94
+ // Files to analyze (OAuth-related)
95
+ this.targetPatterns = [
96
+ /oauth/i,
97
+ /auth/i,
98
+ /login/i,
99
+ /callback/i,
100
+ /redirect/i,
101
+ /authorize/i,
102
+ /token/i
103
+ ];
104
+
105
+ // Files to skip
106
+ this.skipPatterns = [
107
+ /\.test\./i,
108
+ /\.spec\./i,
109
+ /test\//i,
110
+ /tests\//i,
111
+ /__tests__\//i,
112
+ /node_modules/i,
113
+ /\.md$/i
114
+ ];
115
+ }
116
+
117
+ shouldSkipFile(filePath) {
118
+ return this.skipPatterns.some(pattern => pattern.test(filePath));
119
+ }
120
+
121
+ isOAuthRelatedFile(filePath, content) {
122
+ // Check filename
123
+ if (this.targetPatterns.some(pattern => pattern.test(filePath))) {
124
+ return true;
125
+ }
126
+ // Check content for OAuth patterns
127
+ const oauthIndicators = [
128
+ /oauth/i,
129
+ /redirect[_-]?uri/i,
130
+ /callback[_-]?url/i,
131
+ /authorization[_-]?code/i,
132
+ /client[_-]?id/i,
133
+ /client[_-]?secret/i,
134
+ /access[_-]?token/i
135
+ ];
136
+ return oauthIndicators.some(pattern => pattern.test(content));
137
+ }
138
+
139
+ async analyze(files, language, options = {}) {
140
+ const violations = [];
141
+
142
+ for (const filePath of files) {
143
+ if (this.shouldSkipFile(filePath)) {
144
+ continue;
145
+ }
146
+
147
+ try {
148
+ const content = fs.readFileSync(filePath, 'utf8');
149
+
150
+ // Only analyze OAuth-related files
151
+ if (!this.isOAuthRelatedFile(filePath, content)) {
152
+ continue;
153
+ }
154
+
155
+ const fileViolations = this.analyzeFile(content, filePath);
156
+ violations.push(...fileViolations);
157
+ } catch (error) {
158
+ if (options.verbose) {
159
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
160
+ }
161
+ }
162
+ }
163
+
164
+ return violations;
165
+ }
166
+
167
+ analyzeFile(content, filePath) {
168
+ const violations = [];
169
+ const lines = content.split('\n');
170
+
171
+ // Check insecure patterns (errors)
172
+ for (const { pattern, message, type } of this.insecurePatterns) {
173
+ pattern.lastIndex = 0;
174
+
175
+ let match;
176
+ while ((match = pattern.exec(content)) !== null) {
177
+ const lineNum = this.getLineNumber(content, match.index);
178
+ const line = lines[lineNum - 1];
179
+
180
+ if (this.isComment(line)) {
181
+ continue;
182
+ }
183
+
184
+ if (this.isTestContext(lines, lineNum)) {
185
+ continue;
186
+ }
187
+
188
+ violations.push({
189
+ file: filePath,
190
+ line: lineNum,
191
+ column: this.getColumnNumber(content, match.index),
192
+ message: message,
193
+ severity: 'error',
194
+ ruleId: this.ruleId,
195
+ type: type,
196
+ matchedText: match[0]
197
+ });
198
+ }
199
+ }
200
+
201
+ // Check warning patterns
202
+ for (const { pattern, message, type } of this.warningPatterns) {
203
+ pattern.lastIndex = 0;
204
+
205
+ let match;
206
+ while ((match = pattern.exec(content)) !== null) {
207
+ const lineNum = this.getLineNumber(content, match.index);
208
+ const line = lines[lineNum - 1];
209
+
210
+ if (this.isComment(line)) {
211
+ continue;
212
+ }
213
+
214
+ // Check if there's validation nearby
215
+ if (this.hasValidationNearby(lines, lineNum)) {
216
+ continue;
217
+ }
218
+
219
+ violations.push({
220
+ file: filePath,
221
+ line: lineNum,
222
+ column: this.getColumnNumber(content, match.index),
223
+ message: message,
224
+ severity: 'warning',
225
+ ruleId: this.ruleId,
226
+ type: type,
227
+ matchedText: match[0]
228
+ });
229
+ }
230
+ }
231
+
232
+ return violations;
233
+ }
234
+
235
+ hasValidationNearby(lines, lineNum) {
236
+ const start = Math.max(0, lineNum - 5);
237
+ const end = Math.min(lines.length, lineNum + 5);
238
+ const context = lines.slice(start, end).join('\n').toLowerCase();
239
+
240
+ // Check for validation patterns
241
+ const validationPatterns = [
242
+ /allowlist|allow[_-]?list|whitelist|white[_-]?list/i,
243
+ /registered[_-]?uri/i,
244
+ /valid[_-]?redirect/i,
245
+ /validate.*redirect/i,
246
+ /===\s*['"]/, // Exact comparison
247
+ /\.has\(/, // Set/Map has
248
+ /\.includes\(.*===/ // Array includes with exact check
249
+ ];
250
+
251
+ return validationPatterns.some(pattern => pattern.test(context));
252
+ }
253
+
254
+ isComment(line) {
255
+ const trimmed = line.trim();
256
+ return trimmed.startsWith('//') || trimmed.startsWith('#') ||
257
+ trimmed.startsWith('/*') || trimmed.startsWith('*');
258
+ }
259
+
260
+ isTestContext(lines, lineNum) {
261
+ const start = Math.max(0, lineNum - 5);
262
+ const end = Math.min(lines.length, lineNum + 2);
263
+ const context = lines.slice(start, end).join('\n').toLowerCase();
264
+
265
+ return /describe\(|it\(|test\(|jest|mocha|mock|stub|fake/i.test(context);
266
+ }
267
+
268
+ getLineNumber(content, index) {
269
+ return content.substring(0, index).split('\n').length;
270
+ }
271
+
272
+ getColumnNumber(content, index) {
273
+ const lastNewline = content.lastIndexOf('\n', index - 1);
274
+ return index - lastNewline;
275
+ }
276
+ }
277
+
278
+ module.exports = S048Analyzer;
@@ -84,15 +84,22 @@
84
84
  "never-expire",
85
85
  "permanent",
86
86
  "forever",
87
- "infinite",
88
- "unlimited"
87
+ "infinite[_-]?(?:token|expir|ttl|validity)",
88
+ "(?:token|expir|ttl|validity)[_-]?infinite",
89
+ "unlimited[_-]?(?:token|expir|ttl)",
90
+ "(?:token|expir|ttl)[_-]?unlimited"
89
91
  ],
90
92
  "exemptedScenarios": [
91
93
  "test",
92
94
  "testing",
93
95
  "mock",
94
96
  "development",
95
- "dev"
97
+ "dev",
98
+ "infinitescroll",
99
+ "infinitequery",
100
+ "useinfinite",
101
+ "infiniteloading",
102
+ "infinitelist"
96
103
  ]
97
104
  },
98
105
  "examples": {
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "S050",
3
+ "name": "Reference tokens must be unique with 128-bit entropy using CSPRNG",
4
+ "description": "Reference tokens (session IDs, authorization codes, access tokens) must have at least 128 bits of entropy generated using a cryptographically secure pseudo-random number generator (CSPRNG).",
5
+ "category": "security",
6
+ "severity": "high",
7
+ "enabled": true,
8
+ "engines": ["heuristic"],
9
+ "enginePreference": ["heuristic"],
10
+ "tags": ["security", "tokens", "entropy", "csprng", "session"],
11
+ "examples": {
12
+ "valid": [
13
+ "crypto.randomBytes(16).toString('hex'); // 128-bit",
14
+ "crypto.randomBytes(32).toString('base64'); // 256-bit",
15
+ "uuid.v4(); // UUIDv4 has 122 bits of randomness"
16
+ ],
17
+ "invalid": [
18
+ "Math.random().toString(36); // Not cryptographically secure",
19
+ "Date.now().toString(); // Predictable",
20
+ "userId + timestamp; // Low entropy"
21
+ ]
22
+ },
23
+ "fixable": false,
24
+ "docs": {
25
+ "description": "This rule ensures reference tokens have sufficient entropy. Minimum 128 bits recommended, 256 bits preferred for high-security applications. Use CSPRNG: crypto.randomBytes() in Node.js, SecureRandom in Java, secrets module in Python. Avoid Math.random(), timestamps, or sequential IDs.",
26
+ "url": "https://owasp.org/Top10/A02_2021-Cryptographic_Failures/"
27
+ }
28
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * S050 Dart Analyzer - Reference Tokens Entropy
3
+ *
4
+ * This is a JS wrapper that delegates to DartAnalyzer binary.
5
+ * Actual implementation: dart_analyzer/lib/rules/security/S050_reference_tokens_entropy.dart
6
+ *
7
+ * Rule: Reference tokens must be unique with 128-bit entropy using CSPRNG
8
+ */
9
+
10
+ class DartS050Analyzer {
11
+ constructor() {
12
+ this.ruleId = 'S050';
13
+ this.language = 'dart';
14
+ }
15
+
16
+ getMetadata() {
17
+ return {
18
+ ruleId: 'S050',
19
+ name: 'Reference Tokens Entropy',
20
+ language: 'dart',
21
+ delegateTo: 'dart_analyzer',
22
+ description: 'Reference tokens must be unique with 128-bit entropy using CSPRNG'
23
+ };
24
+ }
25
+
26
+ getConfig() {
27
+ return {
28
+ checkCsprng: true,
29
+ checkMathRandom: true,
30
+ minEntropyBits: 128,
31
+ severity: 'high'
32
+ };
33
+ }
34
+
35
+ async analyze(files, language, options) {
36
+ // Delegated to DartAnalyzer binary via heuristic-engine.js
37
+ return [];
38
+ }
39
+
40
+ supportsLanguage(language) {
41
+ return language === 'dart';
42
+ }
43
+ }
44
+
45
+ module.exports = DartS050Analyzer;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * S050 Rule Router - Reference Tokens Entropy
3
+ *
4
+ * Routes analysis to the appropriate language-specific analyzer.
5
+ * Supports: TypeScript, JavaScript, Dart
6
+ *
7
+ * Rule: Reference tokens must be unique with 128-bit entropy using CSPRNG
8
+ */
9
+
10
+ const path = require('path');
11
+
12
+ class S050Router {
13
+ constructor() {
14
+ this.analyzers = new Map();
15
+ this.ruleId = 'S050';
16
+ }
17
+
18
+ getAnalyzer(language) {
19
+ const normalizedLang = this.normalizeLanguage(language);
20
+
21
+ if (!this.analyzers.has(normalizedLang)) {
22
+ try {
23
+ const analyzerPath = path.join(__dirname, normalizedLang, 'analyzer.js');
24
+ const AnalyzerClass = require(analyzerPath);
25
+ this.analyzers.set(normalizedLang, new AnalyzerClass());
26
+ } catch (error) {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ return this.analyzers.get(normalizedLang);
32
+ }
33
+
34
+ normalizeLanguage(language) {
35
+ if (typeof language !== 'string') {
36
+ return 'typescript';
37
+ }
38
+ const languageMap = {
39
+ 'typescript': 'typescript',
40
+ 'javascript': 'typescript',
41
+ 'ts': 'typescript',
42
+ 'js': 'typescript',
43
+ 'dart': 'dart'
44
+ };
45
+ return languageMap[language.toLowerCase()] || language.toLowerCase();
46
+ }
47
+
48
+ supportsLanguage(language) {
49
+ if (typeof language !== 'string') return false;
50
+ const supported = ['typescript', 'javascript', 'ts', 'js', 'dart'];
51
+ return supported.includes(language.toLowerCase());
52
+ }
53
+
54
+ getSupportedLanguages() {
55
+ return ['typescript', 'javascript', 'dart'];
56
+ }
57
+
58
+ async analyze(files, language, options = {}) {
59
+ const analyzer = this.getAnalyzer(language);
60
+ if (!analyzer) return [];
61
+ if (typeof analyzer.analyze === 'function') {
62
+ return analyzer.analyze(files, language, options);
63
+ }
64
+ return [];
65
+ }
66
+
67
+ async initialize(semanticEngineOrLanguage = null, semanticEngine = null) {
68
+ let engine = semanticEngine;
69
+ let lang = null;
70
+
71
+ if (typeof semanticEngineOrLanguage === 'string') {
72
+ lang = semanticEngineOrLanguage;
73
+ } else if (semanticEngineOrLanguage && typeof semanticEngineOrLanguage === 'object') {
74
+ engine = semanticEngineOrLanguage;
75
+ }
76
+
77
+ if (lang) {
78
+ const analyzer = this.getAnalyzer(lang);
79
+ if (analyzer && typeof analyzer.initialize === 'function') {
80
+ await analyzer.initialize(engine);
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ module.exports = new S050Router();
@@ -0,0 +1,74 @@
1
+ /**
2
+ * S050 – Reference tokens must be unique with 128-bit entropy using CSPRNG
3
+ *
4
+ * Detects weak token generation:
5
+ * - Math.random() for tokens
6
+ * - Date/timestamp-based tokens
7
+ * - Sequential/predictable tokens
8
+ */
9
+
10
+ const fs = require('fs');
11
+
12
+ class S050Analyzer {
13
+ constructor() {
14
+ this.ruleId = 'S050';
15
+ this.ruleName = 'Reference tokens must have 128-bit entropy using CSPRNG';
16
+ this.description = 'Use cryptographically secure random generation for tokens';
17
+
18
+ this.criticalPatterns = [
19
+ {
20
+ pattern: /(?:token|session|id)\s*=\s*Math\.random\(\)/gi,
21
+ message: 'Math.random() is not cryptographically secure - use crypto.randomBytes()',
22
+ type: 'math_random_token'
23
+ },
24
+ {
25
+ pattern: /(?:token|session|id)\s*=\s*Date\.now\(\)/gi,
26
+ message: 'Timestamp-based tokens are predictable - use CSPRNG',
27
+ type: 'timestamp_token'
28
+ },
29
+ {
30
+ pattern: /(?:token|session|id)\s*=\s*\+\+\w+|(?:token|session|id)\s*=\s*\w+\+\+/gi,
31
+ message: 'Sequential tokens are predictable - use CSPRNG with 128-bit entropy',
32
+ type: 'sequential_token'
33
+ }
34
+ ];
35
+
36
+ this.skipPatterns = [
37
+ /\.test\./i, /\.spec\./i, /test\//i, /__tests__\//i, /node_modules/i
38
+ ];
39
+ }
40
+
41
+ shouldSkipFile(filePath) {
42
+ return this.skipPatterns.some(p => p.test(filePath));
43
+ }
44
+
45
+ async analyze(files, language, options = {}) {
46
+ const violations = [];
47
+ for (const filePath of files) {
48
+ if (this.shouldSkipFile(filePath)) continue;
49
+ try {
50
+ const content = fs.readFileSync(filePath, 'utf8');
51
+ violations.push(...this.analyzeFile(content, filePath));
52
+ } catch (e) { /* skip */ }
53
+ }
54
+ return violations;
55
+ }
56
+
57
+ analyzeFile(content, filePath) {
58
+ const violations = [];
59
+ for (const { pattern, message, type } of this.criticalPatterns) {
60
+ pattern.lastIndex = 0;
61
+ let match;
62
+ while ((match = pattern.exec(content)) !== null) {
63
+ const lineNum = content.substring(0, match.index).split('\n').length;
64
+ violations.push({
65
+ file: filePath, line: lineNum, column: 1,
66
+ message, severity: 'error', ruleId: this.ruleId, type
67
+ });
68
+ }
69
+ }
70
+ return violations;
71
+ }
72
+ }
73
+
74
+ module.exports = S050Analyzer;
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "S053",
3
+ "name": "Return generic error messages, hide internal details",
4
+ "description": "Return generic error messages to users while logging detailed errors server-side. Do not expose stack traces, database errors, SQL queries, file paths, or internal system details in API responses.",
5
+ "category": "security",
6
+ "severity": "medium",
7
+ "enabled": true,
8
+ "engines": ["heuristic"],
9
+ "enginePreference": ["heuristic"],
10
+ "tags": ["security", "error-handling", "information-disclosure", "api"],
11
+ "examples": {
12
+ "valid": [
13
+ "res.status(500).json({ error: 'An unexpected error occurred' });",
14
+ "logger.error('Database error', { error, query }); // Log details",
15
+ "throw new HttpException('Invalid request', 400);"
16
+ ],
17
+ "invalid": [
18
+ "res.status(500).json({ error: error.stack });",
19
+ "res.send(error.message); // May contain internal details",
20
+ "res.json({ sql: query, error: dbError }); // Exposes SQL"
21
+ ]
22
+ },
23
+ "fixable": false,
24
+ "docs": {
25
+ "description": "This rule prevents information disclosure through error messages. Internal details to hide: stack traces, database error messages, SQL queries, file paths, internal IPs, configuration values, library versions. Log full details server-side with correlation IDs. Return generic messages to clients.",
26
+ "url": "https://owasp.org/Top10/A01_2021-Broken_Access_Control/"
27
+ }
28
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * S053 Dart Analyzer - Generic Error Messages
3
+ *
4
+ * This is a JS wrapper that delegates to DartAnalyzer binary.
5
+ * Actual implementation: dart_analyzer/lib/rules/security/S053_generic_error_messages.dart
6
+ *
7
+ * Rule: Return generic error messages, hide internal details
8
+ */
9
+
10
+ class DartS053Analyzer {
11
+ constructor() {
12
+ this.ruleId = 'S053';
13
+ this.language = 'dart';
14
+ }
15
+
16
+ getMetadata() {
17
+ return {
18
+ ruleId: 'S053',
19
+ name: 'Generic Error Messages',
20
+ language: 'dart',
21
+ delegateTo: 'dart_analyzer',
22
+ description: 'Return generic error messages, hide internal details from users'
23
+ };
24
+ }
25
+
26
+ getConfig() {
27
+ return {
28
+ checkStackTraceExposure: true,
29
+ checkSqlExposure: true,
30
+ checkPathExposure: true,
31
+ severity: 'medium'
32
+ };
33
+ }
34
+
35
+ async analyze(files, language, options) {
36
+ // Delegated to DartAnalyzer binary via heuristic-engine.js
37
+ return [];
38
+ }
39
+
40
+ supportsLanguage(language) {
41
+ return language === 'dart';
42
+ }
43
+ }
44
+
45
+ module.exports = DartS053Analyzer;