@sun-asterisk/sunlint 1.3.1 → 1.3.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 (66) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/CONTRIBUTING.md +210 -1691
  3. package/config/rule-analysis-strategies.js +17 -1
  4. package/config/rules/enhanced-rules-registry.json +369 -1135
  5. package/config/rules/rules-registry-generated.json +1 -1
  6. package/core/enhanced-rules-registry.js +2 -1
  7. package/core/semantic-engine.js +15 -3
  8. package/core/semantic-rule-base.js +4 -2
  9. package/engines/heuristic-engine.js +65 -4
  10. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  11. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  12. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  13. package/origin-rules/common-en.md +11 -7
  14. package/package.json +1 -1
  15. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  16. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  17. package/rules/common/C006_function_naming/analyzer.js +29 -3
  18. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  19. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  20. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  21. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  22. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  23. package/rules/common/C013_no_dead_code/config.json +61 -0
  24. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  25. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  26. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  27. package/rules/common/C014_dependency_injection/config.json +26 -0
  28. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  29. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  30. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  31. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  32. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  33. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  34. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  35. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  36. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  37. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  38. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  39. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  40. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  41. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  42. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  43. package/rules/common/C035_error_logging_context/analyzer.js +3 -1
  44. package/rules/index.js +5 -1
  45. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  46. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  47. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  48. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  49. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  50. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  51. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  52. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  53. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  54. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  55. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  56. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  57. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  58. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  59. package/rules/security/S055_content_type_validation/README.md +176 -0
  60. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  61. package/rules/security/S055_content_type_validation/config.json +48 -0
  62. package/rules/utils/rule-helpers.js +140 -1
  63. package/scripts/consolidate-config.js +116 -0
  64. package/config/rules/S027-categories.json +0 -122
  65. package/config/rules/rules-registry.json +0 -777
  66. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Heuristic analyzer for S010 - Must use cryptographically secure random number generators (CSPRNG)
3
+ * Purpose: Detect usage of insecure random number generators for security purposes
4
+ * Based on OWASP A02:2021 - Cryptographic Failures
5
+ */
6
+
7
+ class S010Analyzer {
8
+ constructor() {
9
+ this.ruleId = 'S010';
10
+ this.ruleName = 'Must use cryptographically secure random number generators (CSPRNG)';
11
+ this.description = 'Detect usage of insecure random number generators for security purposes';
12
+
13
+ // Insecure random functions that should not be used for security
14
+ this.insecureRandomFunctions = [
15
+ // JavaScript/Node.js insecure random functions
16
+ 'Math.random',
17
+ 'Math.floor(Math.random',
18
+ 'Math.ceil(Math.random',
19
+ 'Math.round(Math.random',
20
+
21
+ // Common insecure patterns
22
+ 'new Date().getTime()',
23
+ 'Date.now()',
24
+ 'performance.now()',
25
+ 'process.hrtime()',
26
+
27
+ // Insecure libraries
28
+ 'random-js',
29
+ 'mersenne-twister',
30
+ 'seedrandom',
31
+
32
+ // Browser APIs (when used for security)
33
+ 'window.crypto.getRandomValues', // Actually secure, but context matters
34
+ ];
35
+
36
+ // Secure random functions (CSPRNG)
37
+ this.secureRandomFunctions = [
38
+ 'crypto.randomBytes',
39
+ 'crypto.randomUUID',
40
+ 'crypto.randomInt',
41
+ 'crypto.webcrypto.getRandomValues',
42
+ 'window.crypto.getRandomValues',
43
+ 'require("crypto").randomBytes',
44
+ 'import("crypto").randomBytes',
45
+ 'webcrypto.getRandomValues',
46
+ 'sodium.randombytes_buf',
47
+ 'forge.random.getBytesSync',
48
+ 'nanoid',
49
+ 'uuid.v4',
50
+ 'uuidv4',
51
+ ];
52
+
53
+ // Security-related contexts where secure random is required
54
+ this.securityContextKeywords = [
55
+ // Authentication
56
+ 'password', 'token', 'jwt', 'session', 'auth', 'login', 'signin',
57
+ 'activation', 'verification', 'reset', 'recovery', 'otp', 'totp',
58
+
59
+ // Cryptography
60
+ 'encrypt', 'decrypt', 'cipher', 'hash', 'salt', 'key', 'secret',
61
+ 'nonce', 'iv', 'seed', 'entropy', 'random', 'secure',
62
+
63
+ // Security tokens
64
+ 'csrf', 'xsrf', 'api_key', 'access_token', 'refresh_token',
65
+ 'bearer', 'authorization', 'signature', 'certificate',
66
+
67
+ // Identifiers
68
+ 'id', 'uuid', 'guid', 'code', 'pin', 'challenge',
69
+
70
+ // File/data security
71
+ 'upload', 'filename', 'path', 'temp', 'cache'
72
+ ];
73
+
74
+ // Patterns that indicate insecure random usage
75
+ this.insecurePatterns = [
76
+ // Math.random() variations
77
+ /Math\.random\(\)/g,
78
+ /Math\.floor\s*\(\s*Math\.random\s*\(\s*\)\s*\*\s*\d+\s*\)/g,
79
+ /Math\.ceil\s*\(\s*Math\.random\s*\(\s*\)\s*\*\s*\d+\s*\)/g,
80
+ /Math\.round\s*\(\s*Math\.random\s*\(\s*\)\s*\*\s*\d+\s*\)/g,
81
+
82
+ // Date-based random (only when used for randomness, not timestamps)
83
+ /new\s+Date\(\)\.getTime\(\)/g,
84
+ /Date\.now\(\)/g,
85
+ /performance\.now\(\)/g,
86
+
87
+ // String-based insecure random
88
+ /Math\.random\(\)\.toString\(\d*\)\.substring\(\d+\)/g,
89
+ /Math\.random\(\)\.toString\(\d*\)\.slice\(\d+\)/g,
90
+
91
+ // Simple increment patterns (only in security contexts)
92
+ /\+\+\s*\w+|--\s*\w+|\w+\s*\+\+|\w+\s*--/g,
93
+ ];
94
+
95
+ // Patterns that should be excluded (safe contexts)
96
+ this.safePatterns = [
97
+ // Comments and documentation
98
+ /\/\/|\/\*|\*\/|@param|@return|@example/,
99
+
100
+ // Type definitions and interfaces
101
+ /interface|type|enum|class.*\{/i,
102
+
103
+ // Import/export statements
104
+ /import|export|require|module\.exports/i,
105
+
106
+ // Test files and demo code
107
+ /test|spec|demo|example|mock|fixture/i,
108
+
109
+ // Non-security contexts
110
+ /animation|ui|display|visual|game|chart|graph|color|theme/i,
111
+
112
+ // Configuration and constants
113
+ /const\s+\w+\s*=|enum\s+\w+|type\s+\w+/i,
114
+
115
+ // Safe usage patterns - UI/Animation/Game contexts
116
+ /Math\.random\(\).*(?:animation|ui|display|game|demo|test|chart|color|hue)/i,
117
+ /(?:animation|ui|display|game|demo|test|chart|color|hue).*Math\.random\(\)/i,
118
+
119
+ // Safe class/function contexts
120
+ /class\s+(?:UI|Game|Chart|Mock|Demo|Animation)/i,
121
+ /function\s+(?:get|generate|create).*(?:Color|Animation|Chart|Game|Mock|Demo)/i,
122
+
123
+ // Safe variable names
124
+ /(?:const|let|var)\s+(?:color|hue|delay|position|chart|game|mock|demo|animation)/i,
125
+ ];
126
+
127
+ // Function patterns that indicate security context
128
+ this.securityFunctionPatterns = [
129
+ /generate.*(?:token|key|id|code|password|salt|nonce|iv)/i,
130
+ /create.*(?:token|key|id|code|password|salt|nonce|iv)/i,
131
+ /make.*(?:token|key|id|code|password|salt|nonce|iv)/i,
132
+ /random.*(?:token|key|id|code|password|salt|nonce|iv)/i,
133
+ /(?:token|key|id|code|password|salt|nonce|iv).*generator/i,
134
+ ];
135
+ }
136
+
137
+ async analyze(files, language, options = {}) {
138
+ const violations = [];
139
+
140
+ for (const filePath of files) {
141
+ // Skip test files, build directories, and node_modules
142
+ if (this.shouldSkipFile(filePath)) {
143
+ continue;
144
+ }
145
+
146
+ try {
147
+ const content = require('fs').readFileSync(filePath, 'utf8');
148
+ const fileViolations = this.analyzeFile(content, filePath, options);
149
+ violations.push(...fileViolations);
150
+ } catch (error) {
151
+ if (options.verbose) {
152
+ console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
153
+ }
154
+ }
155
+ }
156
+
157
+ return violations;
158
+ }
159
+
160
+ shouldSkipFile(filePath) {
161
+ const skipPatterns = [
162
+ 'test/', 'tests/', '__tests__/', '.test.', '.spec.',
163
+ 'node_modules/', 'build/', 'dist/', '.next/', 'coverage/',
164
+ 'vendor/', 'mocks/', '.mock.'
165
+ // Removed 'fixtures/' to allow testing
166
+ ];
167
+
168
+ return skipPatterns.some(pattern => filePath.includes(pattern));
169
+ }
170
+
171
+ analyzeFile(content, filePath, options = {}) {
172
+ const violations = [];
173
+ const lines = content.split('\n');
174
+
175
+ lines.forEach((line, index) => {
176
+ const lineNumber = index + 1;
177
+ const trimmedLine = line.trim();
178
+
179
+ // Skip comments, imports, and empty lines
180
+ if (this.shouldSkipLine(trimmedLine)) {
181
+ return;
182
+ }
183
+
184
+ // Check for insecure random usage in security context
185
+ const violation = this.checkForInsecureRandom(line, lineNumber, filePath, content);
186
+ if (violation) {
187
+ violations.push(violation);
188
+ }
189
+ });
190
+
191
+ return violations;
192
+ }
193
+
194
+ shouldSkipLine(line) {
195
+ // Skip comments, imports, and other non-code lines
196
+ return (
197
+ line.length === 0 ||
198
+ line.startsWith('//') ||
199
+ line.startsWith('/*') ||
200
+ line.startsWith('*') ||
201
+ line.startsWith('import ') ||
202
+ line.startsWith('export ') ||
203
+ line.startsWith('require(') ||
204
+ line.includes('module.exports')
205
+ );
206
+ }
207
+
208
+ checkForInsecureRandom(line, lineNumber, filePath, fullContent) {
209
+ const lowerLine = line.toLowerCase();
210
+
211
+ // First check if line contains safe patterns (early exit)
212
+ if (this.containsSafePattern(line)) {
213
+ return null;
214
+ }
215
+
216
+ // Check for insecure random patterns
217
+ for (const pattern of this.insecurePatterns) {
218
+ const matches = [...line.matchAll(pattern)];
219
+
220
+ for (const match of matches) {
221
+ // Special handling for Date.now() - check if it's legitimate timestamp usage
222
+ if (match[0].includes('Date.now()') && this.isLegitimateTimestampUsage(line)) {
223
+ continue; // Skip this match
224
+ }
225
+
226
+ // Check if this usage is in a security context
227
+ if (this.isInSecurityContext(line, fullContent, lineNumber)) {
228
+ const column = match.index + 1;
229
+
230
+ return {
231
+ ruleId: this.ruleId,
232
+ severity: 'error',
233
+ message: 'Must use cryptographically secure random number generators (CSPRNG) for security purposes. Math.random() and similar functions are not secure.',
234
+ line: lineNumber,
235
+ column: column,
236
+ filePath: filePath,
237
+ type: 'insecure_random_usage',
238
+ details: this.getSecureAlternatives(match[0]),
239
+ insecureFunction: match[0]
240
+ };
241
+ }
242
+ }
243
+ }
244
+
245
+ // Check for insecure function calls
246
+ const insecureFunctionViolation = this.checkInsecureFunctionCall(line, lineNumber, filePath, fullContent);
247
+ if (insecureFunctionViolation) {
248
+ return insecureFunctionViolation;
249
+ }
250
+
251
+ return null;
252
+ }
253
+
254
+ containsSafePattern(line) {
255
+ return this.safePatterns.some(pattern => pattern.test(line));
256
+ }
257
+
258
+ isInSecurityContext(line, fullContent, lineNumber) {
259
+ const lowerLine = line.toLowerCase();
260
+ const lowerContent = fullContent.toLowerCase();
261
+
262
+ // First check if this is explicitly a non-security context
263
+ if (this.isNonSecurityContext(line, fullContent, lineNumber)) {
264
+ return false;
265
+ }
266
+
267
+ // Check if line contains security keywords
268
+ const hasSecurityKeyword = this.securityContextKeywords.some(keyword =>
269
+ lowerLine.includes(keyword)
270
+ );
271
+
272
+ if (hasSecurityKeyword) {
273
+ return true;
274
+ }
275
+
276
+ // Check function context (look at function name)
277
+ const functionContext = this.getFunctionContext(fullContent, lineNumber);
278
+ if (functionContext && this.isSecurityFunction(functionContext)) {
279
+ return true;
280
+ }
281
+
282
+ // Check variable context
283
+ const variableContext = this.getVariableContext(line);
284
+ if (variableContext && this.isSecurityVariable(variableContext)) {
285
+ return true;
286
+ }
287
+
288
+ // Check surrounding lines for context
289
+ const contextLines = this.getSurroundingLines(fullContent, lineNumber, 3);
290
+ const contextHasSecurityKeywords = this.securityContextKeywords.some(keyword =>
291
+ contextLines.some(contextLine => contextLine.toLowerCase().includes(keyword))
292
+ );
293
+
294
+ return contextHasSecurityKeywords;
295
+ }
296
+
297
+ isNonSecurityContext(line, fullContent, lineNumber) {
298
+ const lowerLine = line.toLowerCase();
299
+
300
+ // Check for UI/Game/Animation contexts
301
+ const nonSecurityKeywords = [
302
+ 'animation', 'ui', 'display', 'visual', 'game', 'chart', 'graph',
303
+ 'color', 'theme', 'hue', 'rgb', 'hsl', 'position', 'coordinate',
304
+ 'mock', 'demo', 'test', 'example', 'sample', 'fixture'
305
+ ];
306
+
307
+ if (nonSecurityKeywords.some(keyword => lowerLine.includes(keyword))) {
308
+ return true;
309
+ }
310
+
311
+ // Check class context
312
+ const classContext = this.getClassContext(fullContent, lineNumber);
313
+ if (classContext) {
314
+ const lowerClassName = classContext.toLowerCase();
315
+ if (nonSecurityKeywords.some(keyword => lowerClassName.includes(keyword))) {
316
+ return true;
317
+ }
318
+ }
319
+
320
+ // Check function context
321
+ const functionContext = this.getFunctionContext(fullContent, lineNumber);
322
+ if (functionContext) {
323
+ const lowerFunctionName = functionContext.toLowerCase();
324
+ if (nonSecurityKeywords.some(keyword => lowerFunctionName.includes(keyword))) {
325
+ return true;
326
+ }
327
+ }
328
+
329
+ return false;
330
+ }
331
+
332
+ getClassContext(content, lineNumber) {
333
+ const lines = content.split('\n');
334
+
335
+ // Look backwards for class declaration
336
+ for (let i = lineNumber - 1; i >= Math.max(0, lineNumber - 20); i--) {
337
+ const line = lines[i];
338
+ const classMatch = line.match(/class\s+(\w+)/);
339
+ if (classMatch) {
340
+ return classMatch[1];
341
+ }
342
+ }
343
+
344
+ return null;
345
+ }
346
+
347
+ checkInsecureFunctionCall(line, lineNumber, filePath, fullContent) {
348
+ // Look for specific insecure function patterns
349
+ const mathRandomMatch = line.match(/(Math\.random\(\))/);
350
+ if (mathRandomMatch && this.isInSecurityContext(line, fullContent, lineNumber)) {
351
+ return {
352
+ ruleId: this.ruleId,
353
+ severity: 'error',
354
+ message: 'Math.random() is not cryptographically secure. Use crypto.randomBytes() or crypto.randomInt() for security purposes.',
355
+ line: lineNumber,
356
+ column: mathRandomMatch.index + 1,
357
+ filePath: filePath,
358
+ type: 'math_random_insecure',
359
+ details: 'Consider using: crypto.randomBytes(), crypto.randomInt(), crypto.randomUUID(), or nanoid() for secure random generation.',
360
+ insecureFunction: mathRandomMatch[1]
361
+ };
362
+ }
363
+
364
+ // Check for Date-based random, but exclude legitimate timestamp usage
365
+ const dateRandomMatch = line.match(/(Date\.now\(\)|new\s+Date\(\)\.getTime\(\))/);
366
+ if (dateRandomMatch && this.isInSecurityContext(line, fullContent, lineNumber)) {
367
+ // Check if this is legitimate timestamp usage (JWT iat/exp, logging, etc.)
368
+ if (this.isLegitimateTimestampUsage(line)) {
369
+ return null;
370
+ }
371
+
372
+ return {
373
+ ruleId: this.ruleId,
374
+ severity: 'warning',
375
+ message: 'Using timestamp for random generation is predictable and insecure.',
376
+ line: lineNumber,
377
+ column: dateRandomMatch.index + 1,
378
+ filePath: filePath,
379
+ type: 'timestamp_random_insecure',
380
+ details: 'Consider using crypto.randomBytes() or crypto.randomUUID() for secure random generation.',
381
+ insecureFunction: dateRandomMatch[1]
382
+ };
383
+ }
384
+
385
+ return null;
386
+ }
387
+
388
+ isLegitimateTimestampUsage(line) {
389
+ // Check for legitimate timestamp usage patterns
390
+ const legitimatePatterns = [
391
+ // JWT timestamp fields - more flexible matching
392
+ /\b(?:iat|exp|nbf)\s*:\s*Math\.floor\s*\(\s*Date\.now\(\)\s*\/\s*1000\s*\)/,
393
+ /Math\.floor\s*\(\s*Date\.now\(\)\s*\/\s*1000\s*\).*(?:iat|exp|nbf)/i,
394
+
395
+ // JWT timestamp with arithmetic
396
+ /Math\.floor\s*\(\s*Date\.now\(\)\s*\/\s*1000\s*\)\s*[\+\-]\s*\d+/,
397
+
398
+ // Logging timestamps
399
+ /timestamp\s*:\s*Date\.now\(\)/i,
400
+ /createdAt\s*:\s*new\s+Date\(\)/i,
401
+ /updatedAt\s*:\s*new\s+Date\(\)/i,
402
+
403
+ // Expiration times
404
+ /expiresAt\s*:\s*new\s+Date\s*\(\s*Date\.now\(\)/i,
405
+ /expiry\s*:\s*Date\.now\(\)/i,
406
+
407
+ // Performance measurement
408
+ /performance\.now\(\)/,
409
+
410
+ // Date arithmetic (not for randomness)
411
+ /Date\.now\(\)\s*[\+\-]\s*\d+/,
412
+ /new\s+Date\s*\(\s*Date\.now\(\)\s*[\+\-]/,
413
+
414
+ // JWT context - check for jwt, token, payload keywords nearby
415
+ /(?:jwt|token|payload).*Date\.now\(\)/i,
416
+ /Date\.now\(\).*(?:jwt|token|payload)/i,
417
+ ];
418
+
419
+ return legitimatePatterns.some(pattern => pattern.test(line));
420
+ }
421
+
422
+ getFunctionContext(content, lineNumber) {
423
+ const lines = content.split('\n');
424
+
425
+ // Look backwards for function declaration
426
+ for (let i = lineNumber - 1; i >= Math.max(0, lineNumber - 10); i--) {
427
+ const line = lines[i];
428
+ const functionMatch = line.match(/(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\w*\s*(?:function\s*)?|\s*(\w+)\s*[:=]\s*(?:async\s+)?(?:function|\w*\s*=>))/);
429
+ if (functionMatch) {
430
+ return functionMatch[1] || functionMatch[2] || functionMatch[3];
431
+ }
432
+ }
433
+
434
+ return null;
435
+ }
436
+
437
+ isSecurityFunction(functionName) {
438
+ if (!functionName) return false;
439
+
440
+ const lowerFunctionName = functionName.toLowerCase();
441
+ return this.securityFunctionPatterns.some(pattern => pattern.test(lowerFunctionName)) ||
442
+ this.securityContextKeywords.some(keyword => lowerFunctionName.includes(keyword));
443
+ }
444
+
445
+ getVariableContext(line) {
446
+ // Extract variable name from assignment
447
+ const assignmentMatch = line.match(/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/);
448
+ if (assignmentMatch) {
449
+ return assignmentMatch[1];
450
+ }
451
+
452
+ // Extract property assignment
453
+ const propertyMatch = line.match(/(\w+)\s*[:=]/);
454
+ if (propertyMatch) {
455
+ return propertyMatch[1];
456
+ }
457
+
458
+ return null;
459
+ }
460
+
461
+ isSecurityVariable(variableName) {
462
+ if (!variableName) return false;
463
+
464
+ const lowerVariableName = variableName.toLowerCase();
465
+ return this.securityContextKeywords.some(keyword => lowerVariableName.includes(keyword));
466
+ }
467
+
468
+ getSurroundingLines(content, lineNumber, range) {
469
+ const lines = content.split('\n');
470
+ const start = Math.max(0, lineNumber - range - 1);
471
+ const end = Math.min(lines.length, lineNumber + range);
472
+
473
+ return lines.slice(start, end);
474
+ }
475
+
476
+ getSecureAlternatives(insecureFunction) {
477
+ const alternatives = {
478
+ 'Math.random()': 'crypto.randomBytes(), crypto.randomInt(), or crypto.randomUUID()',
479
+ 'Date.now()': 'crypto.randomBytes() or crypto.randomUUID()',
480
+ 'new Date().getTime()': 'crypto.randomBytes() or crypto.randomUUID()',
481
+ 'performance.now()': 'crypto.randomBytes() or crypto.randomUUID()'
482
+ };
483
+
484
+ return alternatives[insecureFunction] || 'Use crypto.randomBytes(), crypto.randomInt(), or crypto.randomUUID() for secure random generation.';
485
+ }
486
+
487
+ findPatternColumn(line, pattern) {
488
+ const match = pattern.exec(line);
489
+ return match ? match.index + 1 : 1;
490
+ }
491
+ }
492
+
493
+ module.exports = S010Analyzer;
@@ -0,0 +1,48 @@
1
+ {
2
+ "ruleId": "S010",
3
+ "name": "Must use cryptographically secure random number generators (CSPRNG)",
4
+ "description": "Detect usage of insecure random number generators for security purposes",
5
+ "category": "security",
6
+ "severity": "error",
7
+ "languages": ["JavaScript", "TypeScript", "Node.js"],
8
+ "tags": ["security", "owasp", "cryptographic-failures", "random", "csprng"],
9
+ "enabled": true,
10
+ "fixable": false,
11
+ "engine": "heuristic",
12
+ "metadata": {
13
+ "owaspCategory": "A02:2021 - Cryptographic Failures",
14
+ "cweId": "CWE-338",
15
+ "description": "Using insecure random number generators like Math.random() for security purposes can lead to predictable values that attackers can exploit. Cryptographically secure random number generators (CSPRNG) must be used for security-sensitive operations.",
16
+ "impact": "High - Predictable tokens, weak encryption keys, authentication bypass",
17
+ "likelihood": "Medium",
18
+ "remediation": "Use crypto.randomBytes(), crypto.randomInt(), crypto.randomUUID(), or other CSPRNG functions for security purposes"
19
+ },
20
+ "patterns": {
21
+ "vulnerable": [
22
+ "Using Math.random() for generating security tokens",
23
+ "Using Date.now() or timestamps for random generation",
24
+ "Using performance.now() for security purposes",
25
+ "Using simple increment patterns for sensitive IDs"
26
+ ],
27
+ "secure": [
28
+ "Using crypto.randomBytes() for random data",
29
+ "Using crypto.randomUUID() for unique identifiers",
30
+ "Using crypto.randomInt() for random integers",
31
+ "Using nanoid() for URL-safe IDs"
32
+ ]
33
+ },
34
+ "examples": {
35
+ "violations": [
36
+ "const token = Math.random().toString(36).substring(2);",
37
+ "const sessionId = Date.now().toString();",
38
+ "const apiKey = Math.floor(Math.random() * 1000000);",
39
+ "const nonce = performance.now().toString();"
40
+ ],
41
+ "fixes": [
42
+ "const token = crypto.randomUUID();",
43
+ "const sessionId = crypto.randomBytes(16).toString('hex');",
44
+ "const apiKey = crypto.randomInt(100000, 999999);",
45
+ "const nonce = crypto.randomBytes(8).toString('hex');"
46
+ ]
47
+ }
48
+ }
@@ -0,0 +1,149 @@
1
+ # S016 Analysis Strategy
2
+
3
+ ## Rule Focus: Prevent Sensitive Data in URL Query Parameters
4
+
5
+ ### Detection Pipeline:
6
+
7
+ 1. **Find URL Construction Patterns** (Symbol-based)
8
+ - Use AST to detect URL construction: `new URL()`, `new URLSearchParams()`
9
+ - Identify HTTP client calls: `fetch()`, `axios.*()`, `request.*()`
10
+ - Find location manipulations: `window.location.href`, `location.search`
11
+
12
+ 2. **Locate Query Parameter Usage** (Symbol-based)
13
+ - Find property access patterns: `params.password`, `query.token`
14
+ - Detect query string manipulations: `querystring.stringify()`, `qs.stringify()`
15
+ - Identify URL building with template literals and concatenation
16
+
17
+ 3. **Analyze Parameter Content** (AST + Pattern Analysis)
18
+ - Check parameter names against sensitive patterns
19
+ - Validate URL construction arguments
20
+ - Detect object properties in URLSearchParams constructor
21
+ - Check for sensitive data in query string values
22
+
23
+ ### Violations to Detect:
24
+
25
+ #### 1. **Sensitive Data in URL Constructor**
26
+ ```javascript
27
+ // ❌ Bad - Password in URL
28
+ const url = new URL(`https://api.com/login?password=${userPassword}`);
29
+
30
+ // ❌ Bad - Token in URLSearchParams
31
+ const params = new URLSearchParams({
32
+ token: authToken,
33
+ apiKey: process.env.API_KEY
34
+ });
35
+
36
+ // ✅ Good - Use request body or headers
37
+ const response = await fetch('https://api.com/login', {
38
+ method: 'POST',
39
+ headers: { 'Authorization': `Bearer ${token}` },
40
+ body: JSON.stringify({ credentials })
41
+ });
42
+ ```
43
+
44
+ #### 2. **Sensitive Data in HTTP Client URLs**
45
+ ```javascript
46
+ // ❌ Bad - Credentials in query string
47
+ fetch(`https://api.com/users?password=${pwd}&ssn=${userSSN}`);
48
+
49
+ // ❌ Bad - Sensitive data in axios
50
+ axios.get('/api/profile', {
51
+ params: {
52
+ email: user.email,
53
+ creditCard: user.cardNumber
54
+ }
55
+ });
56
+
57
+ // ✅ Good - Use POST with body or secure headers
58
+ axios.post('/api/profile', {
59
+ email: user.email,
60
+ // Move sensitive data to encrypted request body
61
+ }, {
62
+ headers: { 'Authorization': `Bearer ${token}` }
63
+ });
64
+ ```
65
+
66
+ #### 3. **Location/Window Object Manipulations**
67
+ ```javascript
68
+ // ❌ Bad - Sensitive data in location
69
+ window.location.href = `/dashboard?token=${jwt}&password=${pwd}`;
70
+
71
+ // ❌ Bad - Search parameter manipulation
72
+ location.search += `&apiKey=${apiKey}&secret=${clientSecret}`;
73
+
74
+ // ✅ Good - Use sessionStorage or secure alternatives
75
+ sessionStorage.setItem('token', jwt);
76
+ window.location.href = '/dashboard';
77
+ ```
78
+
79
+ #### 4. **Query String Building**
80
+ ```javascript
81
+ // ❌ Bad - Sensitive data in query string
82
+ const params = querystring.stringify({
83
+ username: user.name,
84
+ password: user.password,
85
+ creditCard: user.card
86
+ });
87
+
88
+ // ✅ Good - Separate public and sensitive data
89
+ const params = querystring.stringify({
90
+ username: user.name,
91
+ // Move sensitive data to secure request body
92
+ });
93
+ ```
94
+
95
+ ### Sensitive Data Patterns to Detect:
96
+
97
+ #### Authentication & Authorization:
98
+ - `password`, `passwd`, `pwd`, `pass`
99
+ - `token`, `jwt`, `accesstoken`, `refreshtoken`, `bearertoken`
100
+ - `secret`, `secretkey`, `clientsecret`, `serversecret`
101
+ - `apikey`, `api_key`, `key`, `privatekey`, `publickey`
102
+ - `auth`, `authorization`, `authenticate`
103
+ - `sessionid`, `session_id`, `jsessionid`
104
+ - `csrf`, `csrftoken`, `xsrf`
105
+
106
+ #### Financial & Personal:
107
+ - `ssn`, `social`, `socialsecurity`
108
+ - `creditcard`, `cardnumber`, `cardnum`, `ccnumber`
109
+ - `cvv`, `cvc`, `cvd`, `cid`
110
+ - `pin`, `pincode`
111
+ - `bankaccount`, `routing`, `iban`
112
+
113
+ #### Personal Identifiable Information:
114
+ - `email`, `emailaddress`, `mail`
115
+ - `phone`, `phonenumber`, `mobile`, `tel`
116
+ - `address`, `homeaddress`, `zipcode`, `postal`
117
+ - `birthdate`, `birthday`, `dob`
118
+ - `license`, `passport`, `identity`
119
+
120
+ ### URL Construction Methods to Monitor:
121
+ - `new URL()`
122
+ - `new URLSearchParams()`
123
+ - `fetch()` with query parameters
124
+ - `axios.*()` methods with params
125
+ - `request.*()` methods
126
+ - `window.location.href` assignments
127
+ - `location.search` manipulations
128
+ - `querystring.stringify()` / `qs.stringify()`
129
+
130
+ ### Security Risks:
131
+ 1. **Server Logs**: URLs with query parameters are logged by web servers
132
+ 2. **Browser History**: URLs are stored in browser history
133
+ 3. **Referrer Headers**: URLs can leak via referrer headers to third parties
134
+ 4. **Network Traces**: URLs visible in network monitoring tools
135
+ 5. **Proxy Logs**: Corporate/ISP proxies log full URLs
136
+ 6. **Cache Issues**: URLs may be cached by CDNs or proxy servers
137
+
138
+ ### Recommended Alternatives:
139
+ 1. **Request Body**: Use POST/PUT with JSON body for sensitive data
140
+ 2. **Secure Headers**: Use Authorization header for tokens
141
+ 3. **Session Storage**: Store sensitive data in secure browser storage
142
+ 4. **Encrypted Payloads**: Encrypt sensitive data before transmission
143
+ 5. **Server-Side Sessions**: Use session IDs instead of actual sensitive data
144
+
145
+ ### Implementation Priority:
146
+ 1. **Phase 1**: Basic URL construction detection (new URL, fetch)
147
+ 2. **Phase 2**: HTTP client library detection (axios, request)
148
+ 3. **Phase 3**: Query string manipulation detection
149
+ 4. **Phase 4**: Advanced patterns (template literals, dynamic construction)