@sun-asterisk/sunlint 1.3.0 → 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.
- package/CHANGELOG.md +115 -1
- package/CONTRIBUTING.md +249 -605
- package/README.md +3 -4
- package/config/ci-cd.json +54 -0
- package/config/development.json +56 -0
- package/config/large-project.json +143 -0
- package/config/presets/all.json +0 -1
- package/config/release.json +70 -0
- package/config/rule-analysis-strategies.js +38 -3
- package/config/rules/enhanced-rules-registry.json +474 -1179
- package/config/rules/rules-registry-generated.json +3 -3
- package/core/cli-action-handler.js +24 -30
- package/core/cli-program.js +11 -3
- package/core/config-merger.js +29 -2
- package/core/enhanced-rules-registry.js +3 -2
- package/core/semantic-engine.js +129 -19
- package/core/semantic-rule-base.js +4 -2
- package/core/unified-rule-registry.js +1 -1
- package/docs/COMMAND-EXAMPLES.md +134 -0
- package/docs/LARGE-PROJECT-GUIDE.md +324 -0
- package/engines/heuristic-engine.js +135 -16
- package/integrations/eslint/plugin/index.js +0 -2
- package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
- package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
- package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
- package/origin-rules/common-en.md +19 -15
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
- package/rules/common/C006_function_naming/analyzer.js +29 -3
- package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
- package/rules/common/C010_limit_block_nesting/config.json +64 -0
- package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
- package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
- package/rules/common/C013_no_dead_code/analyzer.js +75 -177
- package/rules/common/C013_no_dead_code/config.json +61 -0
- package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
- package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
- package/rules/common/C014_dependency_injection/analyzer.js +48 -313
- package/rules/common/C014_dependency_injection/config.json +26 -0
- package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
- package/rules/common/C017_constructor_logic/analyzer.js +254 -17
- package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
- package/rules/common/C018_no_throw_generic_error/config.json +50 -0
- package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
- package/rules/common/C019_log_level_usage/analyzer.js +110 -317
- package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
- package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
- package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
- package/rules/common/C023_no_duplicate_variable/config.json +50 -0
- package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
- package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
- package/rules/common/C033_separate_service_repository/README.md +78 -0
- package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
- package/rules/common/C033_separate_service_repository/config.json +50 -0
- package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
- package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
- package/rules/common/C035_error_logging_context/analyzer.js +232 -0
- package/rules/common/C035_error_logging_context/config.json +54 -0
- package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
- package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
- package/rules/common/C040_centralized_validation/analyzer.js +165 -0
- package/rules/common/C040_centralized_validation/config.json +46 -0
- package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
- package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
- package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
- package/rules/common/C076_explicit_function_types/README.md +30 -0
- package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
- package/rules/common/C076_explicit_function_types/config.json +15 -0
- package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
- package/rules/index.js +6 -1
- package/rules/parser/rule-parser.js +13 -2
- package/rules/security/S005_no_origin_auth/README.md +226 -0
- package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
- package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
- package/rules/security/S005_no_origin_auth/config.json +85 -0
- package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
- package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
- package/rules/security/S007_no_plaintext_otp/README.md +198 -0
- package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
- package/rules/security/S007_no_plaintext_otp/config.json +79 -0
- package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
- package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
- package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
- package/rules/security/S009_no_insecure_encryption/README.md +158 -0
- package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
- package/rules/security/S009_no_insecure_encryption/config.json +55 -0
- package/rules/security/S010_no_insecure_encryption/README.md +224 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
- package/rules/security/S010_no_insecure_encryption/config.json +48 -0
- package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
- package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
- package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
- package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
- package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
- package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
- package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
- package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
- package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
- package/rules/security/S055_content_type_validation/README.md +176 -0
- package/rules/security/S055_content_type_validation/analyzer.js +312 -0
- package/rules/security/S055_content_type_validation/config.json +48 -0
- package/rules/utils/rule-helpers.js +140 -1
- package/scripts/consolidate-config.js +116 -0
- package/scripts/prepare-release.sh +1 -1
- package/config/rules/rules-registry.json +0 -765
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
- package/docs/FUTURE_PACKAGES.md +0 -83
- package/docs/HEURISTIC_VS_AI.md +0 -113
- package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
- package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
- package/docs/RELEASE_GUIDE.md +0 -230
- package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
- package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
- 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)
|