@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,153 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
class S040Analyzer {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.ruleId = 'S040';
|
|
6
|
+
this.ruleName = 'Session Fixation Protection';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async analyze(files, language, options = {}) {
|
|
10
|
+
const violations = [];
|
|
11
|
+
|
|
12
|
+
for (const filePath of files) {
|
|
13
|
+
// Skip test files
|
|
14
|
+
if (this.isTestFile(filePath)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
20
|
+
const fileViolations = this.analyzeFile(content, filePath);
|
|
21
|
+
violations.push(...fileViolations);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (options.verbose) {
|
|
24
|
+
console.warn(`S040: Error analyzing ${filePath}:`, error.message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return violations;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
isTestFile(filePath) {
|
|
33
|
+
const testPatterns = [
|
|
34
|
+
/\.test\.(ts|tsx|js|jsx|php|py)$/,
|
|
35
|
+
/\.spec\.(ts|tsx|js|jsx|php|py)$/,
|
|
36
|
+
/__tests__\//,
|
|
37
|
+
/__mocks__\//,
|
|
38
|
+
/\/tests?\//,
|
|
39
|
+
];
|
|
40
|
+
return testPatterns.some(p => p.test(filePath));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
analyzeFile(content, filePath) {
|
|
44
|
+
const violations = [];
|
|
45
|
+
|
|
46
|
+
// Find login/authentication functions
|
|
47
|
+
const loginFunctions = this.findLoginFunctions(content);
|
|
48
|
+
|
|
49
|
+
loginFunctions.forEach(({ startLine, endLine, functionContent, functionName }) => {
|
|
50
|
+
// Check if session is regenerated
|
|
51
|
+
if (!this.hasSessionRegeneration(functionContent)) {
|
|
52
|
+
violations.push({
|
|
53
|
+
file: filePath,
|
|
54
|
+
line: startLine,
|
|
55
|
+
column: 1,
|
|
56
|
+
message: `Login function '${functionName}' does not regenerate session - vulnerable to Session Fixation. Use session.regenerate(), req.session.regenerate(), or create new JWT token.`,
|
|
57
|
+
severity: 2, // error
|
|
58
|
+
ruleId: this.ruleId,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return violations;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
findLoginFunctions(content) {
|
|
67
|
+
const functions = [];
|
|
68
|
+
const lines = content.split(/\r?\n/);
|
|
69
|
+
|
|
70
|
+
// Patterns that indicate login/authentication functions
|
|
71
|
+
const loginPatterns = [
|
|
72
|
+
/(?:async\s+)?(?:function\s+)?(\w*login\w*|authenticate\w*|signin\w*)\s*\(/gi,
|
|
73
|
+
/(?:const|let|var)\s+(\w*login\w*|authenticate\w*|signin\w*)\s*=\s*(?:async\s+)?\(/gi,
|
|
74
|
+
/@Post\s*\(\s*['"`]\/?(login|signin|authenticate)/gi,
|
|
75
|
+
/router\.\w+\s*\(\s*['"`]\/?(login|signin|authenticate)/gi,
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
let currentFunction = null;
|
|
79
|
+
let braceCount = 0;
|
|
80
|
+
|
|
81
|
+
lines.forEach((line, index) => {
|
|
82
|
+
// Check if this line starts a login function
|
|
83
|
+
if (!currentFunction) {
|
|
84
|
+
loginPatterns.forEach(pattern => {
|
|
85
|
+
const match = line.match(pattern);
|
|
86
|
+
if (match) {
|
|
87
|
+
currentFunction = {
|
|
88
|
+
startLine: index + 1,
|
|
89
|
+
functionName: match[1] || 'anonymous',
|
|
90
|
+
functionContent: '',
|
|
91
|
+
};
|
|
92
|
+
braceCount = 0;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (currentFunction) {
|
|
98
|
+
currentFunction.functionContent += line + '\n';
|
|
99
|
+
|
|
100
|
+
// Count braces to find function end
|
|
101
|
+
braceCount += (line.match(/\{/g) || []).length;
|
|
102
|
+
braceCount -= (line.match(/\}/g) || []).length;
|
|
103
|
+
|
|
104
|
+
if (braceCount === 0 && currentFunction.functionContent.includes('{')) {
|
|
105
|
+
currentFunction.endLine = index + 1;
|
|
106
|
+
functions.push(currentFunction);
|
|
107
|
+
currentFunction = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return functions;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
hasSessionRegeneration(functionContent) {
|
|
116
|
+
// Patterns that indicate session regeneration
|
|
117
|
+
const regenerationPatterns = [
|
|
118
|
+
/session\.regenerate\s*\(/,
|
|
119
|
+
/req\.session\.regenerate\s*\(/,
|
|
120
|
+
/request\.session\.regenerate\s*\(/,
|
|
121
|
+
/sessionStore\.regenerate\s*\(/,
|
|
122
|
+
|
|
123
|
+
// Express-session
|
|
124
|
+
/session\.destroy.*session\.save/s,
|
|
125
|
+
/req\.session\.destroy.*new\s+session/s,
|
|
126
|
+
|
|
127
|
+
// JWT token creation (new token after login)
|
|
128
|
+
/jwt\.sign\s*\(/,
|
|
129
|
+
/generateToken\s*\(/,
|
|
130
|
+
/createToken\s*\(/,
|
|
131
|
+
/signToken\s*\(/,
|
|
132
|
+
|
|
133
|
+
// PHP session regeneration
|
|
134
|
+
/session_regenerate_id\s*\(/,
|
|
135
|
+
/session_destroy.*session_start/s,
|
|
136
|
+
|
|
137
|
+
// Python Flask/Django
|
|
138
|
+
/session\.regenerate\s*\(/,
|
|
139
|
+
/session\.clear\s*\(.*session\[/s,
|
|
140
|
+
/login_user.*renew=True/,
|
|
141
|
+
|
|
142
|
+
// Passport.js (implicitly regenerates)
|
|
143
|
+
/passport\.authenticate/,
|
|
144
|
+
/req\.login\s*\(/,
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
return regenerationPatterns.some(pattern => pattern.test(functionContent));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
cleanup() {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = S040Analyzer;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S040",
|
|
3
|
+
"name": "Session Fixation Protection - Regenerate Session on Login",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"description": "S040 - Prevent Session Fixation attacks by regenerating session tokens after successful authentication.",
|
|
6
|
+
"severity": "error",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"patterns": {
|
|
9
|
+
"include": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx", "**/*.php", "**/*.py"],
|
|
10
|
+
"exclude": [
|
|
11
|
+
"**/*.test.*",
|
|
12
|
+
"**/*.spec.*",
|
|
13
|
+
"__tests__/**",
|
|
14
|
+
"__mocks__/**",
|
|
15
|
+
"**/node_modules/**",
|
|
16
|
+
"**/dist/**",
|
|
17
|
+
"**/build/**"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
## Rule C042
|
|
2
|
+
**Title:** Require re-authentication for long-lived sessions or sensitive actions
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
### Objective
|
|
7
|
+
Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.
|
|
8
|
+
|
|
9
|
+
### Details
|
|
10
|
+
- **Persistent Login or "Remember Me":**
|
|
11
|
+
- Require re-login after X hours (e.g., 12h, 24h)
|
|
12
|
+
- Re-authenticate after inactivity (e.g., 30 mins)
|
|
13
|
+
- Require password or 2FA for sensitive actions (password change, payments)
|
|
14
|
+
- **For JWT:**
|
|
15
|
+
- Use short-lived tokens with secure refresh logic
|
|
16
|
+
- **Best Practice:**
|
|
17
|
+
- Do not flag as a violation if re-authentication logic exists in downstream service/common methods even when the immediate controller/service does not show it directly.
|
|
18
|
+
|
|
19
|
+
### Applies to
|
|
20
|
+
- All programming languages
|
|
21
|
+
- Critical for backend/auth REST API development
|
|
22
|
+
|
|
23
|
+
### Detection
|
|
24
|
+
- Detect session configs with excessive lifetime (e.g., session maxAge > 24h, JWT expiresIn > 1h for access tokens)
|
|
25
|
+
- Detect missing re-authentication check on sensitive actions (by function name or endpoint path)
|
|
26
|
+
- Should NOT flag data objects (e.g., session DB records with expiresAt)
|
|
27
|
+
- Should follow method call chains to detect delegated re-authentication (service/deep method etc.)
|
|
28
|
+
|
|
29
|
+
### Tools/Validation
|
|
30
|
+
- Manual code review
|
|
31
|
+
- Static analysis (for JWT expiry, session policy)
|
|
32
|
+
- Security functional tests
|
|
33
|
+
- SonarQube (custom rule integration)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### Example: Clean vs Violation
|
|
38
|
+
|
|
39
|
+
#### Clean (no violation)
|
|
40
|
+
```typescript
|
|
41
|
+
async changePassword(userId: string, currentPassword: string, newPassword: string) {
|
|
42
|
+
// Re-authentication logic via password verification
|
|
43
|
+
const user = await this.userRepository.findById(userId);
|
|
44
|
+
if (!await bcrypt.compare(currentPassword, user.passwordHash)) {
|
|
45
|
+
throw new UnauthorizedException();
|
|
46
|
+
}
|
|
47
|
+
// ...
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Violation (should be flagged)
|
|
52
|
+
```typescript
|
|
53
|
+
async changePassword(userId: string, newPassword: string) {
|
|
54
|
+
// No password verification
|
|
55
|
+
await this.userRepository.updatePassword(userId, newPassword);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Violation (configuration)
|
|
60
|
+
```js
|
|
61
|
+
app.use(session({ cookie: { maxAge: 90 * 24 * 60 * 60 * 1000 } })); // 90 days
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### Resources
|
|
67
|
+
- [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
|
|
68
|
+
- [JWT Best Practices](https://auth0.com/blog/jwt-best-practices/)
|
|
69
|
+
|
|
70
|
+
### Severity
|
|
71
|
+
Medium
|
|
72
|
+
|
|
73
|
+
### Status
|
|
74
|
+
Activated
|
|
75
|
+
|
|
76
|
+
### Version
|
|
77
|
+
1.0
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### Labels
|
|
82
|
+
- Security Custom Rules
|
|
83
|
+
- documentation
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S042 Require re-authentication for long-lived sessions or sensitive actions
|
|
3
|
+
* Primary: Symbol-based analysis (when available)
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* Purpose: Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.
|
|
6
|
+
* Command: node cli.js --rule=S042 --input=examples/rule-test-fixtures/rules/S042_require_re_authentication_for_long_lived --engine=heuristic --verbose
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const S042SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
10
|
+
|
|
11
|
+
class S042Analyzer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
14
|
+
console.log(`🔧 [S042] Constructor called with options:`, !!options);
|
|
15
|
+
console.log(
|
|
16
|
+
`🔧 [S042] Options type:`,
|
|
17
|
+
typeof options,
|
|
18
|
+
Object.keys(options || {})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.ruleId = "S042";
|
|
23
|
+
this.ruleName = "Require re-authentication for long-lived sessions or sensitive actions";
|
|
24
|
+
this.description =
|
|
25
|
+
"Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.";
|
|
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 S042SymbolBasedAnalyzer(this.semanticEngine);
|
|
38
|
+
if (process.env.SUNLINT_DEBUG)
|
|
39
|
+
console.log(`🔧 [S042] Symbol analyzer created successfully`);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`🔧 [S042] 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(`🔧 [S042 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
analyzeSingle(filePath, options = {}) {
|
|
63
|
+
if (process.env.SUNLINT_DEBUG)
|
|
64
|
+
console.log(`🔍 [S042] 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(`🔧 [S042] 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(`🔧 [S042] 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(`🔧 [S042] File ${filePath}: Found ${fileViolations.length} violations`);
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn(`❌ [S042] Analysis failed for ${filePath}:`, error.message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
93
|
+
console.log(`🔧 [S042] 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(`🔧 [S042] 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(`🔧 [S042] 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(`🔧 [S042] 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(`✅ [S042] 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(`⚠️ [S042] Source file not found in project`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.warn(`⚠️ [S042] Symbol analysis failed: ${error.message}`);
|
|
133
|
+
// Continue to fallback
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
137
|
+
console.log(`🔄 [S042] 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(`🔄 [S042] Symbol analysis unavailable, using regex fallback`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (options?.verbose) {
|
|
147
|
+
console.log(`🔧 [S042] No analysis methods succeeded, returning empty`);
|
|
148
|
+
}
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = S042Analyzer;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S042",
|
|
3
|
+
"name": "Require re-authentication for long-lived sessions or sensitive actions",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"description": "S042 - Reduce the risk of session hijacking or privilege misuse by forcing re-authentication after long idle periods or before critical actions.",
|
|
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
|
+
}
|