@sun-asterisk/sunlint 1.3.26 → 1.3.28
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 +101 -17
- 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/C029_catch_block_logging/analyzer.js +47 -12
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +35 -15
- package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +9 -5
- package/rules/security/S003_open_redirect_protection/README.md +371 -0
- package/rules/security/S003_open_redirect_protection/analyzer.js +135 -0
- package/rules/security/S003_open_redirect_protection/config.json +58 -0
- package/rules/security/S003_open_redirect_protection/symbol-based-analyzer.js +884 -0
- package/rules/security/S004_sensitive_data_logging/analyzer.js +135 -0
- package/rules/security/S004_sensitive_data_logging/config.json +62 -0
- package/rules/security/S004_sensitive_data_logging/symbol-based-analyzer.js +592 -0
- 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/S012_hardcoded_secrets/analyzer.js +149 -0
- package/rules/security/S012_hardcoded_secrets/config.json +75 -0
- package/rules/security/S012_hardcoded_secrets/symbol-based-analyzer.js +1204 -0
- 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/S019_smtp_injection_protection/analyzer.js +120 -0
- package/rules/security/S019_smtp_injection_protection/config.json +35 -0
- package/rules/security/S019_smtp_injection_protection/symbol-based-analyzer.js +687 -0
- 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/S022_escape_output_context/README.md +254 -0
- package/rules/security/S022_escape_output_context/analyzer.js +510 -0
- package/rules/security/S022_escape_output_context/config.json +229 -0
- package/rules/security/S023_no_json_injection/analyzer.js +15 -0
- package/rules/security/S023_no_json_injection/ast-analyzer.js +18 -3
- package/rules/security/S023_no_json_injection/config.json +133 -0
- package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +41 -0
- package/rules/security/S027_no_hardcoded_secrets/analyzer.js +67 -8
- package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +29 -6
- package/rules/security/S029_csrf_protection/config.json +127 -0
- package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +160 -28
- package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +81 -19
- package/rules/security/S031_secure_session_cookies/analyzer.js +20 -2
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +100 -0
- package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +8 -1
- package/rules/security/S032_httponly_session_cookies/analyzer.js +2 -2
- package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +115 -0
- package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +39 -10
- package/rules/security/S036_lfi_rfi_protection/analyzer.js +224 -0
- package/rules/security/S036_lfi_rfi_protection/config.json +20 -0
- package/rules/security/S040_session_fixation_protection/analyzer.js +153 -0
- package/rules/security/S040_session_fixation_protection/config.json +20 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/README.md +83 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +153 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/config.json +41 -0
- package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +1139 -0
- 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
|
@@ -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
|
+
}
|