@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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S016 Main Analyzer - Sensitive Data in URL Query Parameters Detection
|
|
3
|
+
* Primary: Symbol-based analysis (when available)
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* Command: node cli.js --rule=S016 --input=examples/rule-test-fixtures/rules/S016_no_sensitive_querystring --engine=heuristic
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const S016SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
9
|
+
const S016RegexBasedAnalyzer = require("./regex-based-analyzer.js");
|
|
10
|
+
|
|
11
|
+
class S016Analyzer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
14
|
+
console.log(`🔧 [S016] Constructor called with options:`, !!options);
|
|
15
|
+
console.log(
|
|
16
|
+
`🔧 [S016] Options type:`,
|
|
17
|
+
typeof options,
|
|
18
|
+
Object.keys(options || {})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.ruleId = "S016";
|
|
23
|
+
this.ruleName = "Sensitive Data in URL Query Parameters";
|
|
24
|
+
this.description =
|
|
25
|
+
"Do not pass sensitive data (e.g. password, token, secret, apiKey, etc.) via query string in URLs. This can lead to exposure in logs, browser history, and network traces";
|
|
26
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
27
|
+
this.verbose = options.verbose || false;
|
|
28
|
+
|
|
29
|
+
// Configuration
|
|
30
|
+
this.config = {
|
|
31
|
+
useSymbolBased: true, // Primary approach
|
|
32
|
+
fallbackToRegex: true, // Only when symbol fails completely
|
|
33
|
+
symbolBasedOnly: false, // Can be set to true for pure mode
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Initialize both analyzers
|
|
37
|
+
try {
|
|
38
|
+
this.symbolAnalyzer = new S016SymbolBasedAnalyzer(this.semanticEngine);
|
|
39
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
40
|
+
console.log(`🔧 [S016] Symbol analyzer created successfully`);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`🔧 [S016] Error creating symbol analyzer:`, error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
this.regexAnalyzer = new S016RegexBasedAnalyzer(this.semanticEngine);
|
|
48
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
49
|
+
console.log(`🔧 [S016] Regex analyzer created successfully`);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`🔧 [S016] Error creating regex analyzer:`, error);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
56
|
+
console.log(`🔧 [S016] Constructor completed`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initialize with semantic engine
|
|
62
|
+
*/
|
|
63
|
+
async initialize(semanticEngine = null) {
|
|
64
|
+
if (semanticEngine) {
|
|
65
|
+
this.semanticEngine = semanticEngine;
|
|
66
|
+
}
|
|
67
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
68
|
+
|
|
69
|
+
// Initialize both analyzers
|
|
70
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
71
|
+
await this.regexAnalyzer.initialize(semanticEngine);
|
|
72
|
+
|
|
73
|
+
// Ensure verbose flag is propagated
|
|
74
|
+
this.regexAnalyzer.verbose = this.verbose;
|
|
75
|
+
this.symbolAnalyzer.verbose = this.verbose;
|
|
76
|
+
|
|
77
|
+
if (this.verbose) {
|
|
78
|
+
console.log(
|
|
79
|
+
`🔧 [S016 Hybrid] Analyzer initialized - verbose: ${this.verbose}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async analyze(files, language, options = {}) {
|
|
85
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
86
|
+
console.log(
|
|
87
|
+
`🔧 [S016] analyze() method called with ${files.length} files, language: ${language}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const violations = [];
|
|
92
|
+
|
|
93
|
+
for (const filePath of files) {
|
|
94
|
+
try {
|
|
95
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
96
|
+
console.log(`🔧 [S016] Processing file: ${filePath}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
100
|
+
violations.push(...fileViolations);
|
|
101
|
+
|
|
102
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
103
|
+
console.log(
|
|
104
|
+
`🔧 [S016] File ${filePath}: Found ${fileViolations.length} violations`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.warn(
|
|
109
|
+
`⚠ [S016] Analysis failed for ${filePath}:`,
|
|
110
|
+
error.message
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
116
|
+
console.log(`🔧 [S016] Total violations found: ${violations.length}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return violations;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async analyzeFile(filePath, options = {}) {
|
|
123
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
124
|
+
console.log(`🔧 [S016] analyzeFile() called for: ${filePath}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
128
|
+
if (
|
|
129
|
+
this.config.useSymbolBased &&
|
|
130
|
+
this.semanticEngine?.project &&
|
|
131
|
+
this.semanticEngine?.initialized
|
|
132
|
+
) {
|
|
133
|
+
try {
|
|
134
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
135
|
+
console.log(`🔧 [S016] Trying symbol-based analysis...`);
|
|
136
|
+
}
|
|
137
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
138
|
+
if (sourceFile) {
|
|
139
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
140
|
+
console.log(
|
|
141
|
+
`🔧 [S016] Source file found, analyzing with symbol-based...`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(
|
|
145
|
+
filePath,
|
|
146
|
+
{ ...options, verbose: options.verbose }
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Mark violations with analysis strategy
|
|
150
|
+
violations.forEach((v) => (v.analysisStrategy = "symbol-based"));
|
|
151
|
+
|
|
152
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
153
|
+
console.log(
|
|
154
|
+
`✅ [S016] Symbol-based analysis: ${violations.length} violations`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return violations; // Return even if 0 violations - symbol analysis completed successfully
|
|
158
|
+
} else {
|
|
159
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
160
|
+
console.log(`⚠️ [S016] Source file not found in project`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.warn(`⚠️ [S016] Symbol analysis failed: ${error.message}`);
|
|
165
|
+
// Continue to fallback
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
169
|
+
console.log(`🔄 [S016] Symbol analysis conditions check:`);
|
|
170
|
+
console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
|
|
171
|
+
console.log(` - semanticEngine: ${!!this.semanticEngine}`);
|
|
172
|
+
console.log(
|
|
173
|
+
` - semanticEngine.project: ${!!this.semanticEngine?.project}`
|
|
174
|
+
);
|
|
175
|
+
console.log(
|
|
176
|
+
` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`
|
|
177
|
+
);
|
|
178
|
+
console.log(
|
|
179
|
+
`🔄 [S016] Symbol analysis unavailable, using regex fallback`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 2. Fallback to regex-based analysis
|
|
185
|
+
if (this.config.fallbackToRegex) {
|
|
186
|
+
try {
|
|
187
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
188
|
+
console.log(`🔧 [S016] Trying regex-based analysis...`);
|
|
189
|
+
}
|
|
190
|
+
const violations = await this.regexAnalyzer.analyzeFileBasic(
|
|
191
|
+
filePath,
|
|
192
|
+
options
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Mark violations with analysis strategy
|
|
196
|
+
violations.forEach((v) => (v.analysisStrategy = "regex-fallback"));
|
|
197
|
+
|
|
198
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
199
|
+
console.log(
|
|
200
|
+
`🔄 [S016] Regex-based analysis: ${violations.length} violations`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return violations;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(`⚠ [S016] Regex analysis failed: ${error.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(`🔧 [S016] No analysis methods succeeded, returning empty`);
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
214
|
+
console.log(`🔧 [S016] analyzeFileBasic() called for: ${filePath}`);
|
|
215
|
+
console.log(`🔧 [S016] semanticEngine exists: ${!!this.semanticEngine}`);
|
|
216
|
+
console.log(`🔧 [S016] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
|
|
217
|
+
console.log(`🔧 [S016] regexAnalyzer exists: ${!!this.regexAnalyzer}`);
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
// Try symbol-based analysis first
|
|
221
|
+
if (
|
|
222
|
+
this.semanticEngine?.isSymbolEngineReady?.() &&
|
|
223
|
+
this.semanticEngine.project
|
|
224
|
+
) {
|
|
225
|
+
if (this.verbose) {
|
|
226
|
+
console.log(`🔍 [S016] Using symbol-based analysis for ${filePath}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const violations = await this.symbolAnalyzer.analyzeFileBasic(
|
|
230
|
+
filePath,
|
|
231
|
+
options
|
|
232
|
+
);
|
|
233
|
+
return violations;
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (this.verbose) {
|
|
237
|
+
console.warn(`⚠️ [S016] Symbol analysis failed: ${error.message}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Fallback to regex-based analysis
|
|
242
|
+
if (this.verbose) {
|
|
243
|
+
console.log(
|
|
244
|
+
`🔄 [S016] Using regex-based analysis (fallback) for ${filePath}`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log(`🔧 [S016] About to call regexAnalyzer.analyzeFileBasic()`);
|
|
249
|
+
try {
|
|
250
|
+
const result = await this.regexAnalyzer.analyzeFileBasic(
|
|
251
|
+
filePath,
|
|
252
|
+
options
|
|
253
|
+
);
|
|
254
|
+
console.log(
|
|
255
|
+
`🔧 [S016] Regex analyzer returned: ${result.length} violations`
|
|
256
|
+
);
|
|
257
|
+
return result;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error(`🔧 [S016] Error in regex analyzer:`, error);
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Methods for compatibility with different engine invocation patterns
|
|
266
|
+
*/
|
|
267
|
+
async analyzeFileWithSymbols(filePath, options = {}) {
|
|
268
|
+
return this.analyzeFile(filePath, options);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async analyzeWithSemantics(filePath, options = {}) {
|
|
272
|
+
return this.analyzeFile(filePath, options);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = S016Analyzer;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S016",
|
|
3
|
+
"name": "Do not pass sensitive data via query string",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"description": "S016 - Do not pass sensitive data (e.g. password, token, secret, apiKey, etc.) via query string in URLs. This can lead to exposure in logs, browser history, and network traces",
|
|
6
|
+
"severity": "error",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"semantic": {
|
|
9
|
+
"enabled": true,
|
|
10
|
+
"priority": "high",
|
|
11
|
+
"fallback": "heuristic"
|
|
12
|
+
},
|
|
13
|
+
"patterns": {
|
|
14
|
+
"include": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"],
|
|
15
|
+
"exclude": [
|
|
16
|
+
"**/*.test.js",
|
|
17
|
+
"**/*.test.ts",
|
|
18
|
+
"**/*.spec.js",
|
|
19
|
+
"**/*.spec.ts",
|
|
20
|
+
"**/node_modules/**",
|
|
21
|
+
"**/dist/**",
|
|
22
|
+
"**/build/**"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"analysis": {
|
|
26
|
+
"approach": "symbol-based-primary",
|
|
27
|
+
"fallback": "regex-based",
|
|
28
|
+
"depth": 2,
|
|
29
|
+
"timeout": 5000
|
|
30
|
+
},
|
|
31
|
+
"validation": {
|
|
32
|
+
"urlPatterns": [
|
|
33
|
+
"new URL",
|
|
34
|
+
"URLSearchParams",
|
|
35
|
+
"fetch",
|
|
36
|
+
"axios",
|
|
37
|
+
"request",
|
|
38
|
+
"location.href",
|
|
39
|
+
"location.search",
|
|
40
|
+
"querystring.stringify",
|
|
41
|
+
"qs.stringify"
|
|
42
|
+
],
|
|
43
|
+
"sensitivePatterns": [
|
|
44
|
+
"password",
|
|
45
|
+
"passwd",
|
|
46
|
+
"pwd",
|
|
47
|
+
"pass",
|
|
48
|
+
"token",
|
|
49
|
+
"jwt",
|
|
50
|
+
"accesstoken",
|
|
51
|
+
"refreshtoken",
|
|
52
|
+
"bearertoken",
|
|
53
|
+
"secret",
|
|
54
|
+
"secretkey",
|
|
55
|
+
"clientsecret",
|
|
56
|
+
"serversecret",
|
|
57
|
+
"apikey",
|
|
58
|
+
"api_key",
|
|
59
|
+
"key",
|
|
60
|
+
"privatekey",
|
|
61
|
+
"publickey",
|
|
62
|
+
"auth",
|
|
63
|
+
"authorization",
|
|
64
|
+
"authenticate",
|
|
65
|
+
"sessionid",
|
|
66
|
+
"session_id",
|
|
67
|
+
"jsessionid",
|
|
68
|
+
"csrf",
|
|
69
|
+
"csrftoken",
|
|
70
|
+
"xsrf",
|
|
71
|
+
"ssn",
|
|
72
|
+
"social",
|
|
73
|
+
"socialsecurity",
|
|
74
|
+
"creditcard",
|
|
75
|
+
"cardnumber",
|
|
76
|
+
"cardnum",
|
|
77
|
+
"ccnumber",
|
|
78
|
+
"cvv",
|
|
79
|
+
"cvc",
|
|
80
|
+
"cvd",
|
|
81
|
+
"cid",
|
|
82
|
+
"pin",
|
|
83
|
+
"pincode",
|
|
84
|
+
"bankaccount",
|
|
85
|
+
"routing",
|
|
86
|
+
"iban",
|
|
87
|
+
"email",
|
|
88
|
+
"emailaddress",
|
|
89
|
+
"mail",
|
|
90
|
+
"phone",
|
|
91
|
+
"phonenumber",
|
|
92
|
+
"mobile",
|
|
93
|
+
"tel",
|
|
94
|
+
"address",
|
|
95
|
+
"homeaddress",
|
|
96
|
+
"zipcode",
|
|
97
|
+
"postal",
|
|
98
|
+
"birthdate",
|
|
99
|
+
"birthday",
|
|
100
|
+
"dob",
|
|
101
|
+
"license",
|
|
102
|
+
"passport",
|
|
103
|
+
"identity",
|
|
104
|
+
"salary",
|
|
105
|
+
"income",
|
|
106
|
+
"wage",
|
|
107
|
+
"medical",
|
|
108
|
+
"health",
|
|
109
|
+
"diagnosis"
|
|
110
|
+
],
|
|
111
|
+
"httpClientPatterns": [
|
|
112
|
+
"fetch",
|
|
113
|
+
"axios.get",
|
|
114
|
+
"axios.post",
|
|
115
|
+
"axios.put",
|
|
116
|
+
"axios.delete",
|
|
117
|
+
"axios.patch",
|
|
118
|
+
"axios.request",
|
|
119
|
+
"request.get",
|
|
120
|
+
"request.post",
|
|
121
|
+
"http.get",
|
|
122
|
+
"http.request",
|
|
123
|
+
"https.get",
|
|
124
|
+
"https.request"
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S016 Regex-based Analyzer - Sensitive Data in URL Query Parameters Detection
|
|
3
|
+
* Purpose: Fallback pattern matching when symbol analysis fails
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class S016RegexBasedAnalyzer {
|
|
7
|
+
constructor(semanticEngine = null) {
|
|
8
|
+
this.ruleId = 'S016';
|
|
9
|
+
this.ruleName = 'Sensitive Data in URL Query Parameters (Regex-Based)';
|
|
10
|
+
this.semanticEngine = semanticEngine;
|
|
11
|
+
this.verbose = false;
|
|
12
|
+
|
|
13
|
+
// URL construction patterns (regex)
|
|
14
|
+
this.urlConstructionPatterns = [
|
|
15
|
+
/new\s+URL\s*\([^)]*\)/gi,
|
|
16
|
+
/new\s+URLSearchParams\s*\([^)]*\)/gi,
|
|
17
|
+
/window\.location\.href\s*=\s*[^;]+/gi,
|
|
18
|
+
/location\.href\s*=\s*[^;]+/gi,
|
|
19
|
+
/location\.search\s*[+]?=\s*[^;]+/gi
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// HTTP client patterns
|
|
23
|
+
this.httpClientPatterns = [
|
|
24
|
+
/fetch\s*\(\s*[`"'][^`"']*[?][^`"']*[`"']/gi,
|
|
25
|
+
/axios\.(get|post|put|delete|patch|request)\s*\([^)]*\)/gi,
|
|
26
|
+
/request\.(get|post)\s*\([^)]*\)/gi,
|
|
27
|
+
/https?\.(?:get|request)\s*\([^)]*\)/gi
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Query string manipulation patterns
|
|
31
|
+
this.queryStringPatterns = [
|
|
32
|
+
/querystring\.stringify\s*\([^)]*\)/gi,
|
|
33
|
+
/qs\.stringify\s*\([^)]*\)/gi,
|
|
34
|
+
/URLSearchParams\s*\([^)]*\)/gi,
|
|
35
|
+
/\.search\s*[+]?=\s*[^;]+/gi,
|
|
36
|
+
/[?&]\w+=[^&\s]+/g
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Sensitive data patterns (same as symbol-based)
|
|
40
|
+
this.sensitivePatterns = [
|
|
41
|
+
// Authentication & Authorization
|
|
42
|
+
/\b(?:password|passwd|pwd|pass)\b/gi,
|
|
43
|
+
/\b(?:token|jwt|accesstoken|refreshtoken|bearertoken)\b/gi,
|
|
44
|
+
/\b(?:secret|secretkey|clientsecret|serversecret)\b/gi,
|
|
45
|
+
/\b(?:apikey|api_key|key|privatekey|publickey)\b/gi,
|
|
46
|
+
/\b(?:auth|authorization|authenticate)\b/gi,
|
|
47
|
+
/\b(?:sessionid|session_id|jsessionid)\b/gi,
|
|
48
|
+
/\b(?:csrf|csrftoken|xsrf)\b/gi,
|
|
49
|
+
|
|
50
|
+
// Financial & Personal
|
|
51
|
+
/\b(?:ssn|social|socialsecurity)\b/gi,
|
|
52
|
+
/\b(?:creditcard|cardnumber|cardnum|ccnumber)\b/gi,
|
|
53
|
+
/\b(?:cvv|cvc|cvd|cid)\b/gi,
|
|
54
|
+
/\b(?:pin|pincode)\b/gi,
|
|
55
|
+
/\b(?:bankaccount|routing|iban)\b/gi,
|
|
56
|
+
|
|
57
|
+
// Personal Identifiable Information
|
|
58
|
+
/\b(?:email|emailaddress|mail)\b/gi,
|
|
59
|
+
/\b(?:phone|phonenumber|mobile|tel)\b/gi,
|
|
60
|
+
/\b(?:address|homeaddress|zipcode|postal)\b/gi,
|
|
61
|
+
/\b(?:birthdate|birthday|dob)\b/gi,
|
|
62
|
+
/\b(?:license|passport|identity)\b/gi
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// Combined patterns for efficiency
|
|
66
|
+
this.allUrlPatterns = [
|
|
67
|
+
...this.urlConstructionPatterns,
|
|
68
|
+
...this.httpClientPatterns,
|
|
69
|
+
...this.queryStringPatterns
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async initialize(semanticEngine = null) {
|
|
74
|
+
if (semanticEngine) {
|
|
75
|
+
this.semanticEngine = semanticEngine;
|
|
76
|
+
}
|
|
77
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
78
|
+
|
|
79
|
+
if (this.verbose) {
|
|
80
|
+
console.log(`🔧 [S016 Regex-Based] Analyzer initialized`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async analyzeFileBasic(filePath, options = {}) {
|
|
85
|
+
const fs = require('fs');
|
|
86
|
+
const violations = [];
|
|
87
|
+
|
|
88
|
+
if (this.verbose) {
|
|
89
|
+
console.log(`🔧 [S016 Regex] Starting analysis for: ${filePath}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
94
|
+
|
|
95
|
+
if (this.verbose) {
|
|
96
|
+
console.log(`🔧 [S016 Regex] File content length: ${content.length}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const lines = content.split('\n');
|
|
100
|
+
|
|
101
|
+
// Find all URL/query related patterns
|
|
102
|
+
const urlMatches = this.findUrlPatterns(content);
|
|
103
|
+
|
|
104
|
+
if (this.verbose) {
|
|
105
|
+
console.log(`🔧 [S016 Regex] Found ${urlMatches.length} URL patterns`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const match of urlMatches) {
|
|
109
|
+
const matchViolations = this.analyzeUrlMatch(match, lines, filePath);
|
|
110
|
+
if (this.verbose && matchViolations.length > 0) {
|
|
111
|
+
console.log(`🔧 [S016 Regex] Match violations: ${matchViolations.length}`);
|
|
112
|
+
}
|
|
113
|
+
violations.push(...matchViolations);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (this.verbose) {
|
|
117
|
+
console.log(`🔧 [S016 Regex] Total violations found: ${violations.length}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return violations;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (this.verbose) {
|
|
123
|
+
console.error(`🔧 [S016 Regex] Error analyzing ${filePath}:`, error);
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Find URL patterns in content using regex
|
|
131
|
+
*/
|
|
132
|
+
findUrlPatterns(content) {
|
|
133
|
+
const matches = [];
|
|
134
|
+
|
|
135
|
+
for (const pattern of this.allUrlPatterns) {
|
|
136
|
+
pattern.lastIndex = 0; // Reset regex
|
|
137
|
+
let match;
|
|
138
|
+
|
|
139
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
140
|
+
const fullMatch = match[0];
|
|
141
|
+
|
|
142
|
+
// Calculate line number
|
|
143
|
+
const beforeMatch = content.substring(0, match.index);
|
|
144
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
145
|
+
const lineStart = beforeMatch.lastIndexOf('\n') + 1;
|
|
146
|
+
const columnNumber = match.index - lineStart + 1;
|
|
147
|
+
|
|
148
|
+
matches.push({
|
|
149
|
+
fullMatch,
|
|
150
|
+
lineNumber,
|
|
151
|
+
columnNumber,
|
|
152
|
+
startIndex: match.index,
|
|
153
|
+
pattern: pattern.source
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return matches;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Analyze URL match for sensitive data violations
|
|
163
|
+
*/
|
|
164
|
+
analyzeUrlMatch(match, lines, filePath) {
|
|
165
|
+
const violations = [];
|
|
166
|
+
const { fullMatch, lineNumber, columnNumber, pattern } = match;
|
|
167
|
+
|
|
168
|
+
// Check for sensitive data in the matched content
|
|
169
|
+
const sensitiveData = this.findSensitiveDataInMatch(fullMatch);
|
|
170
|
+
|
|
171
|
+
if (sensitiveData.length > 0) {
|
|
172
|
+
const patternType = this.identifyPatternType(pattern);
|
|
173
|
+
|
|
174
|
+
violations.push({
|
|
175
|
+
ruleId: this.ruleId,
|
|
176
|
+
severity: 'error',
|
|
177
|
+
message: 'Sensitive data detected in URL query parameters',
|
|
178
|
+
source: this.ruleId,
|
|
179
|
+
file: filePath,
|
|
180
|
+
line: lineNumber,
|
|
181
|
+
column: columnNumber,
|
|
182
|
+
description: `[REGEX-FALLBACK] Sensitive patterns detected: ${sensitiveData.join(', ')}. URLs with sensitive data can be exposed in logs, browser history, and network traces.`,
|
|
183
|
+
suggestion: 'Move sensitive data to request body (POST/PUT) or use secure headers. For authentication, use proper Authorization header.',
|
|
184
|
+
category: 'security',
|
|
185
|
+
patternType: patternType,
|
|
186
|
+
matchedText: fullMatch.length > 100 ? fullMatch.substring(0, 100) + '...' : fullMatch
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return violations;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Find sensitive data patterns in matched text
|
|
195
|
+
*/
|
|
196
|
+
findSensitiveDataInMatch(matchText) {
|
|
197
|
+
const sensitiveData = [];
|
|
198
|
+
|
|
199
|
+
for (const pattern of this.sensitivePatterns) {
|
|
200
|
+
pattern.lastIndex = 0; // Reset regex
|
|
201
|
+
const matches = matchText.match(pattern);
|
|
202
|
+
if (matches) {
|
|
203
|
+
sensitiveData.push(...matches.map(m => m.toLowerCase()));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return [...new Set(sensitiveData)]; // Remove duplicates
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Identify what type of pattern was matched
|
|
212
|
+
*/
|
|
213
|
+
identifyPatternType(patternSource) {
|
|
214
|
+
if (patternSource.includes('URL') || patternSource.includes('location')) {
|
|
215
|
+
return 'url_construction';
|
|
216
|
+
} else if (patternSource.includes('fetch') || patternSource.includes('axios') || patternSource.includes('request')) {
|
|
217
|
+
return 'http_client';
|
|
218
|
+
} else if (patternSource.includes('stringify') || patternSource.includes('search')) {
|
|
219
|
+
return 'query_string';
|
|
220
|
+
}
|
|
221
|
+
return 'unknown';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async analyze(files, language, options = {}) {
|
|
225
|
+
if (this.verbose) {
|
|
226
|
+
console.log(`🔧 [S016 Regex] analyze() called with ${files.length} files, language: ${language}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const violations = [];
|
|
230
|
+
|
|
231
|
+
for (const filePath of files) {
|
|
232
|
+
try {
|
|
233
|
+
if (this.verbose) {
|
|
234
|
+
console.log(`🔧 [S016 Regex] Processing file: ${filePath}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const fileViolations = await this.analyzeFileBasic(filePath, options);
|
|
238
|
+
violations.push(...fileViolations);
|
|
239
|
+
|
|
240
|
+
if (this.verbose) {
|
|
241
|
+
console.log(`🔧 [S016 Regex] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (this.verbose) {
|
|
245
|
+
console.warn(`⚠ [S016 Regex] Analysis failed for ${filePath}:`, error.message);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (this.verbose) {
|
|
251
|
+
console.log(`🔧 [S016 Regex] Total violations found: ${violations.length}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return violations;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
module.exports = S016RegexBasedAnalyzer;
|