@sun-asterisk/sunlint 1.3.36 → 1.3.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +34 -0
- package/config/rules/enhanced-rules-registry.json +387 -98
- package/config/rules/rules-registry-generated.json +202 -174
- package/config/rules-summary.json +1 -1
- package/core/architecture-integration.js +115 -17
- package/core/cli-action-handler.js +103 -28
- package/core/cli-program.js +7 -2
- package/core/github-annotate-service.js +62 -0
- package/core/impact-integration.js +31 -16
- package/core/init-command.js +261 -0
- package/core/output-service.js +64 -10
- package/core/performance-optimizer.js +1 -1
- package/core/summary-report-service.js +46 -0
- package/core/unified-rule-registry.js +4 -3
- package/docs/DART_RULE_EXECUTION_FLOW.md +1 -1
- package/docs/REGISTRY_GENERATION_DIAGRAM.md +289 -0
- package/docs/REGISTRY_GENERATION_FLOW.md +486 -0
- package/docs/skills/CREATE_NEW_DART_RULE.md +932 -0
- package/engines/eslint-engine.js +6 -0
- package/engines/heuristic-engine.js +23 -10
- package/engines/impact/core/detectors/database-detector.js +1 -1
- package/engines/impact/core/detectors/endpoint-detector.js +1 -1
- package/engines/impact/core/report-generator.js +235 -73
- package/origin-rules/dart-en.md +4 -4
- package/origin-rules/security-en.md +470 -282
- package/package.json +1 -1
- package/rules/dart/D001_recommended_lint_rules/config.json +134 -0
- package/rules/index.js +6 -4
- package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
- package/rules/security/S001_backend_auth_communications/index.js +87 -0
- package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
- package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
- package/rules/security/S002_os_command_injection/index.js +87 -0
- package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
- package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
- package/rules/security/S008_svg_content_validation/index.js +87 -0
- package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
- package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
- package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
- package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
- package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
- package/rules/security/S021_referrer_policy/index.js +86 -0
- package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
- package/rules/security/S023_no_json_injection/config.json +133 -44
- package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
- package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
- package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
- package/rules/security/S026_tls_all_connections/config.json +30 -0
- package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
- package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
- package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
- package/rules/security/S035_separate_app_hostnames/config.json +28 -0
- package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
- package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
- package/rules/security/S039_tls_certificate_validation/config.json +29 -0
- package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
- package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
- package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
- package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
- package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
- package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
- package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
- package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
- package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
- package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
- package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
- package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
- package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
- package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
- package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
- package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
- package/rules/security/S053_generic_error_messages/config.json +28 -0
- package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
- package/rules/security/S053_generic_error_messages/index.js +86 -0
- package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
- package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
- package/rules/security/S059_disable_debug_mode/config.json +28 -0
- package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
- package/rules/security/S059_disable_debug_mode/index.js +86 -0
- package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
- package/rules/security/S060_password_minimum_length/config.json +28 -0
- package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
- package/rules/security/S060_password_minimum_length/index.js +86 -0
- package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
- package/rules/security/S026_json_schema_validation/config.json +0 -27
- package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
- package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
- package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
- package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
- package/rules/security/S035_path_session_cookies/config.json +0 -99
- package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
- package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
- package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
- package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
- package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
- package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
- package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
- package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
- package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
- /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
- /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/typescript/README.md +0 -0
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S039 Symbol-Based Analyzer - Do not pass Session Tokens via URL parameters
|
|
3
|
-
* Enhanced to analyze per route handler for URL parameter token exposure
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
class S039SymbolBasedAnalyzer {
|
|
7
|
-
constructor(semanticEngine) {
|
|
8
|
-
this.ruleId = "S039";
|
|
9
|
-
this.semanticEngine = semanticEngine;
|
|
10
|
-
|
|
11
|
-
// Session token parameter names to detect
|
|
12
|
-
this.sessionTokenParams = [
|
|
13
|
-
"sessionId",
|
|
14
|
-
"session_id",
|
|
15
|
-
"session-id",
|
|
16
|
-
"sessionToken",
|
|
17
|
-
"session_token",
|
|
18
|
-
"session-token",
|
|
19
|
-
"authToken",
|
|
20
|
-
"auth_token",
|
|
21
|
-
"auth-token",
|
|
22
|
-
"authorization",
|
|
23
|
-
"bearer",
|
|
24
|
-
"jwt",
|
|
25
|
-
"jwtToken",
|
|
26
|
-
"jwt_token",
|
|
27
|
-
"jwt-token",
|
|
28
|
-
"accessToken",
|
|
29
|
-
"access_token",
|
|
30
|
-
"access-token",
|
|
31
|
-
"refreshToken",
|
|
32
|
-
"refresh_token",
|
|
33
|
-
"refresh-token",
|
|
34
|
-
"apiKey",
|
|
35
|
-
"api_key",
|
|
36
|
-
"api-key",
|
|
37
|
-
"csrfToken",
|
|
38
|
-
"csrf_token",
|
|
39
|
-
"csrf-token",
|
|
40
|
-
"xsrfToken",
|
|
41
|
-
"xsrf_token",
|
|
42
|
-
"xsrf-token",
|
|
43
|
-
"token",
|
|
44
|
-
"apiToken",
|
|
45
|
-
"api_token",
|
|
46
|
-
"api-token",
|
|
47
|
-
"sid",
|
|
48
|
-
"sessionkey",
|
|
49
|
-
"session_key",
|
|
50
|
-
"session-key",
|
|
51
|
-
"userToken",
|
|
52
|
-
"user_token",
|
|
53
|
-
"user-token",
|
|
54
|
-
"authKey",
|
|
55
|
-
"auth_key",
|
|
56
|
-
"auth-key",
|
|
57
|
-
"securityToken",
|
|
58
|
-
"security_token",
|
|
59
|
-
"security-token",
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
// Pattern to detect session token-like values
|
|
63
|
-
this.tokenValuePattern = /^[a-zA-Z0-9+/=\-_.]{16,}$/;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async initialize() {}
|
|
67
|
-
|
|
68
|
-
analyze(sourceFile, filePath) {
|
|
69
|
-
const violations = [];
|
|
70
|
-
|
|
71
|
-
// Skip files that are unlikely to be route handlers
|
|
72
|
-
const skipPatterns = [
|
|
73
|
-
/\.dto\.ts$/,
|
|
74
|
-
/\.interface\.ts$/,
|
|
75
|
-
/\.module\.ts$/,
|
|
76
|
-
/\.service\.spec\.ts$/,
|
|
77
|
-
/\.controller\.spec\.ts$/,
|
|
78
|
-
/\.spec\.ts$/,
|
|
79
|
-
/\.test\.ts$/,
|
|
80
|
-
/\.d\.ts$/,
|
|
81
|
-
/\.types\.ts$/,
|
|
82
|
-
/\.constants?.ts$/,
|
|
83
|
-
/\.config\.ts$/,
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const shouldSkip = skipPatterns.some((pattern) => pattern.test(filePath));
|
|
87
|
-
if (shouldSkip) {
|
|
88
|
-
return violations;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const { SyntaxKind } = require("ts-morph");
|
|
93
|
-
|
|
94
|
-
// Find all function expressions and arrow functions that could be route handlers
|
|
95
|
-
const routeHandlers = [];
|
|
96
|
-
|
|
97
|
-
// Express route patterns: app.get("/path", (req, res) => {...})
|
|
98
|
-
const callExpressions = sourceFile.getDescendantsOfKind(
|
|
99
|
-
SyntaxKind.CallExpression
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
for (const call of callExpressions) {
|
|
103
|
-
const expression = call.getExpression();
|
|
104
|
-
if (expression && expression.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
105
|
-
const nameNode = expression.getNameNode();
|
|
106
|
-
if (nameNode) {
|
|
107
|
-
const methodName = nameNode.getText();
|
|
108
|
-
if (/^(get|post|put|delete|patch|all|use)$/.test(methodName)) {
|
|
109
|
-
const args = call.getArguments();
|
|
110
|
-
const lastArg = args[args.length - 1];
|
|
111
|
-
// The last argument should be the handler function
|
|
112
|
-
if (
|
|
113
|
-
lastArg && (
|
|
114
|
-
lastArg.getKind() === SyntaxKind.ArrowFunction ||
|
|
115
|
-
lastArg.getKind() === SyntaxKind.FunctionExpression
|
|
116
|
-
)
|
|
117
|
-
) {
|
|
118
|
-
routeHandlers.push({
|
|
119
|
-
handler: lastArg,
|
|
120
|
-
routeCall: call,
|
|
121
|
-
type: "express",
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Next.js export functions
|
|
130
|
-
const exportAssignments = sourceFile.getDescendantsOfKind(
|
|
131
|
-
SyntaxKind.ExportAssignment
|
|
132
|
-
);
|
|
133
|
-
const exportDeclarations = sourceFile.getDescendantsOfKind(
|
|
134
|
-
SyntaxKind.ExportDeclaration
|
|
135
|
-
);
|
|
136
|
-
const functionDeclarations = sourceFile.getDescendantsOfKind(
|
|
137
|
-
SyntaxKind.FunctionDeclaration
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
for (const func of functionDeclarations) {
|
|
141
|
-
const name = func.getName();
|
|
142
|
-
if (name && /^(GET|POST|PUT|DELETE|PATCH|handler)$/.test(name)) {
|
|
143
|
-
routeHandlers.push({
|
|
144
|
-
handler: func,
|
|
145
|
-
type: "nextjs",
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// NestJS Controller methods with decorators
|
|
151
|
-
const methodDeclarations = sourceFile.getDescendantsOfKind(
|
|
152
|
-
SyntaxKind.MethodDeclaration
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
for (const method of methodDeclarations) {
|
|
156
|
-
const decorators = method.getDecorators();
|
|
157
|
-
const hasRouteDecorator = decorators.some((decorator) => {
|
|
158
|
-
const decoratorName = decorator.getName();
|
|
159
|
-
return /^(Get|Post|Put|Delete|Patch|All)$/.test(decoratorName);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (hasRouteDecorator) {
|
|
163
|
-
routeHandlers.push({
|
|
164
|
-
handler: method,
|
|
165
|
-
type: "nestjs",
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Nuxt.js defineEventHandler patterns
|
|
171
|
-
for (const call of callExpressions) {
|
|
172
|
-
const expression = call.getExpression();
|
|
173
|
-
if (expression && expression.getKind() === SyntaxKind.Identifier) {
|
|
174
|
-
const identifier = expression.asKindOrThrow(SyntaxKind.Identifier);
|
|
175
|
-
if (identifier.getText() === "defineEventHandler") {
|
|
176
|
-
// Find the arrow function or function parameter
|
|
177
|
-
const args = call.getArguments();
|
|
178
|
-
if (args.length > 0) {
|
|
179
|
-
const firstArg = args[0];
|
|
180
|
-
if (
|
|
181
|
-
firstArg && (
|
|
182
|
-
firstArg.getKind() === SyntaxKind.ArrowFunction ||
|
|
183
|
-
firstArg.getKind() === SyntaxKind.FunctionExpression
|
|
184
|
-
)
|
|
185
|
-
) {
|
|
186
|
-
routeHandlers.push({
|
|
187
|
-
handler: firstArg,
|
|
188
|
-
type: "nuxtjs",
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Analyze each route handler for session token exposure in URL parameters
|
|
197
|
-
for (const { handler, routeCall, type } of routeHandlers) {
|
|
198
|
-
try {
|
|
199
|
-
const handlerViolations = this.analyzeRouteHandler(
|
|
200
|
-
handler,
|
|
201
|
-
routeCall,
|
|
202
|
-
type,
|
|
203
|
-
filePath
|
|
204
|
-
);
|
|
205
|
-
violations.push(...handlerViolations);
|
|
206
|
-
} catch (error) {
|
|
207
|
-
console.warn(
|
|
208
|
-
`⚠ [S039] Handler analysis failed for ${type}:`,
|
|
209
|
-
error.message
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} catch (error) {
|
|
214
|
-
console.warn(
|
|
215
|
-
`⚠ [S039] Symbol analysis failed for ${filePath}:`,
|
|
216
|
-
error.message
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return violations;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
analyzeRouteHandler(handler, routeCall, type, filePath) {
|
|
224
|
-
const violations = [];
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
const { SyntaxKind } = require("ts-morph");
|
|
228
|
-
|
|
229
|
-
// Find URL parameter access patterns within this handler
|
|
230
|
-
const tokenExposures = this.findTokenParametersInNode(handler);
|
|
231
|
-
|
|
232
|
-
// Report violations for exposed session tokens in URL parameters
|
|
233
|
-
for (const exposure of tokenExposures) {
|
|
234
|
-
const startLine = exposure.node.getStartLineNumber();
|
|
235
|
-
violations.push({
|
|
236
|
-
ruleId: this.ruleId,
|
|
237
|
-
message: `Session token '${exposure.paramName}' passed via URL parameter - use secure headers or request body instead`,
|
|
238
|
-
severity: "warning",
|
|
239
|
-
line: startLine,
|
|
240
|
-
column: 1,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.warn(`⚠ [S039] Route handler analysis failed:`, error.message);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return violations;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
findTokenParametersInNode(node) {
|
|
251
|
-
const exposures = [];
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
const { SyntaxKind } = require("ts-morph");
|
|
255
|
-
|
|
256
|
-
// For NestJS, check decorator parameters first
|
|
257
|
-
if (node && node.getKind() === SyntaxKind.MethodDeclaration) {
|
|
258
|
-
const parameters = node.getParameters();
|
|
259
|
-
for (const param of parameters) {
|
|
260
|
-
const decorators = param.getDecorators();
|
|
261
|
-
for (const decorator of decorators) {
|
|
262
|
-
const decoratorName = decorator.getName();
|
|
263
|
-
if (decoratorName === "Query" || decoratorName === "Param") {
|
|
264
|
-
const args = decorator.getArguments();
|
|
265
|
-
if (args.length > 0) {
|
|
266
|
-
const firstArg = args[0];
|
|
267
|
-
if (firstArg && firstArg.getKind() === SyntaxKind.StringLiteral) {
|
|
268
|
-
const paramName = firstArg.getLiteralValue();
|
|
269
|
-
if (this.isSessionTokenParam(paramName)) {
|
|
270
|
-
exposures.push({
|
|
271
|
-
node: param,
|
|
272
|
-
paramName: paramName,
|
|
273
|
-
accessType: decoratorName.toLowerCase(),
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Find all property access expressions for URL parameters
|
|
284
|
-
const propertyAccesses = node.getDescendantsOfKind(
|
|
285
|
-
SyntaxKind.PropertyAccessExpression
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
for (const propAccess of propertyAccesses) {
|
|
289
|
-
const expression = propAccess.getExpression();
|
|
290
|
-
const property = propAccess.getName();
|
|
291
|
-
|
|
292
|
-
// Check for req.query.paramName patterns
|
|
293
|
-
if (expression && expression.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
294
|
-
const parentExpression = expression.getExpression();
|
|
295
|
-
const parentProperty = expression.getName();
|
|
296
|
-
|
|
297
|
-
// req.query.sessionToken, req.params.authToken, etc.
|
|
298
|
-
if (
|
|
299
|
-
parentProperty === "query" ||
|
|
300
|
-
parentProperty === "params" ||
|
|
301
|
-
parentProperty === "searchParams"
|
|
302
|
-
) {
|
|
303
|
-
if (this.isSessionTokenParam(property)) {
|
|
304
|
-
exposures.push({
|
|
305
|
-
node: propAccess,
|
|
306
|
-
paramName: property,
|
|
307
|
-
accessType: parentProperty,
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Check for bracket notation access: req.query["access-token"]
|
|
315
|
-
const elementAccessExpressions = node.getDescendantsOfKind(
|
|
316
|
-
SyntaxKind.ElementAccessExpression
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
for (const elemAccess of elementAccessExpressions) {
|
|
320
|
-
const expression = elemAccess.getExpression();
|
|
321
|
-
const argumentExpression = elemAccess.getArgumentExpression();
|
|
322
|
-
|
|
323
|
-
if (
|
|
324
|
-
expression && expression.getKind() === SyntaxKind.PropertyAccessExpression &&
|
|
325
|
-
argumentExpression &&
|
|
326
|
-
argumentExpression.getKind() === SyntaxKind.StringLiteral
|
|
327
|
-
) {
|
|
328
|
-
const parentProperty = expression.getName();
|
|
329
|
-
const paramName = argumentExpression.getLiteralValue();
|
|
330
|
-
|
|
331
|
-
// req.query["sessionToken"], req.params["authToken"], etc.
|
|
332
|
-
if (
|
|
333
|
-
(parentProperty === "query" ||
|
|
334
|
-
parentProperty === "params" ||
|
|
335
|
-
parentProperty === "searchParams") &&
|
|
336
|
-
this.isSessionTokenParam(paramName)
|
|
337
|
-
) {
|
|
338
|
-
exposures.push({
|
|
339
|
-
node: elemAccess,
|
|
340
|
-
paramName: paramName,
|
|
341
|
-
accessType: parentProperty,
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Check for URL.searchParams.get() patterns
|
|
348
|
-
const callExpressions = node.getDescendantsOfKind(
|
|
349
|
-
SyntaxKind.CallExpression
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
for (const call of callExpressions) {
|
|
353
|
-
const callExpression = call.getExpression();
|
|
354
|
-
if (callExpression && callExpression.getKind() === SyntaxKind.PropertyAccessExpression) {
|
|
355
|
-
const methodName = callExpression.getName();
|
|
356
|
-
const objectExpression = callExpression.getExpression();
|
|
357
|
-
|
|
358
|
-
// searchParams.get("sessionToken"), URLSearchParams.get("token")
|
|
359
|
-
if (
|
|
360
|
-
methodName === "get" &&
|
|
361
|
-
objectExpression && objectExpression.getText().includes("searchParams")
|
|
362
|
-
) {
|
|
363
|
-
const args = call.getArguments();
|
|
364
|
-
if (args.length > 0) {
|
|
365
|
-
const firstArg = args[0];
|
|
366
|
-
if (firstArg && firstArg.getKind() === SyntaxKind.StringLiteral) {
|
|
367
|
-
const paramName = firstArg.getLiteralValue();
|
|
368
|
-
if (this.isSessionTokenParam(paramName)) {
|
|
369
|
-
exposures.push({
|
|
370
|
-
node: call,
|
|
371
|
-
paramName: paramName,
|
|
372
|
-
accessType: "searchParams",
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Check for object destructuring patterns
|
|
382
|
-
const variableDeclarations = node.getDescendantsOfKind(
|
|
383
|
-
SyntaxKind.VariableDeclaration
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
for (const varDecl of variableDeclarations) {
|
|
387
|
-
const nameNode = varDecl.getNameNode();
|
|
388
|
-
if (nameNode && nameNode.getKind() === SyntaxKind.ObjectBindingPattern) {
|
|
389
|
-
const bindingPattern = nameNode.asKindOrThrow(
|
|
390
|
-
SyntaxKind.ObjectBindingPattern
|
|
391
|
-
);
|
|
392
|
-
const elements = bindingPattern.getElements();
|
|
393
|
-
|
|
394
|
-
const initializer = varDecl.getInitializer();
|
|
395
|
-
if (
|
|
396
|
-
initializer &&
|
|
397
|
-
(initializer.getText().includes("req.query") ||
|
|
398
|
-
initializer.getText().includes("req.params") ||
|
|
399
|
-
initializer.getText().includes("searchParams"))
|
|
400
|
-
) {
|
|
401
|
-
for (const element of elements) {
|
|
402
|
-
let paramName = null;
|
|
403
|
-
|
|
404
|
-
// Handle both { paramName } and { "param-name": alias } patterns
|
|
405
|
-
const propNameNode = element.getPropertyNameNode();
|
|
406
|
-
const nameNode = element.getNameNode();
|
|
407
|
-
|
|
408
|
-
if (propNameNode) {
|
|
409
|
-
// { "param-name": alias } or { paramName: alias }
|
|
410
|
-
paramName = propNameNode.getText().replace(/['"]/g, "");
|
|
411
|
-
} else if (nameNode) {
|
|
412
|
-
// { paramName } shorthand
|
|
413
|
-
paramName = nameNode.getText();
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (this.isSessionTokenParam(paramName)) {
|
|
417
|
-
exposures.push({
|
|
418
|
-
node: element,
|
|
419
|
-
paramName: paramName,
|
|
420
|
-
accessType: "destructuring",
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
} catch (error) {
|
|
428
|
-
console.warn(`⚠ [S039] Parameter analysis failed:`, error.message);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
return exposures;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
isSessionTokenParam(paramName) {
|
|
435
|
-
return this.sessionTokenParams.some(
|
|
436
|
-
(tokenParam) => tokenParam.toLowerCase() === paramName.toLowerCase()
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
cleanup() {}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
module.exports = S039SymbolBasedAnalyzer;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"ruleId": "S048",
|
|
3
|
-
"name": "No Current Password in Reset Process",
|
|
4
|
-
"description": "Do not require current password during password reset process",
|
|
5
|
-
"category": "security",
|
|
6
|
-
"severity": "error",
|
|
7
|
-
"languages": ["All languages"],
|
|
8
|
-
"tags": ["security", "owasp", "insecure-design", "authentication", "password-reset"],
|
|
9
|
-
"enabled": true,
|
|
10
|
-
"fixable": false,
|
|
11
|
-
"engine": "heuristic",
|
|
12
|
-
"metadata": {
|
|
13
|
-
"owaspCategory": "A04:2021 - Insecure Design",
|
|
14
|
-
"cweId": "CWE-640",
|
|
15
|
-
"description": "Requiring the current password during password reset defeats the purpose of the reset process and creates security vulnerabilities. Users who have forgotten their password cannot complete the reset, and this practice can lead to account lockouts and security issues.",
|
|
16
|
-
"impact": "Medium - Account lockout, user frustration, security bypass attempts",
|
|
17
|
-
"likelihood": "High",
|
|
18
|
-
"remediation": "Use secure token-based password reset with email/SMS verification. Never require current password during reset process."
|
|
19
|
-
},
|
|
20
|
-
"patterns": {
|
|
21
|
-
"vulnerable": [
|
|
22
|
-
"Requiring current password in forgot password form",
|
|
23
|
-
"Validating old password during reset process",
|
|
24
|
-
"API endpoints that check current password for reset",
|
|
25
|
-
"Reset forms with current password fields"
|
|
26
|
-
],
|
|
27
|
-
"secure": [
|
|
28
|
-
"Token-based password reset via email",
|
|
29
|
-
"SMS verification for password reset",
|
|
30
|
-
"Time-limited secure reset links",
|
|
31
|
-
"Multi-factor authentication for reset verification"
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
"examples": {
|
|
35
|
-
"violations": [
|
|
36
|
-
"if (!validateCurrentPassword(currentPassword)) { return error; }",
|
|
37
|
-
"const resetData = { currentPassword, newPassword };",
|
|
38
|
-
"currentPassword: { type: String, required: true }",
|
|
39
|
-
"req.body.currentPassword === user.password"
|
|
40
|
-
],
|
|
41
|
-
"fixes": [
|
|
42
|
-
"if (!validateResetToken(token)) { return error; }",
|
|
43
|
-
"const resetData = { token, newPassword };",
|
|
44
|
-
"resetToken: { type: String, required: true }",
|
|
45
|
-
"validateResetToken(req.body.token)"
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
}
|