@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.
Files changed (113) hide show
  1. package/cli.js +34 -0
  2. package/config/rules/enhanced-rules-registry.json +387 -98
  3. package/config/rules/rules-registry-generated.json +202 -174
  4. package/config/rules-summary.json +1 -1
  5. package/core/architecture-integration.js +115 -17
  6. package/core/cli-action-handler.js +103 -28
  7. package/core/cli-program.js +7 -2
  8. package/core/github-annotate-service.js +62 -0
  9. package/core/impact-integration.js +31 -16
  10. package/core/init-command.js +261 -0
  11. package/core/output-service.js +64 -10
  12. package/core/performance-optimizer.js +1 -1
  13. package/core/summary-report-service.js +46 -0
  14. package/core/unified-rule-registry.js +4 -3
  15. package/docs/DART_RULE_EXECUTION_FLOW.md +1 -1
  16. package/docs/REGISTRY_GENERATION_DIAGRAM.md +289 -0
  17. package/docs/REGISTRY_GENERATION_FLOW.md +486 -0
  18. package/docs/skills/CREATE_NEW_DART_RULE.md +932 -0
  19. package/engines/eslint-engine.js +6 -0
  20. package/engines/heuristic-engine.js +23 -10
  21. package/engines/impact/core/detectors/database-detector.js +1 -1
  22. package/engines/impact/core/detectors/endpoint-detector.js +1 -1
  23. package/engines/impact/core/report-generator.js +235 -73
  24. package/origin-rules/dart-en.md +4 -4
  25. package/origin-rules/security-en.md +470 -282
  26. package/package.json +1 -1
  27. package/rules/dart/D001_recommended_lint_rules/config.json +134 -0
  28. package/rules/index.js +6 -4
  29. package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
  30. package/rules/security/S001_backend_auth_communications/index.js +87 -0
  31. package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
  32. package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
  33. package/rules/security/S002_os_command_injection/index.js +87 -0
  34. package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
  35. package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
  36. package/rules/security/S008_svg_content_validation/index.js +87 -0
  37. package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
  38. package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
  39. package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
  40. package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
  41. package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
  42. package/rules/security/S021_referrer_policy/index.js +86 -0
  43. package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
  44. package/rules/security/S023_no_json_injection/config.json +133 -44
  45. package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
  46. package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
  47. package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
  48. package/rules/security/S026_tls_all_connections/config.json +30 -0
  49. package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
  50. package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
  51. package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
  52. package/rules/security/S035_separate_app_hostnames/config.json +28 -0
  53. package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
  54. package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
  55. package/rules/security/S039_tls_certificate_validation/config.json +29 -0
  56. package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
  57. package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
  58. package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
  59. package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
  60. package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
  61. package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
  62. package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
  63. package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
  64. package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
  65. package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
  66. package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
  67. package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
  68. package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
  69. package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
  70. package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
  71. package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
  72. package/rules/security/S053_generic_error_messages/config.json +28 -0
  73. package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
  74. package/rules/security/S053_generic_error_messages/index.js +86 -0
  75. package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
  76. package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
  77. package/rules/security/S059_disable_debug_mode/config.json +28 -0
  78. package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
  79. package/rules/security/S059_disable_debug_mode/index.js +86 -0
  80. package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
  81. package/rules/security/S060_password_minimum_length/config.json +28 -0
  82. package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
  83. package/rules/security/S060_password_minimum_length/index.js +86 -0
  84. package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
  85. package/rules/security/S026_json_schema_validation/config.json +0 -27
  86. package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
  87. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  88. package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
  89. package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
  90. package/rules/security/S035_path_session_cookies/config.json +0 -99
  91. package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
  92. package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
  93. package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
  94. package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
  95. package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
  96. package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
  97. package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
  98. package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
  99. package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
  100. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
  101. /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
  102. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
  103. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
  104. /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
  105. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
  106. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
  107. /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
  108. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
  109. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
  110. /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
  111. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
  112. /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
  113. /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
- }