@sun-asterisk/sunlint 1.3.2 → 1.3.4
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/CHANGELOG.md +73 -0
- package/README.md +5 -3
- package/config/rules/enhanced-rules-registry.json +144 -33
- package/core/analysis-orchestrator.js +173 -42
- package/core/auto-performance-manager.js +243 -0
- package/core/cli-action-handler.js +24 -2
- package/core/cli-program.js +19 -5
- package/core/constants/defaults.js +56 -0
- package/core/performance-optimizer.js +271 -0
- package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
- package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
- package/docs/PERFORMANCE.md +311 -0
- package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
- package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
- package/docs/QUICK_FILE_LIMITS.md +64 -0
- package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
- package/engines/engine-factory.js +7 -0
- package/engines/heuristic-engine.js +182 -5
- package/package.json +2 -1
- package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
- package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
- package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
- package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
- package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
- package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
- package/rules/index.js +2 -0
- package/rules/security/S017_use_parameterized_queries/README.md +128 -0
- package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
- package/rules/security/S017_use_parameterized_queries/config.json +109 -0
- package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
- package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
- package/rules/security/S031_secure_session_cookies/README.md +127 -0
- package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
- package/rules/security/S031_secure_session_cookies/config.json +86 -0
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
- package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
- package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
- package/rules/security/S032_httponly_session_cookies/README.md +184 -0
- package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
- package/rules/security/S032_httponly_session_cookies/config.json +96 -0
- package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
- package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
- package/rules/security/S033_samesite_session_cookies/README.md +227 -0
- package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
- package/rules/security/S033_samesite_session_cookies/config.json +87 -0
- package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
- package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
- package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
- package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
- package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
- package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
- package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
- package/rules/security/S035_path_session_cookies/README.md +257 -0
- package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
- package/rules/security/S035_path_session_cookies/config.json +99 -0
- package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
- package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
- package/scripts/batch-processing-demo.js +334 -0
- package/scripts/performance-test.js +541 -0
- package/scripts/quick-performance-test.js +108 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# S031 - Set Secure flag for Session Cookies
|
|
2
|
+
|
|
3
|
+
## Rule Description
|
|
4
|
+
|
|
5
|
+
**S031** ensures that session cookies have the `Secure` flag set to protect them via HTTPS. This prevents cookies from being transmitted over unencrypted connections, reducing the risk of session hijacking and cookie interception.
|
|
6
|
+
|
|
7
|
+
## Security Impact
|
|
8
|
+
|
|
9
|
+
- **High**: Session cookies without Secure flag can be intercepted over HTTP
|
|
10
|
+
- **Attack Vector**: Man-in-the-middle attacks, network sniffing
|
|
11
|
+
- **Compliance**: OWASP Top 10, PCI DSS requirements
|
|
12
|
+
|
|
13
|
+
## Detection Patterns
|
|
14
|
+
|
|
15
|
+
### Vulnerable Code Examples
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// ❌ Express.js - Missing Secure flag
|
|
19
|
+
res.cookie("sessionid", sessionValue, {
|
|
20
|
+
httpOnly: true,
|
|
21
|
+
// Missing: secure: true
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ❌ Set-Cookie header - No Secure flag
|
|
25
|
+
res.setHeader("Set-Cookie", "sessionid=abc123; HttpOnly");
|
|
26
|
+
|
|
27
|
+
// ❌ Document.cookie - Insecure assignment
|
|
28
|
+
document.cookie = "auth=token123; path=/";
|
|
29
|
+
|
|
30
|
+
// ❌ Session middleware - Missing security
|
|
31
|
+
app.use(
|
|
32
|
+
session({
|
|
33
|
+
secret: "secret-key",
|
|
34
|
+
name: "sessionid",
|
|
35
|
+
// Missing: cookie: { secure: true }
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Secure Code Examples
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// ✅ Express.js - With Secure flag
|
|
44
|
+
res.cookie("sessionid", sessionValue, {
|
|
45
|
+
httpOnly: true,
|
|
46
|
+
secure: true,
|
|
47
|
+
sameSite: "strict",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ✅ Set-Cookie header - With Secure flag
|
|
51
|
+
res.setHeader("Set-Cookie", "sessionid=abc123; HttpOnly; Secure");
|
|
52
|
+
|
|
53
|
+
// ✅ Session middleware - Secure configuration
|
|
54
|
+
app.use(
|
|
55
|
+
session({
|
|
56
|
+
secret: "secret-key",
|
|
57
|
+
name: "sessionid",
|
|
58
|
+
cookie: {
|
|
59
|
+
secure: true,
|
|
60
|
+
httpOnly: true,
|
|
61
|
+
sameSite: "strict",
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// ✅ Conditional Secure flag based on environment
|
|
67
|
+
res.cookie("sessionid", sessionValue, {
|
|
68
|
+
httpOnly: true,
|
|
69
|
+
secure: process.env.NODE_ENV === "production",
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Session Cookie Indicators
|
|
74
|
+
|
|
75
|
+
The rule detects cookies that are likely session-related based on:
|
|
76
|
+
|
|
77
|
+
- **Cookie Names**: `session`, `sessionid`, `sessid`, `jsessionid`, `phpsessid`
|
|
78
|
+
- **Framework Patterns**: `connect.sid`, `asp.net_sessionid`
|
|
79
|
+
- **Authentication**: `auth`, `token`, `jwt`, `csrf`
|
|
80
|
+
|
|
81
|
+
## Supported Frameworks
|
|
82
|
+
|
|
83
|
+
- **Express.js**: `res.cookie()`, `res.setHeader()`
|
|
84
|
+
- **Koa**: Cookie setting methods
|
|
85
|
+
- **Fastify**: Cookie plugins
|
|
86
|
+
- **Next.js**: API routes cookie handling
|
|
87
|
+
- **Native**: `document.cookie`, Set-Cookie headers
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
The rule can be configured in `config.json`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"validation": {
|
|
96
|
+
"sessionIndicators": ["session", "sessionid", "auth", "token"],
|
|
97
|
+
"cookieMethods": ["setCookie", "cookie", "setHeader"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Best Practices
|
|
103
|
+
|
|
104
|
+
1. **Always use Secure flag in production**
|
|
105
|
+
2. **Combine with HttpOnly flag** to prevent XSS access
|
|
106
|
+
3. **Use SameSite attribute** for CSRF protection
|
|
107
|
+
4. **Conditional setting** based on environment:
|
|
108
|
+
```javascript
|
|
109
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
110
|
+
res.cookie("session", value, {
|
|
111
|
+
secure: isProduction,
|
|
112
|
+
httpOnly: true,
|
|
113
|
+
sameSite: "strict",
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Related Rules
|
|
118
|
+
|
|
119
|
+
- **S032**: HttpOnly flag for cookies
|
|
120
|
+
- **S033**: SameSite attribute for CSRF protection
|
|
121
|
+
- **S034**: Cookie expiration and domain settings
|
|
122
|
+
|
|
123
|
+
## References
|
|
124
|
+
|
|
125
|
+
- [OWASP Session Management](https://owasp.org/www-project-cheat-sheets/cheatsheets/Session_Management_Cheat_Sheet.html)
|
|
126
|
+
- [MDN Secure Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
|
|
127
|
+
- [RFC 6265 - HTTP State Management](https://tools.ietf.org/html/rfc6265)
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S031 Main Analyzer - Set Secure flag for Session Cookies
|
|
3
|
+
* Primary: Symbol-based analysis (when available)
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* Command: node cli.js --rule=S031 --input=examples/rule-test-fixtures/rules/S031_secure_session_cookies --engine=heuristic
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const S031SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
9
|
+
const S031RegexBasedAnalyzer = require("./regex-based-analyzer.js");
|
|
10
|
+
|
|
11
|
+
class S031Analyzer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
14
|
+
console.log(`🔧 [S031] Constructor called with options:`, !!options);
|
|
15
|
+
console.log(
|
|
16
|
+
`🔧 [S031] Options type:`,
|
|
17
|
+
typeof options,
|
|
18
|
+
Object.keys(options || {})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.ruleId = "S031";
|
|
23
|
+
this.ruleName = "Set Secure flag for Session Cookies";
|
|
24
|
+
this.description =
|
|
25
|
+
"Set Secure flag for Session Cookies to protect via HTTPS. This ensures cookies are only transmitted over secure connections, preventing interception.";
|
|
26
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
27
|
+
this.verbose = options.verbose || false;
|
|
28
|
+
|
|
29
|
+
// Configuration
|
|
30
|
+
this.config = {
|
|
31
|
+
useSymbolBased: true, // Primary approach
|
|
32
|
+
fallbackToRegex: true, // Secondary approach
|
|
33
|
+
regexBasedOnly: false, // Can be set to true for pure mode
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Initialize analyzers
|
|
37
|
+
try {
|
|
38
|
+
this.symbolAnalyzer = new S031SymbolBasedAnalyzer(this.semanticEngine);
|
|
39
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
40
|
+
console.log(`🔧 [S031] Symbol analyzer created successfully`);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`🔧 [S031] Error creating symbol analyzer:`, error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
this.regexAnalyzer = new S031RegexBasedAnalyzer(this.semanticEngine);
|
|
48
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
49
|
+
console.log(`🔧 [S031] Regex analyzer created successfully`);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`🔧 [S031] Error creating regex analyzer:`, error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Initialize analyzer with semantic engine
|
|
58
|
+
|
|
59
|
+
*/
|
|
60
|
+
async initialize(semanticEngine) {
|
|
61
|
+
this.semanticEngine = semanticEngine;
|
|
62
|
+
|
|
63
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
64
|
+
console.log(`🔧 [S031] Main analyzer initializing...`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Initialize both analyzers
|
|
68
|
+
if (this.symbolAnalyzer) {
|
|
69
|
+
await this.symbolAnalyzer.initialize?.(semanticEngine);
|
|
70
|
+
}
|
|
71
|
+
if (this.regexAnalyzer) {
|
|
72
|
+
await this.regexAnalyzer.initialize?.(semanticEngine);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Clean up if needed
|
|
76
|
+
if (this.regexAnalyzer) {
|
|
77
|
+
this.regexAnalyzer.cleanup?.();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
81
|
+
console.log(`🔧 [S031] Main analyzer initialized successfully`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Single file analysis method for testing
|
|
87
|
+
|
|
88
|
+
*/
|
|
89
|
+
analyzeSingle(filePath, options = {}) {
|
|
90
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
91
|
+
console.log(`🔍 [S031] analyzeSingle() called for: ${filePath}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Return result using same format as analyze method
|
|
95
|
+
return this.analyze([filePath], "typescript", options);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async analyze(files, language, options = {}) {
|
|
99
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
100
|
+
console.log(
|
|
101
|
+
`🔧 [S031] analyze() method called with ${files.length} files, language: ${language}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const violations = [];
|
|
106
|
+
|
|
107
|
+
for (const filePath of files) {
|
|
108
|
+
try {
|
|
109
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
110
|
+
console.log(`🔧 [S031] Processing file: ${filePath}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
114
|
+
violations.push(...fileViolations);
|
|
115
|
+
|
|
116
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
117
|
+
console.log(
|
|
118
|
+
`🔧 [S031] File ${filePath}: Found ${fileViolations.length} violations`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.warn(
|
|
123
|
+
`⚠ [S031] Analysis failed for ${filePath}:`,
|
|
124
|
+
error.message
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
130
|
+
console.log(`🔧 [S031] Total violations found: ${violations.length}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return violations;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async analyzeFile(filePath, options = {}) {
|
|
137
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
138
|
+
console.log(`🔍 [S031] analyzeFile() called for: ${filePath}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create a Map to track unique violations and prevent duplicates
|
|
142
|
+
const violationMap = new Map();
|
|
143
|
+
|
|
144
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
145
|
+
if (
|
|
146
|
+
this.config.useSymbolBased &&
|
|
147
|
+
this.semanticEngine?.project &&
|
|
148
|
+
this.semanticEngine?.initialized
|
|
149
|
+
) {
|
|
150
|
+
try {
|
|
151
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
152
|
+
console.log(`🔧 [S031] Trying symbol-based analysis...`);
|
|
153
|
+
}
|
|
154
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
155
|
+
if (sourceFile) {
|
|
156
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
157
|
+
console.log(`🔧 [S031] Source file found, analyzing...`);
|
|
158
|
+
}
|
|
159
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
160
|
+
sourceFile,
|
|
161
|
+
filePath
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Add to violation map with deduplication
|
|
165
|
+
symbolViolations.forEach((violation) => {
|
|
166
|
+
const key = `${violation.line}:${violation.column}:${violation.message}`;
|
|
167
|
+
if (!violationMap.has(key)) {
|
|
168
|
+
violationMap.set(key, violation);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
173
|
+
console.log(
|
|
174
|
+
`🔧 [S031] Symbol analysis completed: ${symbolViolations.length} violations`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
179
|
+
console.log(`🔧 [S031] Source file not found, falling back...`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.warn(`⚠ [S031] Symbol analysis failed:`, error.message);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 2. Try Regex-based analysis (fallback or additional)
|
|
188
|
+
if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
|
|
189
|
+
try {
|
|
190
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
191
|
+
console.log(`🔧 [S031] Trying regex-based analysis...`);
|
|
192
|
+
}
|
|
193
|
+
const regexViolations = await this.regexAnalyzer.analyze(filePath);
|
|
194
|
+
|
|
195
|
+
// Add to violation map with deduplication
|
|
196
|
+
regexViolations.forEach((violation) => {
|
|
197
|
+
const key = `${violation.line}:${violation.column}:${violation.message}`;
|
|
198
|
+
if (!violationMap.has(key)) {
|
|
199
|
+
violationMap.set(key, violation);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
204
|
+
console.log(
|
|
205
|
+
`🔧 [S031] Regex analysis completed: ${regexViolations.length} violations`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.warn(`⚠ [S031] Regex analysis failed:`, error.message);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Convert Map values to array and add filePath to each violation
|
|
214
|
+
const finalViolations = Array.from(violationMap.values()).map(
|
|
215
|
+
(violation) => ({
|
|
216
|
+
...violation,
|
|
217
|
+
filePath: filePath,
|
|
218
|
+
file: filePath, // Also add 'file' for compatibility
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
223
|
+
console.log(
|
|
224
|
+
`🔧 [S031] File analysis completed: ${finalViolations.length} unique violations`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return finalViolations;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Clean up resources
|
|
233
|
+
|
|
234
|
+
*/
|
|
235
|
+
cleanup() {
|
|
236
|
+
if (this.symbolAnalyzer?.cleanup) {
|
|
237
|
+
this.symbolAnalyzer.cleanup();
|
|
238
|
+
}
|
|
239
|
+
if (this.regexAnalyzer?.cleanup) {
|
|
240
|
+
this.regexAnalyzer.cleanup();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = S031Analyzer;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S031",
|
|
3
|
+
"name": "Set Secure flag for Session Cookies",
|
|
4
|
+
"category": "security",
|
|
5
|
+
"description": "S031 - Set Secure flag for Session Cookies to protect via HTTPS. This ensures cookies are only transmitted over secure connections, preventing interception.",
|
|
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": 2,
|
|
29
|
+
"timeout": 5000
|
|
30
|
+
},
|
|
31
|
+
"validation": {
|
|
32
|
+
"cookieMethods": [
|
|
33
|
+
"setCookie",
|
|
34
|
+
"cookie",
|
|
35
|
+
"set",
|
|
36
|
+
"append",
|
|
37
|
+
"session",
|
|
38
|
+
"setHeader",
|
|
39
|
+
"writeHead"
|
|
40
|
+
],
|
|
41
|
+
"cookieLibraries": [
|
|
42
|
+
"express",
|
|
43
|
+
"koa",
|
|
44
|
+
"fastify",
|
|
45
|
+
"hapi",
|
|
46
|
+
"next",
|
|
47
|
+
"nuxt",
|
|
48
|
+
"cookie",
|
|
49
|
+
"cookie-parser",
|
|
50
|
+
"express-session",
|
|
51
|
+
"connect-session",
|
|
52
|
+
"passport"
|
|
53
|
+
],
|
|
54
|
+
"sessionIndicators": [
|
|
55
|
+
"session",
|
|
56
|
+
"sessionid",
|
|
57
|
+
"sessid",
|
|
58
|
+
"jsessionid",
|
|
59
|
+
"phpsessid",
|
|
60
|
+
"asp.net_sessionid",
|
|
61
|
+
"connect.sid",
|
|
62
|
+
"auth",
|
|
63
|
+
"token",
|
|
64
|
+
"jwt",
|
|
65
|
+
"csrf"
|
|
66
|
+
],
|
|
67
|
+
"securePatterns": [
|
|
68
|
+
"secure:\\s*true",
|
|
69
|
+
"secure:true",
|
|
70
|
+
"Secure",
|
|
71
|
+
"secure=true",
|
|
72
|
+
"httpOnly:\\s*true",
|
|
73
|
+
"httpOnly:true",
|
|
74
|
+
"HttpOnly",
|
|
75
|
+
"httpOnly=true"
|
|
76
|
+
],
|
|
77
|
+
"insecurePatterns": [
|
|
78
|
+
"secure:\\s*false",
|
|
79
|
+
"secure:false",
|
|
80
|
+
"secure=false",
|
|
81
|
+
"(?<!secure[\\s=:]+)(?<!Secure[\\s;])Set-Cookie",
|
|
82
|
+
"res\\.cookie\\([^)]*\\)(?![^{]*secure)",
|
|
83
|
+
"document\\.cookie\\s*="
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S031 Regex-Based Analyzer - Set Secure flag for Session Cookies
|
|
3
|
+
* Fallback analysis using regex patterns
|
|
4
|
+
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
|
|
9
|
+
class S031RegexBasedAnalyzer {
|
|
10
|
+
constructor(semanticEngine = null) {
|
|
11
|
+
this.semanticEngine = semanticEngine;
|
|
12
|
+
this.ruleId = "S031";
|
|
13
|
+
this.category = "security";
|
|
14
|
+
|
|
15
|
+
// Session cookie indicators
|
|
16
|
+
this.sessionIndicators = [
|
|
17
|
+
"session",
|
|
18
|
+
"sessionid",
|
|
19
|
+
"sessid",
|
|
20
|
+
"jsessionid",
|
|
21
|
+
"phpsessid",
|
|
22
|
+
"asp.net_sessionid",
|
|
23
|
+
"connect.sid",
|
|
24
|
+
"auth",
|
|
25
|
+
"token",
|
|
26
|
+
"jwt",
|
|
27
|
+
"csrf",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// Regex patterns for cookie detection
|
|
31
|
+
this.cookiePatterns = [
|
|
32
|
+
// Express/Node.js patterns
|
|
33
|
+
/res\.cookie\s*\(\s*['"`]([^'"`]+)['"`]\s*,([^)]+)\)/gi,
|
|
34
|
+
/response\.cookie\s*\(\s*['"`]([^'"`]+)['"`]\s*,([^)]+)\)/gi,
|
|
35
|
+
/\.setCookie\s*\(\s*['"`]([^'"`]+)['"`]\s*,([^)]+)\)/gi,
|
|
36
|
+
|
|
37
|
+
// Set-Cookie header patterns
|
|
38
|
+
/setHeader\s*\(\s*['"`]Set-Cookie['"`]\s*,\s*['"`]([^'"`]+)['"`]\s*\)/gi,
|
|
39
|
+
/writeHead\s*\([^,]*,\s*{[^}]*['"`]Set-Cookie['"`]\s*:\s*['"`]([^'"`]+)['"`]/gi,
|
|
40
|
+
|
|
41
|
+
// Document.cookie assignments
|
|
42
|
+
/document\.cookie\s*=\s*['"`]([^'"`]+)['"`]/gi,
|
|
43
|
+
|
|
44
|
+
// Session middleware patterns
|
|
45
|
+
/session\s*\(\s*{([^}]+)}/gi,
|
|
46
|
+
/\.use\s*\(\s*session\s*\(\s*{([^}]+)}/gi,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Initialize analyzer
|
|
52
|
+
|
|
53
|
+
*/
|
|
54
|
+
async initialize(semanticEngine) {
|
|
55
|
+
this.semanticEngine = semanticEngine;
|
|
56
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
57
|
+
console.log(`🔧 [S031] Regex-based analyzer initialized`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Analyze file content using regex patterns
|
|
63
|
+
|
|
64
|
+
*/
|
|
65
|
+
async analyze(filePath) {
|
|
66
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
67
|
+
console.log(`🔍 [S031] Regex-based analysis for: ${filePath}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let content;
|
|
71
|
+
try {
|
|
72
|
+
content = fs.readFileSync(filePath, "utf8");
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
75
|
+
console.error(`❌ [S031] File read error:`, error);
|
|
76
|
+
}
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const violations = [];
|
|
81
|
+
const lines = content.split("\n");
|
|
82
|
+
|
|
83
|
+
// Check each pattern
|
|
84
|
+
for (const pattern of this.cookiePatterns) {
|
|
85
|
+
this.checkPattern(pattern, content, lines, violations, filePath);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return violations;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check specific regex pattern for violations
|
|
93
|
+
|
|
94
|
+
*/
|
|
95
|
+
checkPattern(pattern, content, lines, violations, filePath) {
|
|
96
|
+
let match;
|
|
97
|
+
pattern.lastIndex = 0; // Reset regex state
|
|
98
|
+
|
|
99
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
100
|
+
const matchText = match[0];
|
|
101
|
+
const cookieName = match[1] || "";
|
|
102
|
+
const cookieOptions = match[2] || match[1] || "";
|
|
103
|
+
|
|
104
|
+
// Check if this is a session cookie
|
|
105
|
+
if (!this.isSessionCookie(cookieName, matchText)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if secure flag is present
|
|
110
|
+
if (!this.hasSecureFlag(cookieOptions, matchText)) {
|
|
111
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
112
|
+
|
|
113
|
+
this.addViolation(
|
|
114
|
+
matchText,
|
|
115
|
+
lineNumber,
|
|
116
|
+
violations,
|
|
117
|
+
`Session cookie "${cookieName || "unknown"}" missing Secure flag`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if cookie name or context indicates session cookie
|
|
125
|
+
|
|
126
|
+
*/
|
|
127
|
+
isSessionCookie(cookieName, matchText) {
|
|
128
|
+
const textToCheck = (cookieName + " " + matchText).toLowerCase();
|
|
129
|
+
return this.sessionIndicators.some((indicator) =>
|
|
130
|
+
textToCheck.includes(indicator.toLowerCase())
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if secure flag is present in cookie options
|
|
136
|
+
*/
|
|
137
|
+
hasSecureFlag(cookieOptions, fullMatch) {
|
|
138
|
+
const textToCheck = cookieOptions + " " + fullMatch;
|
|
139
|
+
|
|
140
|
+
// Check for secure config references (likely safe)
|
|
141
|
+
const secureConfigPatterns = [
|
|
142
|
+
/\bcookieConfig\b/i,
|
|
143
|
+
/\bsecureConfig\b/i,
|
|
144
|
+
/\bsafeConfig\b/i,
|
|
145
|
+
/\bdefaultConfig\b/i,
|
|
146
|
+
/\.\.\..*config/i, // spread operator with config
|
|
147
|
+
/config.*secure/i,
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
// If using a secure config reference, assume it's safe
|
|
151
|
+
if (secureConfigPatterns.some((pattern) => pattern.test(textToCheck))) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for various secure flag patterns
|
|
156
|
+
const securePatterns = [
|
|
157
|
+
/secure\s*:\s*true/i,
|
|
158
|
+
/secure\s*=\s*true/i,
|
|
159
|
+
/;\s*secure\s*[;\s]/i,
|
|
160
|
+
/;\s*secure$/i,
|
|
161
|
+
/['"`]\s*secure\s*['"`]/i,
|
|
162
|
+
/"secure"\s*:\s*true/i,
|
|
163
|
+
/'secure'\s*:\s*true/i,
|
|
164
|
+
/\bsecure\b/i, // Simple secure keyword
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
return securePatterns.some((pattern) => pattern.test(textToCheck));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get line number from content position
|
|
172
|
+
|
|
173
|
+
*/
|
|
174
|
+
getLineNumber(content, position) {
|
|
175
|
+
const beforeMatch = content.substring(0, position);
|
|
176
|
+
return beforeMatch.split("\n").length;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Add violation to results
|
|
181
|
+
|
|
182
|
+
*/
|
|
183
|
+
addViolation(source, lineNumber, violations, message) {
|
|
184
|
+
violations.push({
|
|
185
|
+
ruleId: this.ruleId,
|
|
186
|
+
source: source.trim(),
|
|
187
|
+
category: this.category,
|
|
188
|
+
line: lineNumber,
|
|
189
|
+
column: 1,
|
|
190
|
+
message: `Insecure session cookie: ${message}`,
|
|
191
|
+
severity: "error",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = S031RegexBasedAnalyzer;
|