@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,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S007 Semantic Analyzer - No Plaintext OTP
|
|
3
|
+
* Semantic analysis for detecting plaintext OTP storage/transmission using Symbol Table
|
|
4
|
+
*
|
|
5
|
+
* Advantages over regex:
|
|
6
|
+
* - Context-aware analysis (knows function scopes, variable relationships)
|
|
7
|
+
* - Cross-file dependency tracking
|
|
8
|
+
* - Type information and data flow analysis
|
|
9
|
+
* - Reduced false positives through semantic understanding
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const SemanticRuleBase = require('../../../core/semantic-rule-base');
|
|
13
|
+
|
|
14
|
+
class S007SemanticAnalyzer extends SemanticRuleBase {
|
|
15
|
+
constructor(ruleId = 'S007') {
|
|
16
|
+
super(ruleId, {
|
|
17
|
+
category: 'security',
|
|
18
|
+
severity: 'error',
|
|
19
|
+
description: 'Detects plaintext OTP storage and transmission using semantic analysis',
|
|
20
|
+
crossFileAnalysis: true,
|
|
21
|
+
requiresTypeChecker: false,
|
|
22
|
+
cacheResults: true
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// OTP-related patterns for semantic analysis
|
|
26
|
+
this.otpPatterns = {
|
|
27
|
+
// Variable names that suggest OTP usage
|
|
28
|
+
variableNames: [
|
|
29
|
+
/^(otp|totp|hotp)$/i,
|
|
30
|
+
/^(one_?time|onetime)_?(password|pass|code)$/i,
|
|
31
|
+
/^(auth|verification|sms|temp|security|access|login)_?code$/i,
|
|
32
|
+
/^(two_?factor|2fa|mfa)_?(token|code)$/i,
|
|
33
|
+
/^pin_?code$/i
|
|
34
|
+
],
|
|
35
|
+
|
|
36
|
+
// Function names that handle OTP
|
|
37
|
+
functionNames: [
|
|
38
|
+
/^(generate|create|store|save|send|validate|verify)_?otp$/i,
|
|
39
|
+
/^otp_?(generate|create|store|save|send|validate|verify)$/i,
|
|
40
|
+
/^(generate|create|store|save|send|validate|verify)_?(auth|verification|sms|temp|security|access|login)_?code$/i,
|
|
41
|
+
/^send_?(sms|email|auth)_?code$/i
|
|
42
|
+
],
|
|
43
|
+
|
|
44
|
+
// Method calls that suggest OTP operations
|
|
45
|
+
methodNames: [
|
|
46
|
+
'sendOtp', 'storeOtp', 'saveOtp', 'generateOtp', 'verifyOtp',
|
|
47
|
+
'sendSmsCode', 'sendAuthCode', 'storeAuthCode', 'saveAuthCode'
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Dangerous storage/transmission patterns
|
|
52
|
+
this.dangerousOperations = {
|
|
53
|
+
// Database storage methods
|
|
54
|
+
storage: [
|
|
55
|
+
'save', 'store', 'insert', 'update', 'create', 'persist',
|
|
56
|
+
'collection.insertOne', 'collection.updateOne', 'db.save',
|
|
57
|
+
'redis.set', 'redis.hset', 'cache.put', 'cache.set'
|
|
58
|
+
],
|
|
59
|
+
|
|
60
|
+
// Network transmission methods
|
|
61
|
+
transmission: [
|
|
62
|
+
'send', 'emit', 'post', 'put', 'response.json', 'res.send',
|
|
63
|
+
'email.send', 'sms.send', 'notify', 'broadcast'
|
|
64
|
+
],
|
|
65
|
+
|
|
66
|
+
// Browser storage
|
|
67
|
+
browserStorage: [
|
|
68
|
+
'localStorage.setItem', 'sessionStorage.setItem',
|
|
69
|
+
'localStorage.set', 'sessionStorage.set'
|
|
70
|
+
],
|
|
71
|
+
|
|
72
|
+
// Logging (usually unsafe for OTP)
|
|
73
|
+
logging: [
|
|
74
|
+
'console.log', 'console.debug', 'console.info',
|
|
75
|
+
'logger.info', 'logger.debug', 'log.info'
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Safe operations that encrypt/hash
|
|
80
|
+
this.safeOperations = [
|
|
81
|
+
'bcrypt.hash', 'crypto.createHash', 'crypto.createHmac',
|
|
82
|
+
'hash', 'encrypt', 'cipher', 'secure', 'pbkdf2',
|
|
83
|
+
'scrypt', 'argon2'
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Main file analysis using Symbol Table
|
|
89
|
+
*/
|
|
90
|
+
async analyzeFile(filePath, options = {}) {
|
|
91
|
+
try {
|
|
92
|
+
if (options.verbose) {
|
|
93
|
+
console.log('🧠 Analyzing file:', filePath);
|
|
94
|
+
}
|
|
95
|
+
const symbolTable = await this.getSymbolTable(filePath);
|
|
96
|
+
if (!symbolTable) {
|
|
97
|
+
if (options.verbose) {
|
|
98
|
+
console.warn(`⚠️ ${this.ruleId}: No symbol table available for ${filePath}`);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (options.verbose) {
|
|
104
|
+
console.log(`🧠 ${this.ruleId}: Analyzing ${filePath} with Symbol Table`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Analyze different aspects using semantic information with error handling
|
|
108
|
+
try {
|
|
109
|
+
await this.analyzeVariableUsage(symbolTable, filePath);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (options.verbose) {
|
|
112
|
+
console.warn(`⚠️ ${this.ruleId}: Variable analysis failed for ${filePath}: ${error.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await this.analyzeFunctionCalls(symbolTable, filePath);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (options.verbose) {
|
|
120
|
+
console.warn(`⚠️ ${this.ruleId}: Function call analysis failed for ${filePath}: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await this.analyzeMethodChains(symbolTable, filePath);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (options.verbose) {
|
|
128
|
+
console.warn(`⚠️ ${this.ruleId}: Method chain analysis failed for ${filePath}: ${error.message}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
await this.analyzeDataFlow(symbolTable, filePath);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (options.verbose) {
|
|
136
|
+
console.warn(`⚠️ ${this.ruleId}: Data flow analysis failed for ${filePath}: ${error.message}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Cross-file analysis if enabled
|
|
141
|
+
if (this.config.crossFileAnalysis) {
|
|
142
|
+
try {
|
|
143
|
+
await this.analyzeCrossFileReferences(symbolTable, filePath);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (options.verbose) {
|
|
146
|
+
console.warn(`⚠️ ${this.ruleId}: Cross-file analysis failed for ${filePath}: ${error.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error(`❌ ${this.ruleId}: Semantic analysis failed for ${filePath}: ${error.message}`);
|
|
153
|
+
// Don't throw - let the wrapper handle fallback
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Analyze variable declarations and assignments for OTP patterns
|
|
159
|
+
*/
|
|
160
|
+
async analyzeVariableUsage(symbolTable, filePath) {
|
|
161
|
+
// Check variable declarations with safe array handling
|
|
162
|
+
const variables = symbolTable.variables || [];
|
|
163
|
+
const constants = symbolTable.constants || [];
|
|
164
|
+
const allVariables = [...variables, ...constants];
|
|
165
|
+
|
|
166
|
+
for (const variable of allVariables) {
|
|
167
|
+
if (this.isOtpVariable(variable.name)) {
|
|
168
|
+
// Check if this OTP variable is used in dangerous contexts
|
|
169
|
+
const dangerousUsages = await this.findDangerousVariableUsages(
|
|
170
|
+
symbolTable,
|
|
171
|
+
variable.name,
|
|
172
|
+
variable.line
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
for (const usage of dangerousUsages) {
|
|
176
|
+
this.addViolation({
|
|
177
|
+
filePath,
|
|
178
|
+
line: usage.line,
|
|
179
|
+
column: usage.column || 1,
|
|
180
|
+
message: `OTP variable '${variable.name}' is used in ${usage.context} without encryption`,
|
|
181
|
+
type: 'semantic_otp_variable_usage',
|
|
182
|
+
severity: this.determineSeverity(usage.context),
|
|
183
|
+
symbolContext: {
|
|
184
|
+
variableName: variable.name,
|
|
185
|
+
variableType: variable.type,
|
|
186
|
+
usageContext: usage.context,
|
|
187
|
+
operation: usage.operation
|
|
188
|
+
},
|
|
189
|
+
suggestion: this.generateSecuritySuggestion(usage.context),
|
|
190
|
+
codeSnippet: usage.codeSnippet
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Analyze function calls for OTP-related operations
|
|
199
|
+
*/
|
|
200
|
+
async analyzeFunctionCalls(symbolTable, filePath) {
|
|
201
|
+
const functionCalls = symbolTable.functionCalls || [];
|
|
202
|
+
for (const functionCall of functionCalls) {
|
|
203
|
+
// Check if function name suggests OTP handling
|
|
204
|
+
if (this.isOtpFunction(functionCall.functionName)) {
|
|
205
|
+
const context = await this.analyzeFunctionContext(symbolTable, functionCall);
|
|
206
|
+
|
|
207
|
+
if (context.isDangerous && !context.isSecure) {
|
|
208
|
+
this.addViolation({
|
|
209
|
+
filePath,
|
|
210
|
+
line: functionCall.line,
|
|
211
|
+
column: functionCall.column || 1,
|
|
212
|
+
message: `Function '${functionCall.functionName}' may handle OTP in plaintext`,
|
|
213
|
+
type: 'semantic_otp_function_call',
|
|
214
|
+
severity: 'warning',
|
|
215
|
+
symbolContext: {
|
|
216
|
+
functionName: functionCall.functionName,
|
|
217
|
+
arguments: functionCall.arguments,
|
|
218
|
+
returnType: functionCall.returnType,
|
|
219
|
+
context: context
|
|
220
|
+
},
|
|
221
|
+
suggestion: 'Ensure OTP values are encrypted before processing'
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check for dangerous operations with OTP arguments
|
|
227
|
+
if (this.isDangerousOperation(functionCall.functionName)) {
|
|
228
|
+
const hasOtpArguments = await this.checkOtpArguments(symbolTable, functionCall);
|
|
229
|
+
|
|
230
|
+
if (hasOtpArguments.length > 0) {
|
|
231
|
+
this.addViolation({
|
|
232
|
+
filePath,
|
|
233
|
+
line: functionCall.line,
|
|
234
|
+
column: functionCall.column || 1,
|
|
235
|
+
message: `Potential plaintext OTP passed to ${functionCall.functionName}`,
|
|
236
|
+
type: 'semantic_plaintext_otp_argument',
|
|
237
|
+
severity: 'error',
|
|
238
|
+
symbolContext: {
|
|
239
|
+
functionName: functionCall.functionName,
|
|
240
|
+
otpArguments: hasOtpArguments,
|
|
241
|
+
operation: this.categorizeDangerousOperation(functionCall.functionName)
|
|
242
|
+
},
|
|
243
|
+
suggestion: 'Encrypt or hash OTP values before storage/transmission'
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Analyze method chains for OTP operations
|
|
252
|
+
*/
|
|
253
|
+
async analyzeMethodChains(symbolTable, filePath) {
|
|
254
|
+
const methodCalls = symbolTable.methodCalls || [];
|
|
255
|
+
for (const methodCall of methodCalls) {
|
|
256
|
+
// Look for patterns like: otpCode.save() or user.storeOtp()
|
|
257
|
+
if (this.isOtpRelatedMethodCall(methodCall)) {
|
|
258
|
+
const chainContext = await this.analyzeMethodChain(symbolTable, methodCall);
|
|
259
|
+
|
|
260
|
+
if (chainContext.isUnsafe) {
|
|
261
|
+
this.addViolation({
|
|
262
|
+
filePath,
|
|
263
|
+
line: methodCall.line,
|
|
264
|
+
column: methodCall.column || 1,
|
|
265
|
+
message: `Method chain exposes OTP in plaintext: ${chainContext.chain}`,
|
|
266
|
+
type: 'semantic_method_chain_otp',
|
|
267
|
+
severity: 'error',
|
|
268
|
+
symbolContext: {
|
|
269
|
+
methodChain: chainContext.chain,
|
|
270
|
+
objectName: methodCall.objectName,
|
|
271
|
+
methodName: methodCall.methodName,
|
|
272
|
+
dataFlow: chainContext.dataFlow
|
|
273
|
+
},
|
|
274
|
+
suggestion: 'Use secure methods for OTP handling in method chains'
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Analyze data flow to track OTP values through the code
|
|
283
|
+
*/
|
|
284
|
+
async analyzeDataFlow(symbolTable, filePath) {
|
|
285
|
+
// Find all OTP-related variables and track their usage
|
|
286
|
+
const variables = symbolTable.variables || [];
|
|
287
|
+
const otpVariables = variables.filter(v => this.isOtpVariable(v.name));
|
|
288
|
+
|
|
289
|
+
for (const otpVar of otpVariables) {
|
|
290
|
+
const dataFlow = await this.traceDataFlow(symbolTable, otpVar.name);
|
|
291
|
+
|
|
292
|
+
// Check if OTP flows to dangerous sinks
|
|
293
|
+
const dangerousSinks = dataFlow.filter(flow =>
|
|
294
|
+
this.isDangerousOperation(flow.operation) &&
|
|
295
|
+
!this.isSafeOperation(flow.operation)
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
for (const sink of dangerousSinks) {
|
|
299
|
+
this.addViolation({
|
|
300
|
+
filePath,
|
|
301
|
+
line: sink.line,
|
|
302
|
+
column: sink.column || 1,
|
|
303
|
+
message: `OTP variable '${otpVar.name}' flows to unsafe operation '${sink.operation}'`,
|
|
304
|
+
type: 'semantic_data_flow_violation',
|
|
305
|
+
severity: 'error',
|
|
306
|
+
symbolContext: {
|
|
307
|
+
sourceVariable: otpVar.name,
|
|
308
|
+
dataFlowPath: dataFlow,
|
|
309
|
+
dangerousSink: sink,
|
|
310
|
+
flowDistance: sink.distance
|
|
311
|
+
},
|
|
312
|
+
suggestion: 'Ensure OTP is encrypted before reaching this operation'
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Cross-file analysis for OTP security
|
|
320
|
+
*/
|
|
321
|
+
async analyzeCrossFileReferences(symbolTable, filePath) {
|
|
322
|
+
// Find OTP-related exports
|
|
323
|
+
const exports = symbolTable.exports || [];
|
|
324
|
+
const otpExports = exports.filter(exp =>
|
|
325
|
+
this.isOtpFunction(exp.name) || this.isOtpVariable(exp.name)
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
for (const otpExport of otpExports) {
|
|
329
|
+
// Find where this OTP symbol is imported and used
|
|
330
|
+
const crossFileUsages = await this.findCrossFileUsages(otpExport.name, [filePath]);
|
|
331
|
+
|
|
332
|
+
for (const usage of crossFileUsages) {
|
|
333
|
+
const usageSymbolTable = await this.getSymbolTable(usage.filePath);
|
|
334
|
+
if (!usageSymbolTable) continue;
|
|
335
|
+
|
|
336
|
+
// Check if the imported OTP symbol is used safely
|
|
337
|
+
const dangerousUsage = await this.analyzeImportedOtpUsage(
|
|
338
|
+
usageSymbolTable,
|
|
339
|
+
otpExport.name,
|
|
340
|
+
usage
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (dangerousUsage) {
|
|
344
|
+
this.addViolation({
|
|
345
|
+
filePath: usage.filePath,
|
|
346
|
+
line: usage.line,
|
|
347
|
+
column: usage.column || 1,
|
|
348
|
+
message: `Imported OTP symbol '${otpExport.name}' used unsafely`,
|
|
349
|
+
type: 'semantic_cross_file_otp_violation',
|
|
350
|
+
severity: 'warning',
|
|
351
|
+
symbolContext: {
|
|
352
|
+
exportedFrom: filePath,
|
|
353
|
+
importedSymbol: otpExport.name,
|
|
354
|
+
usageContext: dangerousUsage
|
|
355
|
+
},
|
|
356
|
+
crossFileReferences: [{
|
|
357
|
+
sourceFile: filePath,
|
|
358
|
+
targetFile: usage.filePath,
|
|
359
|
+
symbol: otpExport.name,
|
|
360
|
+
usageType: dangerousUsage.type
|
|
361
|
+
}],
|
|
362
|
+
suggestion: 'Ensure consistent OTP security across file boundaries'
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Helper methods for semantic analysis
|
|
371
|
+
*/
|
|
372
|
+
|
|
373
|
+
isOtpVariable(variableName) {
|
|
374
|
+
return this.otpPatterns.variableNames.some(pattern => pattern.test(variableName));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
isOtpFunction(functionName) {
|
|
378
|
+
return this.otpPatterns.functionNames.some(pattern => pattern.test(functionName)) ||
|
|
379
|
+
this.otpPatterns.methodNames.includes(functionName);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
isDangerousOperation(operationName) {
|
|
383
|
+
return Object.values(this.dangerousOperations).flat().some(op =>
|
|
384
|
+
operationName.includes(op) || operationName === op
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
isSafeOperation(operationName) {
|
|
389
|
+
return this.safeOperations.some(op => operationName.includes(op));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async findDangerousVariableUsages(symbolTable, variableName, declarationLine) {
|
|
393
|
+
const usages = [];
|
|
394
|
+
|
|
395
|
+
// Check function calls that use this variable with safe array access
|
|
396
|
+
const functionCalls = symbolTable.functionCalls || [];
|
|
397
|
+
const relatedCalls = functionCalls.filter(call =>
|
|
398
|
+
call.arguments && call.arguments.some(arg => arg && arg.includes && arg.includes(variableName))
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
for (const call of relatedCalls) {
|
|
402
|
+
if (this.isDangerousOperation(call.functionName)) {
|
|
403
|
+
usages.push({
|
|
404
|
+
line: call.line,
|
|
405
|
+
column: call.column,
|
|
406
|
+
context: this.categorizeDangerousOperation(call.functionName),
|
|
407
|
+
operation: call.functionName,
|
|
408
|
+
codeSnippet: `${call.functionName}(${call.arguments.join(', ')})`
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return usages;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
categorizeDangerousOperation(operationName) {
|
|
417
|
+
if (this.dangerousOperations.storage.some(op => operationName.includes(op))) {
|
|
418
|
+
return 'storage';
|
|
419
|
+
}
|
|
420
|
+
if (this.dangerousOperations.transmission.some(op => operationName.includes(op))) {
|
|
421
|
+
return 'transmission';
|
|
422
|
+
}
|
|
423
|
+
if (this.dangerousOperations.browserStorage.some(op => operationName.includes(op))) {
|
|
424
|
+
return 'browser_storage';
|
|
425
|
+
}
|
|
426
|
+
if (this.dangerousOperations.logging.some(op => operationName.includes(op))) {
|
|
427
|
+
return 'logging';
|
|
428
|
+
}
|
|
429
|
+
return 'unknown_dangerous';
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
determineSeverity(context) {
|
|
433
|
+
const severityMap = {
|
|
434
|
+
'storage': 'error',
|
|
435
|
+
'transmission': 'error',
|
|
436
|
+
'browser_storage': 'warning',
|
|
437
|
+
'logging': 'warning',
|
|
438
|
+
'unknown_dangerous': 'info'
|
|
439
|
+
};
|
|
440
|
+
return severityMap[context] || 'warning';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
generateSecuritySuggestion(context) {
|
|
444
|
+
const suggestions = {
|
|
445
|
+
'storage': 'Use bcrypt.hash() or crypto.createHash() before storing OTP',
|
|
446
|
+
'transmission': 'Encrypt OTP before transmission or use secure channels',
|
|
447
|
+
'browser_storage': 'Avoid storing OTP in browser storage, use secure tokens instead',
|
|
448
|
+
'logging': 'Never log actual OTP values, log only metadata like generation time',
|
|
449
|
+
'unknown_dangerous': 'Review this operation for OTP security best practices'
|
|
450
|
+
};
|
|
451
|
+
return suggestions[context] || 'Ensure OTP security best practices';
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async analyzeFunctionContext(symbolTable, functionCall) {
|
|
455
|
+
// Analyze the context around the function call
|
|
456
|
+
const context = {
|
|
457
|
+
isDangerous: false,
|
|
458
|
+
isSecure: false,
|
|
459
|
+
securityMeasures: []
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// Check if function is called within a security context
|
|
463
|
+
const nearbyLines = this.getNearbyLines(symbolTable, functionCall.line, 3);
|
|
464
|
+
|
|
465
|
+
for (const line of nearbyLines) {
|
|
466
|
+
if (this.safeOperations.some(op => line.text.includes(op))) {
|
|
467
|
+
context.isSecure = true;
|
|
468
|
+
context.securityMeasures.push(line.text);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (this.isDangerousOperation(line.text)) {
|
|
472
|
+
context.isDangerous = true;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return context;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async checkOtpArguments(symbolTable, functionCall) {
|
|
480
|
+
const otpArguments = [];
|
|
481
|
+
|
|
482
|
+
if (!functionCall.arguments) return otpArguments;
|
|
483
|
+
|
|
484
|
+
for (const arg of functionCall.arguments) {
|
|
485
|
+
// Check if argument looks like OTP variable
|
|
486
|
+
if (this.isOtpVariable(arg)) {
|
|
487
|
+
otpArguments.push({
|
|
488
|
+
value: arg,
|
|
489
|
+
type: 'variable',
|
|
490
|
+
confidence: 'high'
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Check if argument is a string that looks like OTP
|
|
495
|
+
if (this.looksLikeOtpValue(arg)) {
|
|
496
|
+
otpArguments.push({
|
|
497
|
+
value: arg,
|
|
498
|
+
type: 'literal',
|
|
499
|
+
confidence: 'medium'
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return otpArguments;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
looksLikeOtpValue(value) {
|
|
508
|
+
// Patterns that suggest OTP values
|
|
509
|
+
const otpValuePatterns = [
|
|
510
|
+
/^['"`]\d{4,8}['"`]$/, // 4-8 digit codes in quotes
|
|
511
|
+
/^['"`][A-Z0-9]{4,8}['"`]$/i, // 4-8 alphanumeric codes
|
|
512
|
+
/\$\{.*otp.*\}/i, // Template strings with otp
|
|
513
|
+
/\+.*otp/i // String concatenation with otp
|
|
514
|
+
];
|
|
515
|
+
|
|
516
|
+
return otpValuePatterns.some(pattern => pattern.test(value));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
isOtpRelatedMethodCall(methodCall) {
|
|
520
|
+
return this.isOtpVariable(methodCall.objectName) ||
|
|
521
|
+
this.isOtpFunction(methodCall.methodName) ||
|
|
522
|
+
(methodCall.objectName && methodCall.objectName.toLowerCase().includes('otp'));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async analyzeMethodChain(symbolTable, methodCall) {
|
|
526
|
+
const chain = `${methodCall.objectName}.${methodCall.methodName}()`;
|
|
527
|
+
const isUnsafe = this.isDangerousOperation(methodCall.methodName) &&
|
|
528
|
+
!this.isSafeOperation(methodCall.methodName);
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
chain,
|
|
532
|
+
isUnsafe,
|
|
533
|
+
dataFlow: await this.traceMethodChainDataFlow(symbolTable, methodCall)
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async traceDataFlow(symbolTable, variableName) {
|
|
538
|
+
const dataFlow = [];
|
|
539
|
+
|
|
540
|
+
// Find all usages of this variable in function calls
|
|
541
|
+
const functionCalls = symbolTable.functionCalls || [];
|
|
542
|
+
for (const call of functionCalls) {
|
|
543
|
+
if (call.arguments && call.arguments.some(arg => arg && arg.includes && arg.includes(variableName))) {
|
|
544
|
+
dataFlow.push({
|
|
545
|
+
line: call.line,
|
|
546
|
+
operation: call.functionName,
|
|
547
|
+
type: 'function_call',
|
|
548
|
+
distance: Math.abs(call.line - this.getVariableDeclarationLine(symbolTable, variableName))
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Find all usages in method calls
|
|
554
|
+
const methodCalls = symbolTable.methodCalls || [];
|
|
555
|
+
for (const call of methodCalls) {
|
|
556
|
+
if (call.arguments && call.arguments.some(arg => arg && arg.includes && arg.includes(variableName))) {
|
|
557
|
+
dataFlow.push({
|
|
558
|
+
line: call.line,
|
|
559
|
+
operation: `${call.objectName}.${call.methodName}`,
|
|
560
|
+
type: 'method_call',
|
|
561
|
+
distance: Math.abs(call.line - this.getVariableDeclarationLine(symbolTable, variableName))
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return dataFlow.sort((a, b) => a.line - b.line);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
getVariableDeclarationLine(symbolTable, variableName) {
|
|
570
|
+
const variables = symbolTable.variables || [];
|
|
571
|
+
const constants = symbolTable.constants || [];
|
|
572
|
+
const variable = variables.find(v => v.name === variableName) ||
|
|
573
|
+
constants.find(v => v.name === variableName);
|
|
574
|
+
return variable ? variable.line : 1;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
async traceMethodChainDataFlow(symbolTable, methodCall) {
|
|
578
|
+
// Simplified data flow analysis for method chains
|
|
579
|
+
return {
|
|
580
|
+
source: methodCall.objectName,
|
|
581
|
+
operation: methodCall.methodName,
|
|
582
|
+
line: methodCall.line
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async analyzeImportedOtpUsage(symbolTable, symbolName, usage) {
|
|
587
|
+
// Check how the imported OTP symbol is used
|
|
588
|
+
const functionCalls = symbolTable.functionCalls || [];
|
|
589
|
+
const usages = functionCalls.filter(call =>
|
|
590
|
+
call.functionName === symbolName ||
|
|
591
|
+
(call.arguments && call.arguments.includes(symbolName))
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
for (const use of usages) {
|
|
595
|
+
if (this.isDangerousOperation(use.functionName) && !this.isSafeOperation(use.functionName)) {
|
|
596
|
+
return {
|
|
597
|
+
type: 'dangerous_usage',
|
|
598
|
+
operation: use.functionName,
|
|
599
|
+
line: use.line,
|
|
600
|
+
context: this.categorizeDangerousOperation(use.functionName)
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
module.exports = S007SemanticAnalyzer;
|