@sun-asterisk/sunlint 1.3.18 → 1.3.20

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 (35) hide show
  1. package/config/rules/enhanced-rules-registry.json +77 -18
  2. package/core/cli-program.js +9 -1
  3. package/core/github-annotate-service.js +986 -0
  4. package/core/output-service.js +294 -6
  5. package/core/summary-report-service.js +30 -30
  6. package/docs/GITHUB_ACTIONS_INTEGRATION.md +421 -0
  7. package/package.json +2 -1
  8. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
  9. package/rules/common/C017_constructor_logic/analyzer.js +137 -503
  10. package/rules/common/C017_constructor_logic/config.json +50 -0
  11. package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
  12. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
  13. package/rules/security/S011_secure_guid_generation/README.md +255 -0
  14. package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
  15. package/rules/security/S011_secure_guid_generation/config.json +56 -0
  16. package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
  17. package/rules/security/S028_file_upload_size_limits/README.md +537 -0
  18. package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
  19. package/rules/security/S028_file_upload_size_limits/config.json +186 -0
  20. package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
  21. package/rules/security/S041_session_token_invalidation/README.md +303 -0
  22. package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
  23. package/rules/security/S041_session_token_invalidation/config.json +175 -0
  24. package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
  25. package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
  26. package/rules/security/S044_re_authentication_required/README.md +136 -0
  27. package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
  28. package/rules/security/S044_re_authentication_required/config.json +161 -0
  29. package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
  30. package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
  31. package/rules/security/S045_brute_force_protection/README.md +345 -0
  32. package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
  33. package/rules/security/S045_brute_force_protection/config.json +139 -0
  34. package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
  35. package/rules/common/C017_constructor_logic/semantic-analyzer.js +0 -340
@@ -0,0 +1,345 @@
1
+ # S045 - Brute-force Protection
2
+
3
+ ## Overview
4
+
5
+ This rule enforces protection against brute-force attacks on authentication endpoints. It detects missing rate limiting, account lockout mechanisms, and other brute-force protection measures in authentication flows to prevent unauthorized access attempts.
6
+
7
+ ## Description
8
+
9
+ Brute-force protection is a critical security measure that helps prevent:
10
+
11
+ - **Brute-force attacks** on login and authentication endpoints
12
+ - **Credential stuffing** attacks using stolen credentials
13
+ - **Automated attacks** that attempt to guess passwords
14
+ - **Account takeover** through systematic password attempts
15
+ - **System overload** from excessive authentication requests
16
+
17
+ ## Rule Details
18
+
19
+ **Rule ID**: S045
20
+ **Category**: Security
21
+ **Severity**: Error
22
+ **Type**: Hybrid Analysis (Symbol-based + Regex-based)
23
+
24
+ ## Examples
25
+
26
+ ### ❌ Violations
27
+
28
+ ```typescript
29
+ // NestJS - Login endpoint without rate limiting
30
+ @Post('login')
31
+ async login(@Body() loginDto: LoginDto) {
32
+ return this.authService.validateUser(loginDto);
33
+ }
34
+
35
+ // Express.js - Authentication without rate limiting
36
+ app.post('/auth/login', (req, res) => {
37
+ const { username, password } = req.body;
38
+ // No rate limiting or lockout mechanism
39
+ authenticateUser(username, password);
40
+ });
41
+
42
+ // Password reset without protection
43
+ @Post('reset-password')
44
+ async resetPassword(@Body() resetDto: ResetPasswordDto) {
45
+ // No rate limiting or captcha
46
+ return this.authService.resetPassword(resetDto);
47
+ }
48
+
49
+ // Sign-in endpoint without throttling
50
+ app.post('/signin', async (req, res) => {
51
+ const { email, password } = req.body;
52
+ // Missing brute-force protection
53
+ const user = await User.findOne({ email });
54
+ if (user && await bcrypt.compare(password, user.password)) {
55
+ res.json({ token: generateToken(user) });
56
+ }
57
+ });
58
+
59
+ // Authentication handler without lockout
60
+ function authenticateUser(username, password) {
61
+ // No attempt counting or lockout mechanism
62
+ return validateCredentials(username, password);
63
+ }
64
+
65
+ // Forgot password without protection
66
+ @Post('forgot-password')
67
+ async forgotPassword(@Body() body: { email: string }) {
68
+ // No rate limiting for password reset requests
69
+ return this.authService.sendResetEmail(body.email);
70
+ }
71
+ ```
72
+
73
+ ### ✅ Correct Usage
74
+
75
+ ```typescript
76
+ // NestJS - Login with rate limiting
77
+ @Post('login')
78
+ @Throttle(5, 60) // 5 attempts per minute
79
+ async login(@Body() loginDto: LoginDto) {
80
+ return this.authService.validateUser(loginDto);
81
+ }
82
+
83
+ // Express.js - Authentication with rate limiting middleware
84
+ const rateLimit = require('express-rate-limit');
85
+
86
+ const loginLimiter = rateLimit({
87
+ windowMs: 15 * 60 * 1000, // 15 minutes
88
+ max: 5, // limit each IP to 5 requests per windowMs
89
+ message: 'Too many login attempts, please try again later'
90
+ });
91
+
92
+ app.post('/auth/login', loginLimiter, (req, res) => {
93
+ const { username, password } = req.body;
94
+ authenticateUser(username, password);
95
+ });
96
+
97
+ // NestJS with ThrottlerModule configuration
98
+ @Module({
99
+ imports: [
100
+ ThrottlerModule.forRoot([{
101
+ ttl: 60000, // 1 minute
102
+ limit: 5, // 5 requests per minute
103
+ }]),
104
+ ],
105
+ })
106
+ export class AuthModule {}
107
+
108
+ // Express with rate-limiter-flexible
109
+ const { RateLimiterMemory } = require('rate-limiter-flexible');
110
+
111
+ const rateLimiter = new RateLimiterMemory({
112
+ points: 5, // Number of attempts
113
+ duration: 900, // Per 15 minutes
114
+ });
115
+
116
+ app.post('/login', async (req, res) => {
117
+ try {
118
+ await rateLimiter.consume(req.ip);
119
+ // Process login
120
+ } catch (rejRes) {
121
+ res.status(429).send('Too Many Requests');
122
+ }
123
+ });
124
+
125
+ // Password reset with rate limiting and captcha
126
+ @Post('reset-password')
127
+ @Throttle(3, 3600) // 3 attempts per hour
128
+ async resetPassword(
129
+ @Body() resetDto: ResetPasswordDto,
130
+ @Body('captcha') captcha: string
131
+ ) {
132
+ // Verify captcha first
133
+ await this.captchaService.verify(captcha);
134
+ return this.authService.resetPassword(resetDto);
135
+ }
136
+
137
+ // Account lockout implementation
138
+ const accountLockout = new Map();
139
+
140
+ app.post('/login', async (req, res) => {
141
+ const { username, password } = req.body;
142
+ const attempts = accountLockout.get(username) || 0;
143
+
144
+ if (attempts >= 5) {
145
+ return res.status(423).json({
146
+ error: 'Account locked due to too many failed attempts'
147
+ });
148
+ }
149
+
150
+ const isValid = await validateCredentials(username, password);
151
+ if (!isValid) {
152
+ accountLockout.set(username, attempts + 1);
153
+ return res.status(401).json({ error: 'Invalid credentials' });
154
+ }
155
+
156
+ // Reset attempts on successful login
157
+ accountLockout.delete(username);
158
+ res.json({ token: generateToken(username) });
159
+ });
160
+
161
+ // Express Brute for progressive delays
162
+ const ExpressBrute = require('express-brute');
163
+ const store = new ExpressBrute.MemoryStore();
164
+
165
+ const bruteforce = new ExpressBrute(store, {
166
+ freeRetries: 3,
167
+ minWait: 5 * 60 * 1000, // 5 minutes
168
+ maxWait: 60 * 60 * 1000, // 1 hour
169
+ });
170
+
171
+ app.post('/auth/login', bruteforce.prevent, (req, res) => {
172
+ // Login logic with brute-force protection
173
+ });
174
+ ```
175
+
176
+ ## Configuration
177
+
178
+ ### Detected Patterns
179
+
180
+ This rule detects brute-force protection issues in multiple scenarios:
181
+
182
+ ### Authentication Endpoints
183
+
184
+ - `login`, `signin`, `authenticate`, `auth` endpoints without protection
185
+ - `password`, `reset`, `forgot` endpoints without rate limiting
186
+ - Authentication handlers without throttling mechanisms
187
+
188
+ ### Rate Limiting Libraries
189
+
190
+ **Supported Libraries:**
191
+ - `express-rate-limit`
192
+ - `express-slow-down`
193
+ - `@nestjs/throttler`
194
+ - `rate-limiter-flexible`
195
+ - `bottleneck`
196
+ - `limiter`
197
+
198
+ ### Account Lockout Libraries
199
+
200
+ **Supported Libraries:**
201
+ - `express-slow-down`
202
+ - `rate-limiter-flexible`
203
+ - `express-brute`
204
+ - `express-brute-mongo`
205
+
206
+ ### CAPTCHA Libraries
207
+
208
+ **Supported Libraries:**
209
+ - `recaptcha`
210
+ - `hcaptcha`
211
+ - `turnstile`
212
+ - `captcha`
213
+
214
+ ### Protection Patterns
215
+
216
+ **Required Protection Indicators:**
217
+ - Rate limiting: `rate.*limit`, `throttle`, `@Throttle()`
218
+ - Account lockout: `lockout`, `max.*attempts`, `brute.*force.*protection`
219
+ - Progressive delays: `cooldown`, `progressive.*delay`
220
+ - CAPTCHA verification: `captcha`, `recaptcha`, `hcaptcha`
221
+
222
+ ### Vulnerable Patterns
223
+
224
+ **Detected Anti-patterns:**
225
+ - Authentication endpoints without rate limiting
226
+ - Login handlers without throttling
227
+ - Password reset without protection
228
+ - Sign-in without attempt limits
229
+
230
+ ## Security Benefits
231
+
232
+ 1. **Attack Prevention**: Blocks automated brute-force attacks
233
+ 2. **Resource Protection**: Prevents system overload from excessive requests
234
+ 3. **Account Security**: Protects user accounts from unauthorized access
235
+ 4. **Service Availability**: Maintains service availability under attack
236
+ 5. **Compliance**: Meets security standards and regulations
237
+
238
+ ## Analysis Approach
239
+
240
+ ### Symbol-based Analysis (Primary)
241
+
242
+ - Uses TypeScript AST for semantic analysis
243
+ - Detects authentication endpoints and decorators
244
+ - Analyzes middleware usage and rate limiting
245
+ - Provides precise line/column positions
246
+
247
+ ### Regex-based Analysis (Fallback)
248
+
249
+ - Pattern-based detection for complex cases
250
+ - Handles string-based endpoint definitions
251
+ - Covers edge cases missed by AST analysis
252
+ - Maintains line number accuracy
253
+
254
+ ## Best Practices
255
+
256
+ 1. **Implement rate limiting** on all authentication endpoints
257
+ 2. **Use progressive delays** for repeated failed attempts
258
+ 3. **Enable account lockout** after multiple failed attempts
259
+ 4. **Add CAPTCHA verification** for sensitive operations
260
+ 5. **Monitor and log** authentication attempts
261
+ 6. **Use IP-based rate limiting** to prevent distributed attacks
262
+ 7. **Implement proper error messages** that don't reveal user existence
263
+ 8. **Set appropriate time windows** for rate limiting (15-60 minutes)
264
+ 9. **Configure reasonable attempt limits** (3-5 attempts before lockout)
265
+ 10. **Use established libraries** rather than custom implementations
266
+
267
+ ## Configuration Options
268
+
269
+ ### Default Settings
270
+
271
+ - **Max Attempts Threshold**: 5 attempts
272
+ - **Time Window**: 15 minutes
273
+ - **Enable Rate Limit Detection**: true
274
+ - **Enable Account Lockout Detection**: true
275
+ - **Enable CAPTCHA Detection**: true
276
+
277
+ ### Customizable Parameters
278
+
279
+ ```json
280
+ {
281
+ "maxAttemptsThreshold": 5,
282
+ "timeWindowMinutes": 15,
283
+ "checkAuthenticationEndpoints": [
284
+ "login", "signin", "authenticate", "auth",
285
+ "password", "reset", "forgot"
286
+ ]
287
+ }
288
+ ```
289
+
290
+ ## Testing
291
+
292
+ Run the rule on test fixtures:
293
+
294
+ ```bash
295
+ # Test violations
296
+ node cli.js --rule=S045 --input=examples/rule-test-fixtures/rules/S045_brute_force_protection/violations --engine=heuristic
297
+
298
+ # Test clean examples
299
+ node cli.js --rule=S045 --input=examples/rule-test-fixtures/rules/S045_brute_force_protection/clean --engine=heuristic
300
+
301
+ # Test all examples
302
+ node cli.js --rule=S045 --input=examples/rule-test-fixtures/rules/S045_brute_force_protection --engine=heuristic
303
+
304
+ # Framework-specific testing
305
+ # Test with ESLint engine (fast)
306
+ node cli.js --rule=S045 --input=path/to/your/files
307
+
308
+ # Test with heuristic engine (comprehensive)
309
+ node cli.js --rule=S045 --input=path/to/your/files --engine=heuristic
310
+ ```
311
+
312
+ ## Framework Compatibility
313
+
314
+ - **Node.js**: All versions
315
+ - **Frameworks**: Express.js, NestJS, Fastify, Koa
316
+ - **Languages**: JavaScript, TypeScript
317
+ - **Analysis Engines**: ESLint (fast), Heuristic (comprehensive)
318
+
319
+ ## Related Rules
320
+
321
+ - **S031**: Set Secure flag for Session Cookies
322
+ - **S032**: Set HttpOnly attribute for Session Cookies
323
+ - **S041**: Session Tokens must be invalidated after logout or expiration
324
+ - **S044**: Re-authentication Required for Sensitive Operations
325
+ - **S048**: No Current Password in Password Reset
326
+
327
+ Together, these rules provide comprehensive authentication security.
328
+
329
+ ## OWASP References
330
+
331
+ - [OWASP Brute Force Attack](https://owasp.org/www-community/attacks/Brute_force_attack)
332
+ - [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
333
+ - [OWASP Blocking Brute Force Attacks](https://owasp.org/www-community/controls/Blocking_Brute_Force_Attacks)
334
+ - [PortSwigger Web Security - Brute Force](https://portswigger.net/web-security/authentication/password-based/brute-force)
335
+
336
+ ## Implementation Notes
337
+
338
+ This rule uses a hybrid analysis approach:
339
+
340
+ 1. **Symbol-based analysis** detects decorator usage (`@Throttle()`) and middleware patterns
341
+ 2. **Regex-based analysis** catches string-based configurations and complex patterns
342
+ 3. **Library detection** identifies known rate limiting and protection libraries
343
+ 4. **Context awareness** distinguishes between authentication and other endpoints
344
+
345
+ The rule maintains high accuracy while minimizing false positives through careful pattern matching and context analysis.
@@ -0,0 +1,336 @@
1
+ /**
2
+ * S045 Main Analyzer - Brute-force Protection
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Command: node cli.js --rule=S045 --input=examples/rule-test-fixtures/rules/S045_brute_force_protection --engine=heuristic
6
+ */
7
+
8
+ const S045SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+
10
+ class S045Analyzer {
11
+ constructor(options = {}) {
12
+ if (process.env.SUNLINT_DEBUG) {
13
+ console.log(`🔧 [S045] Constructor called with options:`, !!options);
14
+ console.log(
15
+ `🔧 [S045] Options type:`,
16
+ typeof options,
17
+ Object.keys(options || {})
18
+ );
19
+ }
20
+
21
+ this.ruleId = "S045";
22
+ this.ruleName = "Brute-force Protection";
23
+ this.description =
24
+ "Implement protection against brute-force attacks on authentication endpoints. This rule detects missing rate limiting, account lockout mechanisms, and other brute-force protection measures in authentication flows.";
25
+ this.semanticEngine = options.semanticEngine || null;
26
+ this.verbose = options.verbose || false;
27
+
28
+ // Configuration
29
+ this.config = {
30
+ useSymbolBased: true, // Primary approach
31
+ fallbackToRegex: true, // Secondary approach
32
+ regexBasedOnly: false, // Can be set to true for pure mode
33
+ };
34
+
35
+ // Initialize analyzers
36
+ try {
37
+ this.symbolAnalyzer = new S045SymbolBasedAnalyzer(this.semanticEngine);
38
+ if (process.env.SUNLINT_DEBUG) {
39
+ console.log(`🔧 [S045] Symbol analyzer created successfully`);
40
+ }
41
+ } catch (error) {
42
+ console.error(`🔧 [S045] Error creating symbol analyzer:`, error);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Initialize analyzer with semantic engine
48
+ */
49
+ async initialize(semanticEngine) {
50
+ this.semanticEngine = semanticEngine;
51
+
52
+ if (process.env.SUNLINT_DEBUG) {
53
+ console.log(`🔧 [S045] Main analyzer initializing...`);
54
+ }
55
+
56
+ // Initialize symbol analyzer
57
+ if (this.symbolAnalyzer) {
58
+ await this.symbolAnalyzer.initialize?.(semanticEngine);
59
+ }
60
+
61
+ if (process.env.SUNLINT_DEBUG) {
62
+ console.log(`🔧 [S045] Main analyzer initialized successfully`);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Single file analysis method for testing
68
+ */
69
+ analyzeSingle(filePath, options = {}) {
70
+ if (process.env.SUNLINT_DEBUG) {
71
+ console.log(`📊 [S045] analyzeSingle() called for: ${filePath}`);
72
+ }
73
+
74
+ // Return result using same format as analyze method
75
+ return this.analyze([filePath], "typescript", options);
76
+ }
77
+
78
+ async analyze(files, language, options = {}) {
79
+ if (process.env.SUNLINT_DEBUG) {
80
+ console.log(
81
+ `🔧 [S045] analyze() method called with ${files.length} files, language: ${language}`
82
+ );
83
+ }
84
+
85
+ const violations = [];
86
+
87
+ for (const filePath of files) {
88
+ try {
89
+ if (process.env.SUNLINT_DEBUG) {
90
+ console.log(`🔧 [S045] Processing file: ${filePath}`);
91
+ }
92
+
93
+ const fileViolations = await this.analyzeFile(filePath, options);
94
+ violations.push(...fileViolations);
95
+
96
+ if (process.env.SUNLINT_DEBUG) {
97
+ console.log(
98
+ `🔧 [S045] File ${filePath}: Found ${fileViolations.length} violations`
99
+ );
100
+ }
101
+ } catch (error) {
102
+ console.warn(
103
+ `⚠ [S045] Analysis failed for ${filePath}:`,
104
+ error.message
105
+ );
106
+ }
107
+ }
108
+
109
+ if (process.env.SUNLINT_DEBUG) {
110
+ console.log(`🔧 [S045] Total violations found: ${violations.length}`);
111
+ }
112
+
113
+ return violations;
114
+ }
115
+
116
+ async analyzeFile(filePath, options = {}) {
117
+ if (process.env.SUNLINT_DEBUG) {
118
+ console.log(`🔍 [S045] analyzeFile() called for: ${filePath}`);
119
+ }
120
+
121
+ // Create a Map to track unique violations and prevent duplicates
122
+ const violationMap = new Map();
123
+
124
+ // 1. Try Symbol-based analysis first (primary)
125
+ if (
126
+ this.config.useSymbolBased &&
127
+ this.semanticEngine?.project &&
128
+ this.semanticEngine?.initialized
129
+ ) {
130
+ try {
131
+ if (process.env.SUNLINT_DEBUG) {
132
+ console.log(`🔧 [S045] Trying symbol-based analysis...`);
133
+ }
134
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
135
+ if (sourceFile) {
136
+ if (process.env.SUNLINT_DEBUG) {
137
+ console.log(`🔧 [S045] Source file found, analyzing...`);
138
+ }
139
+ const symbolViolations = await this.symbolAnalyzer.analyze(
140
+ sourceFile,
141
+ filePath
142
+ );
143
+
144
+ // Add to violation map with deduplication
145
+ symbolViolations.forEach((violation) => {
146
+ const key = `${violation.line}:${violation.column}:${violation.message}`;
147
+ if (!violationMap.has(key)) {
148
+ violationMap.set(key, violation);
149
+ }
150
+ });
151
+
152
+ if (process.env.SUNLINT_DEBUG) {
153
+ console.log(
154
+ `🔧 [S045] Symbol analysis completed: ${symbolViolations.length} violations`
155
+ );
156
+ }
157
+ } else {
158
+ if (process.env.SUNLINT_DEBUG) {
159
+ console.log(`🔧 [S045] Source file not found, falling back...`);
160
+ }
161
+ }
162
+ } catch (error) {
163
+ console.warn(`⚠ [S045] Symbol analysis failed:`, error.message);
164
+ }
165
+ }
166
+
167
+ // 2. Fallback to regex-based analysis if needed
168
+ if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
169
+ try {
170
+ if (process.env.SUNLINT_DEBUG) {
171
+ console.log(`🔧 [S045] Trying regex-based analysis...`);
172
+ }
173
+ const regexViolations = await this.analyzeWithRegex(filePath);
174
+
175
+ // Add to violation map with deduplication
176
+ regexViolations.forEach((violation) => {
177
+ const key = `${violation.line}:${violation.column}:${violation.message}`;
178
+ if (!violationMap.has(key)) {
179
+ violationMap.set(key, violation);
180
+ }
181
+ });
182
+
183
+ if (process.env.SUNLINT_DEBUG) {
184
+ console.log(
185
+ `🔧 [S045] Regex analysis completed: ${regexViolations.length} violations`
186
+ );
187
+ }
188
+ } catch (error) {
189
+ console.warn(`⚠ [S045] Regex analysis failed:`, error.message);
190
+ }
191
+ }
192
+
193
+ // Convert Map values to array and add filePath to each violation
194
+ const finalViolations = Array.from(violationMap.values()).map(
195
+ (violation) => ({
196
+ ...violation,
197
+ filePath: filePath,
198
+ file: filePath, // Also add 'file' for compatibility
199
+ })
200
+ );
201
+
202
+ if (process.env.SUNLINT_DEBUG) {
203
+ console.log(
204
+ `🔧 [S045] File analysis completed: ${finalViolations.length} unique violations`
205
+ );
206
+ }
207
+
208
+ return finalViolations;
209
+ }
210
+
211
+ async analyzeWithRegex(filePath) {
212
+ const fs = require('fs');
213
+ const violations = [];
214
+
215
+ try {
216
+ const content = fs.readFileSync(filePath, 'utf8');
217
+ const lines = content.split('\n');
218
+
219
+ // Authentication endpoint patterns - simplified and more specific
220
+ const authEndpointPatterns = [
221
+ { pattern: /@Post\(['"`](.*login.*)['"`]\)/gi, type: 'nestjs' },
222
+ { pattern: /@Post\(['"`](.*signin.*)['"`]\)/gi, type: 'nestjs' },
223
+ { pattern: /@Post\(['"`](.*auth.*)['"`]\)/gi, type: 'nestjs' },
224
+ { pattern: /@Post\(['"`](.*password.*)['"`]\)/gi, type: 'nestjs' },
225
+ { pattern: /@Post\(['"`](.*reset.*)['"`]\)/gi, type: 'nestjs' },
226
+ { pattern: /@Post\(['"`](.*signup.*)['"`]\)/gi, type: 'nestjs' },
227
+ { pattern: /app\.post\(['"`](.*login.*)['"`]/gi, type: 'express' },
228
+ { pattern: /app\.post\(['"`](.*signin.*)['"`]/gi, type: 'express' },
229
+ { pattern: /app\.post\(['"`](.*auth.*)['"`]/gi, type: 'express' },
230
+ { pattern: /app\.post\(['"`](.*password.*)['"`]/gi, type: 'express' },
231
+ { pattern: /app\.post\(['"`](.*reset.*)['"`]/gi, type: 'express' },
232
+ { pattern: /app\.post\(['"`](.*signup.*)['"`]/gi, type: 'express' },
233
+ { pattern: /router\.post\(['"`](.*login.*)['"`]/gi, type: 'express' },
234
+ { pattern: /router\.post\(['"`](.*signin.*)['"`]/gi, type: 'express' },
235
+ { pattern: /router\.post\(['"`](.*auth.*)['"`]/gi, type: 'express' },
236
+ { pattern: /router\.post\(['"`](.*password.*)['"`]/gi, type: 'express' },
237
+ { pattern: /router\.post\(['"`](.*reset.*)['"`]/gi, type: 'express' },
238
+ { pattern: /router\.post\(['"`](.*signup.*)['"`]/gi, type: 'express' }
239
+ ];
240
+
241
+ // Protection patterns - more comprehensive
242
+ const protectionPatterns = [
243
+ /@Throttle/gi,
244
+ /@RateLimit/gi,
245
+ /@UseGuards.*ThrottlerGuard/gi,
246
+ /rateLimit/gi,
247
+ /throttle/gi,
248
+ /express-rate-limit/gi,
249
+ /@nestjs\/throttler/gi,
250
+ /lockout/gi,
251
+ /maxAttempts/gi,
252
+ /cooldown/gi,
253
+ /brute.*force.*protection/gi,
254
+ /windowMs/gi,
255
+ /max.*requests/gi,
256
+ /express-slow-down/gi,
257
+ /rate-limiter-flexible/gi,
258
+ /Limiter/gi,
259
+ /limiter/gi,
260
+ /RateLimit/gi,
261
+ /Throttle/gi
262
+ ];
263
+
264
+ // Check if file has any protection mechanisms at all
265
+ const hasGlobalProtection = protectionPatterns.some(pattern => {
266
+ pattern.lastIndex = 0;
267
+ return pattern.test(content);
268
+ });
269
+
270
+ for (let i = 0; i < lines.length; i++) {
271
+ const line = lines[i];
272
+ const lineNumber = i + 1;
273
+
274
+ // Check for authentication endpoints
275
+ for (const { pattern, type } of authEndpointPatterns) {
276
+ // Reset regex lastIndex to avoid issues with global flag
277
+ pattern.lastIndex = 0;
278
+ const match = pattern.exec(line);
279
+ if (match) {
280
+ // Check if this specific endpoint has protection
281
+ const hasProtection = this.checkProtectionInContext(lines, i, protectionPatterns);
282
+
283
+ // Always flag violations if no protection is found
284
+ if (!hasProtection) {
285
+ violations.push({
286
+ rule: this.ruleId,
287
+ source: filePath,
288
+ category: "security",
289
+ line: lineNumber,
290
+ column: 1,
291
+ message: `Authentication endpoint '${match[1]}' lacks brute-force protection (rate limiting, account lockout, or CAPTCHA)`,
292
+ severity: "error",
293
+ });
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ return violations;
300
+ } catch (error) {
301
+ if (this.verbose) {
302
+ console.log(`🔍 [${this.ruleId}] Regex analysis error:`, error.message);
303
+ }
304
+ return [];
305
+ }
306
+ }
307
+
308
+ checkProtectionInContext(lines, currentLineIndex, protectionPatterns) {
309
+ // Check current line and surrounding context (10 lines before and after)
310
+ const start = Math.max(0, currentLineIndex - 10);
311
+ const end = Math.min(lines.length, currentLineIndex + 11);
312
+
313
+ for (let i = start; i < end; i++) {
314
+ const line = lines[i];
315
+ for (const pattern of protectionPatterns) {
316
+ pattern.lastIndex = 0;
317
+ if (pattern.test(line)) {
318
+ return true;
319
+ }
320
+ }
321
+ }
322
+
323
+ return false;
324
+ }
325
+
326
+ /**
327
+ * Clean up resources
328
+ */
329
+ cleanup() {
330
+ if (this.symbolAnalyzer?.cleanup) {
331
+ this.symbolAnalyzer.cleanup();
332
+ }
333
+ }
334
+ }
335
+
336
+ module.exports = S045Analyzer;