@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,204 @@
|
|
|
1
|
+
# S034: Use \_\_Host- prefix for Session Cookies
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This rule enforces the use of `__Host-` prefix for session cookies to prevent subdomain sharing attacks. The `__Host-` prefix is a security feature that ensures cookies are only sent to the exact domain that set them.
|
|
6
|
+
|
|
7
|
+
## Rule Details
|
|
8
|
+
|
|
9
|
+
**Rule ID**: S034
|
|
10
|
+
**Category**: Security
|
|
11
|
+
**Severity**: Warning
|
|
12
|
+
**Confidence**: High
|
|
13
|
+
|
|
14
|
+
## Description
|
|
15
|
+
|
|
16
|
+
The `__Host-` prefix is a cookie security feature that:
|
|
17
|
+
|
|
18
|
+
- Prevents subdomain cookie sharing
|
|
19
|
+
- Requires the cookie to be secure (HTTPS only)
|
|
20
|
+
- Requires path to be `/` (root path)
|
|
21
|
+
- Prohibits the Domain attribute
|
|
22
|
+
- Ensures cookies are only sent to the exact domain that set them
|
|
23
|
+
|
|
24
|
+
## Examples
|
|
25
|
+
|
|
26
|
+
### ❌ Violations
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
// Express.js - Missing __Host- prefix for session cookie
|
|
30
|
+
res.cookie("sessionid", token, {
|
|
31
|
+
secure: true,
|
|
32
|
+
httpOnly: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// NestJS - Authentication cookie without __Host- prefix
|
|
36
|
+
@Post('login')
|
|
37
|
+
login(@Res() response: Response) {
|
|
38
|
+
response.cookie('auth_token', value, {
|
|
39
|
+
secure: true,
|
|
40
|
+
path: '/',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Next.js - Session cookie missing __Host- prefix
|
|
45
|
+
export async function POST() {
|
|
46
|
+
const response = NextResponse.json({ success: true });
|
|
47
|
+
response.cookies.set('sessionid', token, {
|
|
48
|
+
secure: true,
|
|
49
|
+
httpOnly: true,
|
|
50
|
+
});
|
|
51
|
+
return response;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// NextAuth.js - Session token without __Host- prefix
|
|
55
|
+
export default NextAuth({
|
|
56
|
+
cookies: {
|
|
57
|
+
sessionToken: {
|
|
58
|
+
name: 'next-auth.session-token',
|
|
59
|
+
options: {
|
|
60
|
+
secure: true,
|
|
61
|
+
httpOnly: true,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### ✅ Correct Usage
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// Express.js - Proper __Host- prefix for session cookie
|
|
72
|
+
res.cookie("__Host-sessionid", token, {
|
|
73
|
+
secure: true,
|
|
74
|
+
httpOnly: true,
|
|
75
|
+
path: "/",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// NestJS - Authentication cookie with __Host- prefix
|
|
79
|
+
@Post('login')
|
|
80
|
+
login(@Res() response: Response) {
|
|
81
|
+
response.cookie('__Host-auth_token', value, {
|
|
82
|
+
secure: true,
|
|
83
|
+
httpOnly: true,
|
|
84
|
+
path: '/',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Next.js - Session cookie with __Host- prefix
|
|
89
|
+
export async function POST() {
|
|
90
|
+
const response = NextResponse.json({ success: true });
|
|
91
|
+
response.cookies.set('__Host-sessionid', token, {
|
|
92
|
+
secure: true,
|
|
93
|
+
httpOnly: true,
|
|
94
|
+
path: '/',
|
|
95
|
+
});
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// NextAuth.js - Session token with __Host- prefix
|
|
100
|
+
export default NextAuth({
|
|
101
|
+
cookies: {
|
|
102
|
+
sessionToken: {
|
|
103
|
+
name: '__Host-next-auth.session-token',
|
|
104
|
+
options: {
|
|
105
|
+
secure: true,
|
|
106
|
+
httpOnly: true,
|
|
107
|
+
path: '/',
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## \_\_Host- Prefix Requirements
|
|
115
|
+
|
|
116
|
+
When using the `__Host-` prefix, the following requirements must be met:
|
|
117
|
+
|
|
118
|
+
1. **Secure**: Cookie must have the `Secure` attribute (HTTPS only)
|
|
119
|
+
2. **Path**: Must be set to `/` (root path)
|
|
120
|
+
3. **Domain**: Must NOT have a Domain attribute
|
|
121
|
+
4. **Exact Domain**: Cookie will only be sent to the exact domain that set it
|
|
122
|
+
|
|
123
|
+
## Detected Patterns
|
|
124
|
+
|
|
125
|
+
This rule detects session cookies without `__Host-` prefix in multiple frameworks:
|
|
126
|
+
|
|
127
|
+
### Express.js
|
|
128
|
+
|
|
129
|
+
- `res.cookie()` calls with session cookie names without `__Host-` prefix
|
|
130
|
+
- `res.setHeader()` with `Set-Cookie` headers missing `__Host-` prefix
|
|
131
|
+
- Session middleware configuration with cookie names missing `__Host-` prefix
|
|
132
|
+
- Array of Set-Cookie headers with session cookies missing `__Host-` prefix
|
|
133
|
+
|
|
134
|
+
### NestJS
|
|
135
|
+
|
|
136
|
+
- `@Res()` decorator response cookie methods
|
|
137
|
+
- `@Cookies()` decorator usage with session cookies
|
|
138
|
+
- Response object cookie setting methods
|
|
139
|
+
- NestJS session middleware configuration
|
|
140
|
+
|
|
141
|
+
### Next.js
|
|
142
|
+
|
|
143
|
+
- `response.cookies.set()` method calls
|
|
144
|
+
- `cookies().set()` from next/headers
|
|
145
|
+
- NextAuth.js session and CSRF token configuration
|
|
146
|
+
- API route response cookie setting
|
|
147
|
+
|
|
148
|
+
### NextAuth.js
|
|
149
|
+
|
|
150
|
+
- Session token configuration without `__Host-` prefix
|
|
151
|
+
- CSRF token configuration missing `__Host-` prefix
|
|
152
|
+
- Cookie configuration in NextAuth providers
|
|
153
|
+
|
|
154
|
+
## Session Cookie Detection
|
|
155
|
+
|
|
156
|
+
The rule identifies session cookies based on common naming patterns:
|
|
157
|
+
|
|
158
|
+
- `session`, `sessionid`, `session_id`
|
|
159
|
+
- `sid`, `connect.sid`
|
|
160
|
+
- `auth`, `auth_token`, `authentication`
|
|
161
|
+
- `jwt`, `token`
|
|
162
|
+
- `csrf`, `csrf_token`, `xsrf`
|
|
163
|
+
- `login`, `user`, `userid`, `user_id`
|
|
164
|
+
- `sessionToken`, `csrfToken` (NextAuth specific)
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
The rule uses regex-based analysis for comprehensive framework support:
|
|
169
|
+
|
|
170
|
+
- **Regex-based**: Pattern matching for framework-specific cookie patterns
|
|
171
|
+
- **Framework Support**: Express.js, NestJS, Next.js, NextAuth.js
|
|
172
|
+
|
|
173
|
+
## Security Impact
|
|
174
|
+
|
|
175
|
+
**Without \_\_Host- prefix:**
|
|
176
|
+
|
|
177
|
+
- Subdomain cookie sharing vulnerabilities
|
|
178
|
+
- Session fixation attacks from subdomains
|
|
179
|
+
- Cross-subdomain session hijacking
|
|
180
|
+
|
|
181
|
+
**With \_\_Host- prefix:**
|
|
182
|
+
|
|
183
|
+
- Cookies isolated to exact domain
|
|
184
|
+
- Prevention of subdomain attacks
|
|
185
|
+
- Enhanced session security
|
|
186
|
+
|
|
187
|
+
## Compatibility
|
|
188
|
+
|
|
189
|
+
- **Node.js**: All versions
|
|
190
|
+
- **Frameworks**: Express.js, NestJS, Next.js, NextAuth.js
|
|
191
|
+
- **Browsers**: Modern browsers supporting \_\_Host- prefix
|
|
192
|
+
- **Languages**: JavaScript, TypeScript
|
|
193
|
+
|
|
194
|
+
## References
|
|
195
|
+
|
|
196
|
+
- [MDN: \_\_Host- prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#__Host-)
|
|
197
|
+
- [RFC Draft: Cookie Prefixes](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00)
|
|
198
|
+
- [OWASP: Secure Cookie Attribute](https://owasp.org/www-community/controls/SecureCookieAttribute)
|
|
199
|
+
|
|
200
|
+
## Related Rules
|
|
201
|
+
|
|
202
|
+
- **S031**: Set Secure attribute for Session Cookies
|
|
203
|
+
- **S032**: Set HttpOnly attribute for Session Cookies
|
|
204
|
+
- **S033**: Set SameSite attribute for Session Cookies
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S034 Main Analyzer - Use __Host- prefix for Session Cookies
|
|
3
|
+
* Primary: Symbol-based analysis (when available)
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* Command: node cli.js --rule=S034 --input=examples/rule-test-fixtures/rules/S034_host_prefix_session_cookies --engine=heuristic
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const S034SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
9
|
+
const S034RegexBasedAnalyzer = require("./regex-based-analyzer.js");
|
|
10
|
+
|
|
11
|
+
class S034Analyzer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
14
|
+
console.log(`🔧 [S034] Constructor called with options:`, !!options);
|
|
15
|
+
console.log(
|
|
16
|
+
`🔧 [S034] Options type:`,
|
|
17
|
+
typeof options,
|
|
18
|
+
Object.keys(options || {})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.ruleId = "S034";
|
|
23
|
+
this.ruleName = "Use __Host- prefix for Session Cookies";
|
|
24
|
+
this.description =
|
|
25
|
+
"Use __Host- prefix for Session Cookies to prevent subdomain sharing. The __Host- prefix ensures cookies are only sent to the exact domain that set them, preventing subdomain cookie sharing attacks.";
|
|
26
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
27
|
+
this.verbose = options.verbose || false;
|
|
28
|
+
|
|
29
|
+
// Configuration - Use symbol-based as primary, regex for additional coverage
|
|
30
|
+
this.config = {
|
|
31
|
+
useSymbolBased: true, // Primary approach
|
|
32
|
+
fallbackToRegex: true, // Additional coverage
|
|
33
|
+
regexBasedOnly: false, // Can be set to true for pure mode
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Initialize analyzers
|
|
37
|
+
try {
|
|
38
|
+
this.symbolAnalyzer = new S034SymbolBasedAnalyzer(this.semanticEngine);
|
|
39
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
40
|
+
console.log(`🔧 [S034] Symbol analyzer created successfully`);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`🔧 [S034] Error creating symbol analyzer:`, error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
this.regexAnalyzer = new S034RegexBasedAnalyzer(this.semanticEngine);
|
|
48
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
49
|
+
console.log(`🔧 [S034] Regex analyzer created successfully`);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`🔧 [S034] Error creating regex analyzer:`, error);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Initialize analyzer with semantic engine
|
|
58
|
+
*/
|
|
59
|
+
async initialize(semanticEngine) {
|
|
60
|
+
this.semanticEngine = semanticEngine;
|
|
61
|
+
|
|
62
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
63
|
+
console.log(`🔧 [S034] Main analyzer initializing...`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Initialize both analyzers
|
|
67
|
+
if (this.symbolAnalyzer) {
|
|
68
|
+
await this.symbolAnalyzer.initialize?.(semanticEngine);
|
|
69
|
+
}
|
|
70
|
+
if (this.regexAnalyzer) {
|
|
71
|
+
await this.regexAnalyzer.initialize?.(semanticEngine);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Clean up if needed
|
|
75
|
+
if (this.regexAnalyzer) {
|
|
76
|
+
this.regexAnalyzer.cleanup?.();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
80
|
+
console.log(`🔧 [S034] Main analyzer initialized successfully`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Single file analysis method for testing
|
|
86
|
+
*/
|
|
87
|
+
analyzeSingle(filePath, options = {}) {
|
|
88
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
89
|
+
console.log(`🔍 [S034] analyzeSingle() called for: ${filePath}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Return result using same format as analyze method
|
|
93
|
+
return this.analyze([filePath], "typescript", options);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async analyze(files, language, options = {}) {
|
|
97
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
98
|
+
console.log(
|
|
99
|
+
`🔧 [S034] analyze() method called with ${files.length} files, language: ${language}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const violations = [];
|
|
104
|
+
|
|
105
|
+
for (const filePath of files) {
|
|
106
|
+
try {
|
|
107
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
108
|
+
console.log(`🔧 [S034] Processing file: ${filePath}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const fileViolations = await this.analyzeFile(filePath, options);
|
|
112
|
+
violations.push(...fileViolations);
|
|
113
|
+
|
|
114
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
115
|
+
console.log(
|
|
116
|
+
`🔧 [S034] File ${filePath}: Found ${fileViolations.length} violations`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.warn(
|
|
121
|
+
`⚠ [S034] Analysis failed for ${filePath}:`,
|
|
122
|
+
error.message
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
128
|
+
console.log(`🔧 [S034] Total violations found: ${violations.length}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return violations;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async analyzeFile(filePath, options = {}) {
|
|
135
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
136
|
+
console.log(`🔍 [S034] analyzeFile() called for: ${filePath}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Create a Map to track unique violations and prevent duplicates
|
|
140
|
+
const violationMap = new Map();
|
|
141
|
+
const lineToViolationMap = new Map(); // Track which lines already have violations
|
|
142
|
+
|
|
143
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
144
|
+
if (
|
|
145
|
+
this.config.useSymbolBased &&
|
|
146
|
+
this.semanticEngine?.project &&
|
|
147
|
+
this.semanticEngine?.initialized
|
|
148
|
+
) {
|
|
149
|
+
try {
|
|
150
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
151
|
+
console.log(`🔧 [S034] Trying symbol-based analysis...`);
|
|
152
|
+
}
|
|
153
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
154
|
+
if (sourceFile) {
|
|
155
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
156
|
+
console.log(`🔧 [S034] Source file found, analyzing...`);
|
|
157
|
+
}
|
|
158
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
159
|
+
sourceFile,
|
|
160
|
+
filePath
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Add symbol violations first (higher priority)
|
|
164
|
+
symbolViolations.forEach((violation) => {
|
|
165
|
+
// Extract cookie name from message for better deduplication
|
|
166
|
+
const cookieNameMatch =
|
|
167
|
+
violation.message.match(
|
|
168
|
+
/(?:Session (?:cookie|middleware cookie name)|Set-Cookie.*?|NextAuth.*?) "([^"]+)"/
|
|
169
|
+
) ||
|
|
170
|
+
violation.message.match(
|
|
171
|
+
/Insecure session cookie:.*?(?:Session cookie|NextAuth.*?) "([^"]+)"/
|
|
172
|
+
);
|
|
173
|
+
const cookieName = cookieNameMatch ? cookieNameMatch[1] : "unknown";
|
|
174
|
+
|
|
175
|
+
// Use specific key including column for exact match
|
|
176
|
+
const specificKey = `${violation.line}:${
|
|
177
|
+
violation.column || 1
|
|
178
|
+
}:${cookieName}`;
|
|
179
|
+
const lineKey = `${violation.line}:${cookieName}`;
|
|
180
|
+
|
|
181
|
+
if (!violationMap.has(specificKey)) {
|
|
182
|
+
violationMap.set(specificKey, {
|
|
183
|
+
...violation,
|
|
184
|
+
source: "symbol", // Track source for debugging
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Also track by line for regex deduplication
|
|
188
|
+
lineToViolationMap.set(lineKey, specificKey);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
193
|
+
console.log(
|
|
194
|
+
`🔧 [S034] Symbol analysis completed: ${symbolViolations.length} violations`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
199
|
+
console.log(`🔧 [S034] Source file not found, falling back...`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.warn(`⚠ [S034] Symbol analysis failed:`, error.message);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 2. Try Regex-based analysis (fallback or additional)
|
|
208
|
+
if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
|
|
209
|
+
try {
|
|
210
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
211
|
+
console.log(`🔧 [S034] Trying regex-based analysis...`);
|
|
212
|
+
}
|
|
213
|
+
const regexViolations = await this.regexAnalyzer.analyze(filePath);
|
|
214
|
+
|
|
215
|
+
// Add regex violations only if not already covered by symbol analysis
|
|
216
|
+
regexViolations.forEach((violation) => {
|
|
217
|
+
// Extract cookie name from message for better deduplication
|
|
218
|
+
const cookieNameMatch =
|
|
219
|
+
violation.message.match(
|
|
220
|
+
/(?:Session (?:cookie|middleware cookie name)|Set-Cookie.*?|NextAuth.*?) "([^"]+)"/
|
|
221
|
+
) ||
|
|
222
|
+
violation.message.match(
|
|
223
|
+
/Insecure session cookie:.*?(?:Session cookie|NextAuth.*?) "([^"]+)"/
|
|
224
|
+
);
|
|
225
|
+
const cookieName = cookieNameMatch ? cookieNameMatch[1] : "unknown";
|
|
226
|
+
|
|
227
|
+
// Check if this line+cookie already has a violation from symbol analyzer
|
|
228
|
+
const lineKey = `${violation.line}:${cookieName}`;
|
|
229
|
+
|
|
230
|
+
if (!lineToViolationMap.has(lineKey)) {
|
|
231
|
+
// No symbol violation for this line+cookie, add regex violation
|
|
232
|
+
const specificKey = `${violation.line}:${
|
|
233
|
+
violation.column || 1
|
|
234
|
+
}:${cookieName}:regex`;
|
|
235
|
+
|
|
236
|
+
if (!violationMap.has(specificKey)) {
|
|
237
|
+
violationMap.set(specificKey, {
|
|
238
|
+
...violation,
|
|
239
|
+
source: "regex", // Track source for debugging
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
} else if (process.env.SUNLINT_DEBUG) {
|
|
243
|
+
console.log(
|
|
244
|
+
`🔧 [S034] Skipping duplicate regex violation at ${lineKey} (already covered by symbol)`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
250
|
+
console.log(
|
|
251
|
+
`🔧 [S034] Regex analysis completed: ${regexViolations.length} violations`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.warn(`⚠ [S034] Regex analysis failed:`, error.message);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Convert Map values to array and add filePath to each violation
|
|
260
|
+
const finalViolations = Array.from(violationMap.values()).map(
|
|
261
|
+
(violation) => ({
|
|
262
|
+
...violation,
|
|
263
|
+
filePath: filePath,
|
|
264
|
+
file: filePath, // Also add 'file' for compatibility
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
269
|
+
console.log(
|
|
270
|
+
`🔧 [S034] File analysis completed: ${finalViolations.length} unique violations`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return finalViolations;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Clean up resources
|
|
279
|
+
*/
|
|
280
|
+
cleanup() {
|
|
281
|
+
if (this.symbolAnalyzer?.cleanup) {
|
|
282
|
+
this.symbolAnalyzer.cleanup();
|
|
283
|
+
}
|
|
284
|
+
if (this.regexAnalyzer?.cleanup) {
|
|
285
|
+
this.regexAnalyzer.cleanup();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = S034Analyzer;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S034",
|
|
3
|
+
"name": "Use __Host- prefix for Session Cookies",
|
|
4
|
+
"description": "Use __Host- prefix for Session Cookies to prevent subdomain sharing. The __Host- prefix ensures cookies are only sent to the exact domain that set them, preventing subdomain cookie sharing attacks.",
|
|
5
|
+
"category": "security",
|
|
6
|
+
"severity": "warning",
|
|
7
|
+
"confidence": "high",
|
|
8
|
+
"tags": ["cookie", "security", "session", "subdomain", "host-prefix"],
|
|
9
|
+
"languages": ["javascript", "typescript"],
|
|
10
|
+
"patterns": {
|
|
11
|
+
"cookieNamePatterns": [
|
|
12
|
+
"session",
|
|
13
|
+
"sessionid",
|
|
14
|
+
"session_id",
|
|
15
|
+
"sid",
|
|
16
|
+
"connect.sid",
|
|
17
|
+
"auth",
|
|
18
|
+
"auth_token",
|
|
19
|
+
"authentication",
|
|
20
|
+
"jwt",
|
|
21
|
+
"token",
|
|
22
|
+
"csrf",
|
|
23
|
+
"csrf_token",
|
|
24
|
+
"xsrf",
|
|
25
|
+
"login",
|
|
26
|
+
"user",
|
|
27
|
+
"userid",
|
|
28
|
+
"user_id"
|
|
29
|
+
],
|
|
30
|
+
"hostPrefixPattern": "^__Host-",
|
|
31
|
+
"violationPatterns": [
|
|
32
|
+
"res\\.cookie\\s*\\(\\s*['\"`](?!__Host-)",
|
|
33
|
+
"Set-Cookie:\\s*(?!__Host-)",
|
|
34
|
+
"cookie:\\s*{[^}]*name\\s*:\\s*['\"`](?!__Host-)"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"validation": {
|
|
38
|
+
"hostPrefixRequirements": {
|
|
39
|
+
"secure": true,
|
|
40
|
+
"path": "/",
|
|
41
|
+
"domain": null,
|
|
42
|
+
"description": "__Host- prefix requires Secure=true, Path=/, and no Domain attribute"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"examples": {
|
|
46
|
+
"violation": [
|
|
47
|
+
"res.cookie('sessionid', token, { secure: true, httpOnly: true })",
|
|
48
|
+
"res.cookie('auth_token', value, { secure: true, path: '/' })",
|
|
49
|
+
"res.setHeader('Set-Cookie', 'session=value; Secure; HttpOnly')"
|
|
50
|
+
],
|
|
51
|
+
"clean": [
|
|
52
|
+
"res.cookie('__Host-sessionid', token, { secure: true, httpOnly: true, path: '/' })",
|
|
53
|
+
"res.cookie('__Host-auth_token', value, { secure: true, path: '/', domain: undefined })",
|
|
54
|
+
"res.setHeader('Set-Cookie', '__Host-session=value; Secure; HttpOnly; Path=/')"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"references": [
|
|
58
|
+
"https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#__Host-",
|
|
59
|
+
"https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00",
|
|
60
|
+
"https://owasp.org/www-community/controls/SecureCookieAttribute"
|
|
61
|
+
]
|
|
62
|
+
}
|