@sun-asterisk/sunlint 1.3.27 → 1.3.29
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/config/rules/enhanced-rules-registry.json +2 -1
- package/config/rules/rules-registry-generated.json +22 -22
- package/origin-rules/security-en.md +351 -338
- package/package.json +1 -1
- package/rules/common/C003_no_vague_abbreviations/analyzer.js +73 -21
- package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +206 -2
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +553 -58
- package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +9 -5
- package/rules/security/S005_no_origin_auth/analyzer.js +97 -148
- package/rules/security/S005_no_origin_auth/config.json +28 -67
- package/rules/security/S005_no_origin_auth/symbol-based-analyzer.js +708 -0
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +170 -31
- package/rules/security/S010_no_insecure_encryption/analyzer.js +8 -2
- package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +87 -0
- package/rules/security/S017_use_parameterized_queries/analyzer.js +11 -78
- package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +1146 -1
- package/rules/security/S020_no_eval_dynamic_code/analyzer.js +55 -130
- package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +4 -19
- package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +1 -1
- package/rules/security/S043_password_changes_invalidate_all_sessions/README.md +107 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/analyzer.js +153 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/config.json +41 -0
- package/rules/security/S043_password_changes_invalidate_all_sessions/symbol-based-analyzer.js +541 -0
- package/docs/COMMAND-EXAMPLES.md +0 -390
- package/docs/FILE_LIMITS_COMPLETION_REPORT.md +0 -151
- package/docs/FOLDER_STRUCTURE.md +0 -59
- package/docs/SIMPLIFIED_USAGE_GUIDE.md +0 -208
- package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +0 -541
- package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +0 -307
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const S020SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
9
|
-
const S020RegexBasedAnalyzer = require("./regex-based-analyzer.js");
|
|
10
9
|
|
|
11
10
|
class S020Analyzer {
|
|
12
11
|
constructor(options = {}) {
|
|
@@ -26,14 +25,6 @@ class S020Analyzer {
|
|
|
26
25
|
this.semanticEngine = options.semanticEngine || null;
|
|
27
26
|
this.verbose = options.verbose || false;
|
|
28
27
|
|
|
29
|
-
this.config = {
|
|
30
|
-
useSymbolBased: true,
|
|
31
|
-
fallbackToRegex: true,
|
|
32
|
-
regexBasedOnly: false,
|
|
33
|
-
prioritizeSymbolic: true, // Prefer symbol-based when available
|
|
34
|
-
fallbackToSymbol: true, // Allow symbol analysis even without semantic engine
|
|
35
|
-
};
|
|
36
|
-
|
|
37
28
|
try {
|
|
38
29
|
this.symbolAnalyzer = new S020SymbolBasedAnalyzer(this.semanticEngine);
|
|
39
30
|
if (process.env.SUNLINT_DEBUG)
|
|
@@ -41,14 +32,6 @@ class S020Analyzer {
|
|
|
41
32
|
} catch (error) {
|
|
42
33
|
console.error(`🔧 [S020] Error creating symbol analyzer:`, error);
|
|
43
34
|
}
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
this.regexAnalyzer = new S020RegexBasedAnalyzer(this.semanticEngine);
|
|
47
|
-
if (process.env.SUNLINT_DEBUG)
|
|
48
|
-
console.log(`🔧 [S020] Regex analyzer created successfully`);
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error(`🔧 [S020] Error creating regex analyzer:`, error);
|
|
51
|
-
}
|
|
52
35
|
}
|
|
53
36
|
|
|
54
37
|
async initialize(semanticEngine) {
|
|
@@ -58,9 +41,6 @@ class S020Analyzer {
|
|
|
58
41
|
|
|
59
42
|
if (this.symbolAnalyzer)
|
|
60
43
|
await this.symbolAnalyzer.initialize?.(semanticEngine);
|
|
61
|
-
if (this.regexAnalyzer)
|
|
62
|
-
await this.regexAnalyzer.initialize?.(semanticEngine);
|
|
63
|
-
if (this.regexAnalyzer) this.regexAnalyzer.cleanup?.();
|
|
64
44
|
|
|
65
45
|
if (process.env.SUNLINT_DEBUG)
|
|
66
46
|
console.log(`🔧 [S020] Main analyzer initialized successfully`);
|
|
@@ -108,138 +88,84 @@ class S020Analyzer {
|
|
|
108
88
|
console.log(`🔍 [S020] analyzeFile() called for: ${filePath}`);
|
|
109
89
|
const violationMap = new Map();
|
|
110
90
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
`🔧 [S020] Symbol check: useSymbolBased=${
|
|
115
|
-
this.config.useSymbolBased
|
|
116
|
-
}, semanticEngine=${!!this.semanticEngine}, project=${!!this
|
|
117
|
-
.semanticEngine?.project}, initialized=${!!this.semanticEngine
|
|
118
|
-
?.initialized}`
|
|
119
|
-
);
|
|
120
|
-
}
|
|
91
|
+
try {
|
|
92
|
+
if (process.env.SUNLINT_DEBUG)
|
|
93
|
+
console.log(`🔧 [S020] Running symbol-based analysis...`);
|
|
121
94
|
|
|
122
|
-
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
(
|
|
95
|
+
let sourceFile = null;
|
|
96
|
+
if (this.semanticEngine?.project) {
|
|
97
|
+
sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
98
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
99
|
+
console.log(
|
|
100
|
+
`🔧 [S020] Checked existing semantic engine project: sourceFile=${!!sourceFile}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
126
104
|
|
|
127
|
-
|
|
128
|
-
|
|
105
|
+
if (!sourceFile) {
|
|
106
|
+
// Create a minimal ts-morph project for this analysis
|
|
129
107
|
if (process.env.SUNLINT_DEBUG)
|
|
130
|
-
console.log(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
108
|
+
console.log(
|
|
109
|
+
`🔧 [S020] Creating temporary ts-morph project for: ${filePath}`
|
|
110
|
+
);
|
|
111
|
+
try {
|
|
112
|
+
const fs = require("fs");
|
|
113
|
+
const path = require("path");
|
|
114
|
+
const { Project } = require("ts-morph");
|
|
115
|
+
|
|
116
|
+
// Check if file exists and read content
|
|
117
|
+
if (!fs.existsSync(filePath)) {
|
|
118
|
+
throw new Error(`File not found: ${filePath}`);
|
|
139
119
|
}
|
|
140
|
-
}
|
|
141
120
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (process.env.SUNLINT_DEBUG)
|
|
145
|
-
console.log(
|
|
146
|
-
`🔧 [S020] Creating temporary ts-morph project for: ${filePath}`
|
|
147
|
-
);
|
|
148
|
-
try {
|
|
149
|
-
const fs = require("fs");
|
|
150
|
-
const path = require("path");
|
|
151
|
-
const { Project } = require("ts-morph");
|
|
152
|
-
|
|
153
|
-
// Check if file exists and read content
|
|
154
|
-
if (!fs.existsSync(filePath)) {
|
|
155
|
-
throw new Error(`File not found: ${filePath}`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
159
|
-
const fileName = path.basename(filePath);
|
|
160
|
-
|
|
161
|
-
const tempProject = new Project({
|
|
162
|
-
useInMemoryFileSystem: true,
|
|
163
|
-
compilerOptions: {
|
|
164
|
-
allowJs: true,
|
|
165
|
-
allowSyntheticDefaultImports: true,
|
|
166
|
-
},
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// Add file content to in-memory project
|
|
170
|
-
sourceFile = tempProject.createSourceFile(fileName, fileContent);
|
|
171
|
-
if (process.env.SUNLINT_DEBUG)
|
|
172
|
-
console.log(
|
|
173
|
-
`🔧 [S020] Temporary project created successfully with file: ${fileName}`
|
|
174
|
-
);
|
|
175
|
-
} catch (projectError) {
|
|
176
|
-
if (process.env.SUNLINT_DEBUG)
|
|
177
|
-
console.log(
|
|
178
|
-
`🔧 [S020] Failed to create temporary project:`,
|
|
179
|
-
projectError.message
|
|
180
|
-
);
|
|
181
|
-
throw projectError;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
121
|
+
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
122
|
+
const fileName = path.basename(filePath);
|
|
184
123
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const key = `${v.line}:${v.column}:${v.message}`;
|
|
192
|
-
if (!violationMap.has(key)) violationMap.set(key, v);
|
|
124
|
+
const tempProject = new Project({
|
|
125
|
+
useInMemoryFileSystem: true,
|
|
126
|
+
compilerOptions: {
|
|
127
|
+
allowJs: true,
|
|
128
|
+
allowSyntheticDefaultImports: true,
|
|
129
|
+
},
|
|
193
130
|
});
|
|
131
|
+
|
|
132
|
+
// Add file content to in-memory project
|
|
133
|
+
sourceFile = tempProject.createSourceFile(fileName, fileContent);
|
|
194
134
|
if (process.env.SUNLINT_DEBUG)
|
|
195
135
|
console.log(
|
|
196
|
-
`🔧 [S020]
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
// If symbol-based found violations AND prioritizeSymbolic is true, skip regex
|
|
200
|
-
// But still run regex if symbol-based didn't find any violations
|
|
201
|
-
if (this.config.prioritizeSymbolic && symbolViolations.length > 0) {
|
|
202
|
-
const finalViolations = Array.from(violationMap.values()).map(
|
|
203
|
-
(v) => ({
|
|
204
|
-
...v,
|
|
205
|
-
filePath,
|
|
206
|
-
file: filePath,
|
|
207
|
-
})
|
|
136
|
+
`🔧 [S020] Temporary project created successfully with file: ${fileName}`
|
|
208
137
|
);
|
|
209
|
-
|
|
210
|
-
console.log(
|
|
211
|
-
`🔧 [S020] Symbol-based analysis prioritized: ${finalViolations.length} violations`
|
|
212
|
-
);
|
|
213
|
-
return finalViolations;
|
|
214
|
-
}
|
|
215
|
-
} else {
|
|
138
|
+
} catch (projectError) {
|
|
216
139
|
if (process.env.SUNLINT_DEBUG)
|
|
217
140
|
console.log(
|
|
218
|
-
`🔧 [S020]
|
|
141
|
+
`🔧 [S020] Failed to create temporary project:`,
|
|
142
|
+
projectError.message
|
|
219
143
|
);
|
|
144
|
+
throw projectError;
|
|
220
145
|
}
|
|
221
|
-
} catch (error) {
|
|
222
|
-
console.warn(`⚠ [S020] Symbol analysis failed:`, error.message);
|
|
223
146
|
}
|
|
224
|
-
}
|
|
225
147
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
regexViolations.forEach((v) => {
|
|
148
|
+
if (sourceFile) {
|
|
149
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
150
|
+
sourceFile,
|
|
151
|
+
filePath
|
|
152
|
+
);
|
|
153
|
+
symbolViolations.forEach((v) => {
|
|
233
154
|
const key = `${v.line}:${v.column}:${v.message}`;
|
|
234
155
|
if (!violationMap.has(key)) violationMap.set(key, v);
|
|
235
156
|
});
|
|
236
157
|
if (process.env.SUNLINT_DEBUG)
|
|
237
158
|
console.log(
|
|
238
|
-
`🔧 [S020]
|
|
159
|
+
`🔧 [S020] Symbol analysis completed: ${symbolViolations.length} violations`
|
|
160
|
+
);
|
|
161
|
+
} else {
|
|
162
|
+
if (process.env.SUNLINT_DEBUG)
|
|
163
|
+
console.log(
|
|
164
|
+
`🔧 [S020] No source file found, skipping symbol analysis`
|
|
239
165
|
);
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.warn(`⚠ [S020] Regex analysis failed:`, error.message);
|
|
242
166
|
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.warn(`⚠ [S020] Symbol analysis failed:`, error.message);
|
|
243
169
|
}
|
|
244
170
|
|
|
245
171
|
const finalViolations = Array.from(violationMap.values()).map((v) => ({
|
|
@@ -256,7 +182,6 @@ class S020Analyzer {
|
|
|
256
182
|
|
|
257
183
|
cleanup() {
|
|
258
184
|
if (this.symbolAnalyzer?.cleanup) this.symbolAnalyzer.cleanup();
|
|
259
|
-
if (this.regexAnalyzer?.cleanup) this.regexAnalyzer.cleanup();
|
|
260
185
|
}
|
|
261
186
|
}
|
|
262
187
|
|
|
@@ -182,7 +182,8 @@ class S020SymbolBasedAnalyzer {
|
|
|
182
182
|
if (args.length > 0) {
|
|
183
183
|
const firstArg = args[0];
|
|
184
184
|
|
|
185
|
-
//
|
|
185
|
+
// ONLY flag if first argument is a string literal
|
|
186
|
+
// This is the only truly dangerous case for setTimeout/setInterval
|
|
186
187
|
if (firstArg.getKind() === SyntaxKind.StringLiteral) {
|
|
187
188
|
const startLine = call.getStartLineNumber();
|
|
188
189
|
violations.push({
|
|
@@ -193,24 +194,8 @@ class S020SymbolBasedAnalyzer {
|
|
|
193
194
|
column: 1,
|
|
194
195
|
});
|
|
195
196
|
}
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
const argText = firstArg.getText().toLowerCase();
|
|
199
|
-
const hasDynamicIndicator = this.dynamicCodeIndicators.some(
|
|
200
|
-
(indicator) => argText.includes(indicator)
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
if (hasDynamicIndicator) {
|
|
204
|
-
const startLine = call.getStartLineNumber();
|
|
205
|
-
violations.push({
|
|
206
|
-
ruleId: this.ruleId,
|
|
207
|
-
message: `Function '${functionName}()' may be executing dynamic code based on variable naming`,
|
|
208
|
-
severity: "warning",
|
|
209
|
-
line: startLine,
|
|
210
|
-
column: 1,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
197
|
+
// NOTE: Removed variable name checking as it causes too many false positives
|
|
198
|
+
// Functions like setTimeout with arrow functions or function references are safe
|
|
214
199
|
}
|
|
215
200
|
}
|
|
216
201
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* S042 Require re-authentication for long-lived sessions or sensitive actions
|
|
3
3
|
* Primary: Symbol-based analysis (when available)
|
|
4
4
|
* Fallback: Regex-based for all other cases
|
|
5
|
-
* Purpose:
|
|
5
|
+
* Purpose: Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.
|
|
6
6
|
* Command: node cli.js --rule=S042 --input=examples/rule-test-fixtures/rules/S042_require_re_authentication_for_long_lived --engine=heuristic --verbose
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# S043 - Password Changes Must Invalidate All Active Sessions
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
**Rule ID:** S043
|
|
6
|
+
**Severity:** Medium
|
|
7
|
+
**Category:** Security
|
|
8
|
+
**Status:** Activated
|
|
9
|
+
**Version:** 1.0
|
|
10
|
+
|
|
11
|
+
## Objective
|
|
12
|
+
|
|
13
|
+
Ensure that attackers cannot continue using old session tokens after a password change. This rule enforces correct access control after sensitive password updates by requiring all active sessions to be invalidated.
|
|
14
|
+
|
|
15
|
+
## Description
|
|
16
|
+
|
|
17
|
+
When a user changes their password, all existing session tokens, JWT tokens, and cached credentials must be invalidated immediately. This prevents attackers who may have compromised an old session from continuing to access the account after the password has been changed.
|
|
18
|
+
|
|
19
|
+
This rule performs **deep analysis** across multiple layers:
|
|
20
|
+
- **Controller → Service → Repository → Third Party**
|
|
21
|
+
- Traces up to 4 levels deep in the call chain
|
|
22
|
+
- Resolves NestJS dependency injection to follow service calls
|
|
23
|
+
- Detects session invalidation in any layer of the application
|
|
24
|
+
|
|
25
|
+
## Violation Criteria
|
|
26
|
+
|
|
27
|
+
A password change function is flagged if it does NOT:
|
|
28
|
+
|
|
29
|
+
1. Invalidate all other active sessions (except current if necessary)
|
|
30
|
+
2. Clear all session tokens from database, Redis, or memory storage
|
|
31
|
+
3. For JWT: use token versioning or timestamp to revoke old tokens
|
|
32
|
+
4. Require re-login across all devices
|
|
33
|
+
5. Call logout or sign out operations
|
|
34
|
+
6. Clear session cache (Redis, in-memory, etc.)
|
|
35
|
+
7. Use third-party auth service global sign out (AWS Cognito, Auth0, Firebase)
|
|
36
|
+
|
|
37
|
+
## Detected Patterns
|
|
38
|
+
|
|
39
|
+
### ✅ Session Invalidation (PASS)
|
|
40
|
+
|
|
41
|
+
The rule detects the following valid patterns:
|
|
42
|
+
|
|
43
|
+
#### 1. Direct Session Invalidation Calls
|
|
44
|
+
```typescript
|
|
45
|
+
await sessionService.invalidateAllSessions(userId);
|
|
46
|
+
await sessionService.clearAllSessions(userId);
|
|
47
|
+
await sessionService.destroyAllSessions(userId);
|
|
48
|
+
await sessionService.logoutAllDevices(userId);
|
|
49
|
+
|
|
50
|
+
### 2. Database Session Deletion
|
|
51
|
+
```typescript
|
|
52
|
+
await sessionRepository.delete({ userId });
|
|
53
|
+
await sessionRepository.deleteAllByUserId(userId);
|
|
54
|
+
await this.sessionRepository
|
|
55
|
+
.createQueryBuilder()
|
|
56
|
+
.delete()
|
|
57
|
+
.where('userId = :userId', { userId })
|
|
58
|
+
.execute();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. JWT Token Versioning
|
|
62
|
+
```typescript
|
|
63
|
+
user.tokenVersion = (user.tokenVersion || 0) + 1;
|
|
64
|
+
user.tokenVersion++;
|
|
65
|
+
await userRepository.update(userId, {
|
|
66
|
+
tokenVersion: () => 'tokenVersion + 1'
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 4. Redis/Cache Clearing
|
|
71
|
+
```typescript
|
|
72
|
+
await redis.del(`session:${userId}:*`);
|
|
73
|
+
await cacheManager.del(username);
|
|
74
|
+
await redisService.clearUserSessions(userId);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Deep Analysis Features
|
|
78
|
+
```typescript
|
|
79
|
+
// File: auth.controller.ts
|
|
80
|
+
async changePassword(data) {
|
|
81
|
+
await this.authService.changePassword(data); // ← Traces into service
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// File: auth.service.ts (different file)
|
|
85
|
+
async changePassword(data) {
|
|
86
|
+
await this.userRepo.updatePassword(data);
|
|
87
|
+
await this.sessionService.clearAllSessions(data.userId); // ← Found! ✅
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### NestJS Dependency Injection Resolution
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
@Injectable()
|
|
95
|
+
export class AuthController {
|
|
96
|
+
constructor(
|
|
97
|
+
private readonly commonChangePasswordService: CommonChangePasswordService
|
|
98
|
+
) {}
|
|
99
|
+
|
|
100
|
+
async changePassword() {
|
|
101
|
+
// Resolves: commonChangePasswordService → CommonChangePasswordService class
|
|
102
|
+
await this.commonChangePasswordService.changePassword();
|
|
103
|
+
// ↓ Deep traces into CommonChangePasswordService.changePassword() method
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S043 Password changes must invalidate all other login sessions
|
|
3
|
+
* Primary: Symbol-based analysis (when available)
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* Purpose: Ensure attackers cannot continue using old session tokens after a password change. Enforce correct access control after sensitive updates.
|
|
6
|
+
* Command: node cli.js --rule=S043 --input=examples/rule-test-fixtures/rules/S043_password_changes_invalidate_all_sessions --engine=heuristic --verbose
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const S043SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
10
|
+
|
|
11
|
+
class S043Analyzer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
14
|
+
console.log(`🔧 [S043] Constructor called with options:`, !!options);
|
|
15
|
+
console.log(
|
|
16
|
+
`🔧 [S043] Options type:`,
|
|
17
|
+
typeof options,
|
|
18
|
+
Object.keys(options || {})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.ruleId = "S043";
|
|
23
|
+
this.ruleName = "Password changes must invalidate all other login sessions";
|
|
24
|
+
this.description =
|
|
25
|
+
"Ensure attackers cannot continue using old session tokens after a password change. Enforce correct access control after sensitive updates.";
|
|
26
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
27
|
+
this.verbose = options.verbose || false;
|
|
28
|
+
|
|
29
|
+
this.config = {
|
|
30
|
+
useSymbolBased: true,
|
|
31
|
+
fallbackToRegex: false,
|
|
32
|
+
regexBasedOnly: false,
|
|
33
|
+
fallbackToSymbol: true, // Allow symbol analysis even without semantic engine
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
this.symbolAnalyzer = new S043SymbolBasedAnalyzer(this.semanticEngine);
|
|
38
|
+
if (process.env.SUNLINT_DEBUG)
|
|
39
|
+
console.log(`🔧 [S043] Symbol analyzer created successfully`);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`🔧 [S043] Error creating symbol analyzer:`, error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async initialize(semanticEngine = null) {
|
|
46
|
+
if (semanticEngine) {
|
|
47
|
+
this.semanticEngine = semanticEngine;
|
|
48
|
+
}
|
|
49
|
+
this.verbose = semanticEngine?.verbose || false;
|
|
50
|
+
|
|
51
|
+
// Initialize both analyzers
|
|
52
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
53
|
+
|
|
54
|
+
// Ensure verbose flag is propagated
|
|
55
|
+
this.symbolAnalyzer.verbose = this.verbose;
|
|
56
|
+
|
|
57
|
+
if (this.verbose) {
|
|
58
|
+
console.log(`🔧 [S043 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
analyzeSingle(filePath, options = {}) {
|
|
63
|
+
if (process.env.SUNLINT_DEBUG)
|
|
64
|
+
console.log(`🔍 [S043] analyzeSingle() called for: ${filePath}`);
|
|
65
|
+
return this.analyze([filePath], "typescript", options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async analyze(files, language, options = {}) {
|
|
69
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
70
|
+
console.log(`🔧 [S043] analyze() method called with ${files.length} files, language: ${language}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const violations = [];
|
|
74
|
+
|
|
75
|
+
for (const filePath of files) {
|
|
76
|
+
try {
|
|
77
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
78
|
+
console.log(`🔧 [S043] Processing file: ${filePath}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
82
|
+
violations.push(...fileViolations);
|
|
83
|
+
|
|
84
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
85
|
+
console.log(`🔧 [S043] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn(`❌ [S043] Analysis failed for ${filePath}:`, error.message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
93
|
+
console.log(`🔧 [S043] Total violations found: ${violations.length}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return violations;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async analyzeFile(filePath, options = {}) {
|
|
100
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
101
|
+
console.log(`🔧 [S043] analyzeFile() called for: ${filePath}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
105
|
+
if (this.config.useSymbolBased &&
|
|
106
|
+
this.semanticEngine?.project &&
|
|
107
|
+
this.semanticEngine?.initialized) {
|
|
108
|
+
try {
|
|
109
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
110
|
+
console.log(`🔧 [S043] Trying symbol-based analysis...`);
|
|
111
|
+
}
|
|
112
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
113
|
+
if (sourceFile) {
|
|
114
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
115
|
+
console.log(`🔧 [S043] Source file found, analyzing with symbol-based...`);
|
|
116
|
+
}
|
|
117
|
+
const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
|
|
118
|
+
|
|
119
|
+
// Mark violations with analysis strategy
|
|
120
|
+
violations.forEach(v => v.analysisStrategy = 'symbol-based');
|
|
121
|
+
|
|
122
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
123
|
+
console.log(`✅ [S043] Symbol-based analysis: ${violations.length} violations`);
|
|
124
|
+
}
|
|
125
|
+
return violations; // Return even if 0 violations - symbol analysis completed successfully
|
|
126
|
+
} else {
|
|
127
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
128
|
+
console.log(`⚠️ [S043] Source file not found in project`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.warn(`⚠️ [S043] Symbol analysis failed: ${error.message}`);
|
|
133
|
+
// Continue to fallback
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
137
|
+
console.log(`🔄 [S043] Symbol analysis conditions check:`);
|
|
138
|
+
console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
|
|
139
|
+
console.log(` - semanticEngine: ${!!this.semanticEngine}`);
|
|
140
|
+
console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
|
|
141
|
+
console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
|
|
142
|
+
console.log(`🔄 [S043] Symbol analysis unavailable, using regex fallback`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (options?.verbose) {
|
|
147
|
+
console.log(`🔧 [S043] No analysis methods succeeded, returning empty`);
|
|
148
|
+
}
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = S043Analyzer;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S043",
|
|
3
|
+
"name": "Password changes must invalidate all other login sessions",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"description": "S043 - Ensure attackers cannot continue using old session tokens after a password change. Enforce correct access control after sensitive updates.",
|
|
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": 1,
|
|
29
|
+
"timeout": 4000
|
|
30
|
+
},
|
|
31
|
+
"validation": {
|
|
32
|
+
"httpIndicators": [
|
|
33
|
+
"http://",
|
|
34
|
+
"require('http')",
|
|
35
|
+
"require(\"http\")",
|
|
36
|
+
"http.createServer"
|
|
37
|
+
],
|
|
38
|
+
"httpsModules": ["https", "tls"],
|
|
39
|
+
"frameworks": ["express", "nextjs", "nuxtjs", "nestjs"]
|
|
40
|
+
}
|
|
41
|
+
}
|