@sun-asterisk/sunlint 1.3.2 → 1.3.3
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 +38 -0
- package/README.md +5 -3
- package/config/rules/enhanced-rules-registry.json +144 -33
- package/core/analysis-orchestrator.js +167 -42
- package/core/auto-performance-manager.js +243 -0
- package/core/cli-action-handler.js +9 -1
- 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/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,703 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S033 Regex-Based Analyzer - Set SameSite attribute for Session Cookies
|
|
3
|
+
* Fallback analysis using regex patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
|
|
8
|
+
class S033RegexBasedAnalyzer {
|
|
9
|
+
constructor(semanticEngine = null) {
|
|
10
|
+
this.semanticEngine = semanticEngine;
|
|
11
|
+
this.ruleId = "S033";
|
|
12
|
+
this.category = "security";
|
|
13
|
+
|
|
14
|
+
// Session cookie indicators (enhanced with framework patterns)
|
|
15
|
+
this.sessionIndicators = [
|
|
16
|
+
"session",
|
|
17
|
+
"sessionid",
|
|
18
|
+
"sessid",
|
|
19
|
+
"jsessionid",
|
|
20
|
+
"phpsessid",
|
|
21
|
+
"asp.net_sessionid",
|
|
22
|
+
"connect.sid",
|
|
23
|
+
"auth",
|
|
24
|
+
"token",
|
|
25
|
+
"jwt",
|
|
26
|
+
"csrf",
|
|
27
|
+
"refresh",
|
|
28
|
+
"next-auth",
|
|
29
|
+
"sessiontoken",
|
|
30
|
+
"csrftoken",
|
|
31
|
+
"user_session",
|
|
32
|
+
"api_session",
|
|
33
|
+
"login_session",
|
|
34
|
+
"auth_token",
|
|
35
|
+
"csrf_token",
|
|
36
|
+
"refresh_token",
|
|
37
|
+
"admin_session",
|
|
38
|
+
"app_auth",
|
|
39
|
+
"edge_session",
|
|
40
|
+
"server_session",
|
|
41
|
+
"custom-session",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Cookie method patterns (enhanced with framework support)
|
|
45
|
+
this.cookieMethodPatterns = [
|
|
46
|
+
// Express.js res.cookie patterns
|
|
47
|
+
/res\.cookie\s*\(\s*(['"`])(session|sessionid|sessid|auth|token|jwt|csrf|refresh|connect\.sid)[^)]*\)/gi,
|
|
48
|
+
// Next.js cookies.set patterns
|
|
49
|
+
/(?:response\.)?cookies\.set\s*\(\s*(['"`])(session|sessionid|sessid|auth|token|jwt|csrf|refresh)[^)]*\)/gi,
|
|
50
|
+
// Nuxt.js useCookie patterns
|
|
51
|
+
/useCookie\s*\(\s*(['"`])(session|sessionid|sessid|auth|token|jwt|csrf|refresh)[^)]*\)/gi,
|
|
52
|
+
// Nuxt.js setCookie patterns
|
|
53
|
+
/setCookie\s*\(\s*[^,]+,\s*(['"`])(session|sessionid|sessid|auth|token|jwt|csrf|refresh)[^)]*\)/gi,
|
|
54
|
+
// Set-Cookie header patterns
|
|
55
|
+
/res\.setHeader\s*\(\s*['"`]set-cookie['"`]\s*,\s*([^)]+)\)/gi,
|
|
56
|
+
// Express session patterns
|
|
57
|
+
/session\s*\(\s*\{[^}]*\}/gi,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// SameSite validation patterns (enhanced with ternary support)
|
|
61
|
+
this.sameSitePatterns = [
|
|
62
|
+
/sameSite\s*:\s*['"`](strict|lax|none)['"`]/gi,
|
|
63
|
+
/sameSite\s*:\s*(strict|lax|none)/gi,
|
|
64
|
+
/SameSite=(Strict|Lax|None)/gi,
|
|
65
|
+
/sameSite\s*:\s*.*\?\s*['"`](strict|lax|none)['"`]\s*:\s*['"`](strict|lax|none)['"`]/gi, // Ternary with quotes
|
|
66
|
+
/sameSite\s*:\s*.*\?\s*(strict|lax|none)\s*:\s*(strict|lax|none)/gi, // Ternary without quotes
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async analyze(filePath) {
|
|
71
|
+
if (this.verbose) {
|
|
72
|
+
console.log(`🔍 [${this.ruleId}] Regex-based analysis for: ${filePath}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
77
|
+
return this.analyzeContent(content, filePath);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (this.verbose) {
|
|
80
|
+
console.log(
|
|
81
|
+
`🔍 [${this.ruleId}] Regex: Error reading file:`,
|
|
82
|
+
error.message
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
analyzeContent(content, filePath) {
|
|
90
|
+
const violations = [];
|
|
91
|
+
const lines = content.split("\n");
|
|
92
|
+
|
|
93
|
+
// Remove comments to avoid false positives
|
|
94
|
+
const cleanContent = this.removeComments(content);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Pattern 1: res.cookie() calls (Express.js)
|
|
98
|
+
violations.push(
|
|
99
|
+
...this.analyzeCookieCalls(cleanContent, lines, filePath)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Pattern 2: Set-Cookie headers
|
|
103
|
+
violations.push(
|
|
104
|
+
...this.analyzeSetCookieHeaders(cleanContent, lines, filePath)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Pattern 3: Express session middleware
|
|
108
|
+
violations.push(
|
|
109
|
+
...this.analyzeSessionMiddleware(cleanContent, lines, filePath)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Pattern 4: Next.js cookies.set() calls
|
|
113
|
+
violations.push(
|
|
114
|
+
...this.analyzeNextJSCookies(cleanContent, lines, filePath)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Pattern 5: Nuxt.js useCookie() calls
|
|
118
|
+
violations.push(
|
|
119
|
+
...this.analyzeNuxtJSUseCookie(cleanContent, lines, filePath)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// Pattern 6: Nuxt.js setCookie() calls
|
|
123
|
+
violations.push(
|
|
124
|
+
...this.analyzeNuxtJSSetCookie(cleanContent, lines, filePath)
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Pattern 7: NextAuth configuration
|
|
128
|
+
violations.push(
|
|
129
|
+
...this.analyzeNextAuthConfig(cleanContent, lines, filePath)
|
|
130
|
+
);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (this.verbose) {
|
|
133
|
+
console.log(
|
|
134
|
+
`🔍 [${this.ruleId}] Regex: Error in analysis:`,
|
|
135
|
+
error.message
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return violations;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
analyzeCookieCalls(content, lines, filePath) {
|
|
144
|
+
const violations = [];
|
|
145
|
+
|
|
146
|
+
// Pattern for res.cookie calls
|
|
147
|
+
const cookieCallPattern =
|
|
148
|
+
/res\.cookie\s*\(\s*(['"`])([^'"`]+)\1\s*,\s*[^,]+(?:\s*,\s*(\{[^}]*\}))?\s*\)/gi;
|
|
149
|
+
let match;
|
|
150
|
+
|
|
151
|
+
while ((match = cookieCallPattern.exec(content)) !== null) {
|
|
152
|
+
const fullMatch = match[0];
|
|
153
|
+
const cookieName = match[2];
|
|
154
|
+
const configObject = match[3] || "";
|
|
155
|
+
|
|
156
|
+
if (this.verbose) {
|
|
157
|
+
console.log(
|
|
158
|
+
`🔍 [${
|
|
159
|
+
this.ruleId
|
|
160
|
+
}] Regex: Pattern match - cookieName: "${cookieName}", config: "${configObject.substring(
|
|
161
|
+
0,
|
|
162
|
+
50
|
|
163
|
+
)}..."`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Only check session cookies
|
|
168
|
+
if (!this.isSessionCookie(cookieName)) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check if config object has SameSite
|
|
173
|
+
if (!configObject || !this.hasSameSiteInText(configObject)) {
|
|
174
|
+
// Skip if this looks like a config reference
|
|
175
|
+
if (this.isConfigReference(configObject)) {
|
|
176
|
+
if (this.verbose) {
|
|
177
|
+
console.log(
|
|
178
|
+
`🔍 [${
|
|
179
|
+
this.ruleId
|
|
180
|
+
}] Regex: Skipping config reference: ${configObject.substring(
|
|
181
|
+
0,
|
|
182
|
+
50
|
|
183
|
+
)}...`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
190
|
+
violations.push({
|
|
191
|
+
rule: this.ruleId,
|
|
192
|
+
source: filePath,
|
|
193
|
+
category: this.category,
|
|
194
|
+
line: lineNumber,
|
|
195
|
+
column: 1,
|
|
196
|
+
message: `Insecure session cookie: Session cookie "${cookieName}" missing SameSite attribute`,
|
|
197
|
+
severity: "error",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return violations;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
analyzeSetCookieHeaders(content, lines, filePath) {
|
|
206
|
+
const violations = [];
|
|
207
|
+
|
|
208
|
+
// Pattern for setHeader with Set-Cookie
|
|
209
|
+
const setHeaderPattern =
|
|
210
|
+
/res\.setHeader\s*\(\s*['"`]set-cookie['"`]\s*,\s*([^)]+)\)/gi;
|
|
211
|
+
let match;
|
|
212
|
+
|
|
213
|
+
while ((match = setHeaderPattern.exec(content)) !== null) {
|
|
214
|
+
const cookieValue = match[1];
|
|
215
|
+
|
|
216
|
+
if (this.verbose) {
|
|
217
|
+
console.log(
|
|
218
|
+
`🔍 [${
|
|
219
|
+
this.ruleId
|
|
220
|
+
}] Regex: Checking Set-Cookie header pattern: ${match[0].substring(
|
|
221
|
+
0,
|
|
222
|
+
100
|
|
223
|
+
)}...`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Handle array of cookies
|
|
228
|
+
if (cookieValue.includes("[") && cookieValue.includes("]")) {
|
|
229
|
+
const cookieStrings = this.extractCookieStrings(cookieValue);
|
|
230
|
+
for (const cookieString of cookieStrings) {
|
|
231
|
+
const cookieName = this.extractCookieNameFromString(cookieString);
|
|
232
|
+
|
|
233
|
+
if (this.verbose) {
|
|
234
|
+
console.log(
|
|
235
|
+
`🔍 [${
|
|
236
|
+
this.ruleId
|
|
237
|
+
}] Regex: Checking Set-Cookie string: "${cookieString.substring(
|
|
238
|
+
0,
|
|
239
|
+
50
|
|
240
|
+
)}..." - name: "${cookieName}"`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
this.isSessionCookie(cookieName) &&
|
|
246
|
+
!this.hasSameSiteAttribute(cookieString)
|
|
247
|
+
) {
|
|
248
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
249
|
+
violations.push({
|
|
250
|
+
rule: this.ruleId,
|
|
251
|
+
source: filePath,
|
|
252
|
+
category: this.category,
|
|
253
|
+
line: lineNumber,
|
|
254
|
+
column: 1,
|
|
255
|
+
message: `Insecure session cookie: Session cookie "${cookieName}" in Set-Cookie header missing SameSite attribute`,
|
|
256
|
+
severity: "error",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
// Single cookie
|
|
262
|
+
const cookieName = this.extractCookieNameFromString(cookieValue);
|
|
263
|
+
if (
|
|
264
|
+
this.isSessionCookie(cookieName) &&
|
|
265
|
+
!this.hasSameSiteAttribute(cookieValue)
|
|
266
|
+
) {
|
|
267
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
268
|
+
violations.push({
|
|
269
|
+
rule: this.ruleId,
|
|
270
|
+
source: filePath,
|
|
271
|
+
category: this.category,
|
|
272
|
+
line: lineNumber,
|
|
273
|
+
column: 1,
|
|
274
|
+
message: `Insecure session cookie: Session cookie "${cookieName}" in Set-Cookie header missing SameSite attribute`,
|
|
275
|
+
severity: "error",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return violations;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
analyzeSessionMiddleware(content, lines, filePath) {
|
|
285
|
+
const violations = [];
|
|
286
|
+
|
|
287
|
+
// Pattern for express-session middleware
|
|
288
|
+
const sessionPattern =
|
|
289
|
+
/session\s*\(\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}\s*\)/gi;
|
|
290
|
+
let match;
|
|
291
|
+
|
|
292
|
+
while ((match = sessionPattern.exec(content)) !== null) {
|
|
293
|
+
const sessionConfig = match[1];
|
|
294
|
+
|
|
295
|
+
if (this.verbose) {
|
|
296
|
+
console.log(
|
|
297
|
+
`🔍 [${
|
|
298
|
+
this.ruleId
|
|
299
|
+
}] Regex: Checking session middleware: ${sessionConfig.substring(
|
|
300
|
+
0,
|
|
301
|
+
100
|
|
302
|
+
)}...`
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Check if cookie config has SameSite
|
|
307
|
+
const cookieConfigMatch = sessionConfig.match(/cookie\s*:\s*\{([^}]*)\}/);
|
|
308
|
+
if (cookieConfigMatch) {
|
|
309
|
+
const cookieConfig = cookieConfigMatch[1];
|
|
310
|
+
if (!this.hasSameSiteInText(cookieConfig)) {
|
|
311
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
312
|
+
violations.push({
|
|
313
|
+
rule: this.ruleId,
|
|
314
|
+
source: filePath,
|
|
315
|
+
category: this.category,
|
|
316
|
+
line: lineNumber,
|
|
317
|
+
column: 1,
|
|
318
|
+
message: `Insecure session cookie: Session middleware missing SameSite attribute in cookie configuration`,
|
|
319
|
+
severity: "error",
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
// No cookie config at all
|
|
324
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
325
|
+
violations.push({
|
|
326
|
+
rule: this.ruleId,
|
|
327
|
+
source: filePath,
|
|
328
|
+
category: this.category,
|
|
329
|
+
line: lineNumber,
|
|
330
|
+
column: 1,
|
|
331
|
+
message: `Insecure session cookie: Session middleware missing cookie configuration with SameSite attribute`,
|
|
332
|
+
severity: "error",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return violations;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
extractCookieStrings(cookieValue) {
|
|
341
|
+
const strings = [];
|
|
342
|
+
const matches = cookieValue.match(/['"`][^'"`]*['"`]/g);
|
|
343
|
+
if (matches) {
|
|
344
|
+
strings.push(...matches.map((s) => s.slice(1, -1))); // Remove quotes
|
|
345
|
+
}
|
|
346
|
+
return strings;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
extractCookieNameFromString(cookieString) {
|
|
350
|
+
const match = cookieString.match(/^[^=]+/);
|
|
351
|
+
return match ? match[0].trim() : "unknown";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
hasSameSiteInText(text) {
|
|
355
|
+
return this.sameSitePatterns.some((pattern) => {
|
|
356
|
+
pattern.lastIndex = 0; // Reset regex state
|
|
357
|
+
return pattern.test(text);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
hasSameSiteAttribute(cookieValue) {
|
|
362
|
+
const sameSitePattern = /SameSite=(Strict|Lax|None)/i;
|
|
363
|
+
return sameSitePattern.test(cookieValue);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
isSessionCookie(cookieName) {
|
|
367
|
+
if (!cookieName || cookieName === "null" || cookieName === "undefined") {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const name = cookieName.toLowerCase();
|
|
372
|
+
return this.sessionIndicators.some((indicator) =>
|
|
373
|
+
name.includes(indicator.toLowerCase())
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
isConfigReference(configText) {
|
|
378
|
+
if (!configText) return false;
|
|
379
|
+
|
|
380
|
+
// Check for common config reference patterns
|
|
381
|
+
const configRefPatterns = [
|
|
382
|
+
/\.\.\./, // Spread operator
|
|
383
|
+
/\w+Config/, // Variable ending with Config
|
|
384
|
+
/this\.\w+/, // this.property
|
|
385
|
+
/^\s*\w+\s*$/, // Simple variable reference
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
return configRefPatterns.some((pattern) => pattern.test(configText));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
removeComments(content) {
|
|
392
|
+
// Remove single-line comments
|
|
393
|
+
content = content.replace(/\/\/.*$/gm, "");
|
|
394
|
+
// Remove multi-line comments
|
|
395
|
+
content = content.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
396
|
+
return content;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getLineNumber(content, index) {
|
|
400
|
+
return content.substring(0, index).split("\n").length;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
analyzeNextJSCookies(content, lines, filePath) {
|
|
404
|
+
const violations = [];
|
|
405
|
+
|
|
406
|
+
// Pattern 1: Next.js response.cookies.set() calls
|
|
407
|
+
const nextJSResponsePattern =
|
|
408
|
+
/(?:response\.)?cookies\.set\s*\(\s*(['"`])([^'"`]+)\1\s*,\s*[^,]+(?:\s*,\s*(\{[^}]*\}))?\s*\)/gi;
|
|
409
|
+
let match;
|
|
410
|
+
|
|
411
|
+
while ((match = nextJSResponsePattern.exec(content)) !== null) {
|
|
412
|
+
const cookieName = match[2];
|
|
413
|
+
const configObject = match[3] || "";
|
|
414
|
+
|
|
415
|
+
if (this.verbose) {
|
|
416
|
+
console.log(
|
|
417
|
+
`🔍 [${
|
|
418
|
+
this.ruleId
|
|
419
|
+
}] Regex: Next.js Response Pattern match - cookieName: "${cookieName}", config: "${configObject.substring(
|
|
420
|
+
0,
|
|
421
|
+
50
|
|
422
|
+
)}..."`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Only check session cookies
|
|
427
|
+
if (!this.isSessionCookie(cookieName)) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Check if config object has SameSite
|
|
432
|
+
if (!configObject || !this.hasSameSiteInText(configObject)) {
|
|
433
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
434
|
+
violations.push({
|
|
435
|
+
rule: this.ruleId,
|
|
436
|
+
source: filePath,
|
|
437
|
+
category: this.category,
|
|
438
|
+
line: lineNumber,
|
|
439
|
+
column: 1,
|
|
440
|
+
message: `Insecure session cookie: Session cookie "${cookieName}" (Next.js) missing SameSite attribute`,
|
|
441
|
+
severity: "error",
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Pattern 2: Next.js cookies().set() calls (from next/headers)
|
|
447
|
+
const nextJSCookiesPattern =
|
|
448
|
+
/cookies\(\)\.set\s*\(\s*(['"`])([^'"`]+)\1\s*,\s*[^,]+(?:\s*,\s*(\{[^}]*\}))?\s*\)/gi;
|
|
449
|
+
|
|
450
|
+
while ((match = nextJSCookiesPattern.exec(content)) !== null) {
|
|
451
|
+
const cookieName = match[2];
|
|
452
|
+
const configObject = match[3] || "";
|
|
453
|
+
|
|
454
|
+
if (this.verbose) {
|
|
455
|
+
console.log(
|
|
456
|
+
`🔍 [${
|
|
457
|
+
this.ruleId
|
|
458
|
+
}] Regex: Next.js cookies() Pattern match - cookieName: "${cookieName}", config: "${configObject.substring(
|
|
459
|
+
0,
|
|
460
|
+
50
|
|
461
|
+
)}..."`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Only check session cookies
|
|
466
|
+
if (!this.isSessionCookie(cookieName)) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Check if config object has SameSite
|
|
471
|
+
if (!configObject || !this.hasSameSiteInText(configObject)) {
|
|
472
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
473
|
+
violations.push({
|
|
474
|
+
rule: this.ruleId,
|
|
475
|
+
source: filePath,
|
|
476
|
+
category: this.category,
|
|
477
|
+
line: lineNumber,
|
|
478
|
+
column: 1,
|
|
479
|
+
message: `Insecure session cookie: Session cookie "${cookieName}" (Next.js) missing SameSite attribute`,
|
|
480
|
+
severity: "error",
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Pattern 3: cookieStore.set() calls
|
|
486
|
+
const cookieStorePattern =
|
|
487
|
+
/cookieStore\.set\s*\(\s*(['"`])([^'"`]+)\1\s*,\s*[^,]+(?:\s*,\s*(\{[^}]*\}))?\s*\)/gi;
|
|
488
|
+
|
|
489
|
+
while ((match = cookieStorePattern.exec(content)) !== null) {
|
|
490
|
+
const cookieName = match[2];
|
|
491
|
+
const configObject = match[3] || "";
|
|
492
|
+
|
|
493
|
+
if (this.verbose) {
|
|
494
|
+
console.log(
|
|
495
|
+
`🔍 [${
|
|
496
|
+
this.ruleId
|
|
497
|
+
}] Regex: cookieStore Pattern match - cookieName: "${cookieName}", config: "${configObject.substring(
|
|
498
|
+
0,
|
|
499
|
+
50
|
|
500
|
+
)}..."`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Only check session cookies
|
|
505
|
+
if (!this.isSessionCookie(cookieName)) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Check if config object has SameSite
|
|
510
|
+
if (!configObject || !this.hasSameSiteInText(configObject)) {
|
|
511
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
512
|
+
violations.push({
|
|
513
|
+
rule: this.ruleId,
|
|
514
|
+
source: filePath,
|
|
515
|
+
category: this.category,
|
|
516
|
+
line: lineNumber,
|
|
517
|
+
column: 1,
|
|
518
|
+
message: `Insecure session cookie: Session cookie "${cookieName}" (Next.js) missing SameSite attribute`,
|
|
519
|
+
severity: "error",
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return violations;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
analyzeNuxtJSUseCookie(content, lines, filePath) {
|
|
528
|
+
const violations = [];
|
|
529
|
+
|
|
530
|
+
// Pattern for Nuxt.js useCookie() calls
|
|
531
|
+
const nuxtUseCookiePattern =
|
|
532
|
+
/useCookie\s*\(\s*(['"`])([^'"`]+)\1(?:\s*,\s*(\{[^}]*\}))?\s*\)/gi;
|
|
533
|
+
let match;
|
|
534
|
+
|
|
535
|
+
while ((match = nuxtUseCookiePattern.exec(content)) !== null) {
|
|
536
|
+
const cookieName = match[2];
|
|
537
|
+
const configObject = match[3] || "";
|
|
538
|
+
|
|
539
|
+
if (this.verbose) {
|
|
540
|
+
console.log(
|
|
541
|
+
`🔍 [${
|
|
542
|
+
this.ruleId
|
|
543
|
+
}] Regex: Nuxt.js useCookie Pattern match - cookieName: "${cookieName}", config: "${configObject.substring(
|
|
544
|
+
0,
|
|
545
|
+
50
|
|
546
|
+
)}..."`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Only check session cookies
|
|
551
|
+
if (!this.isSessionCookie(cookieName)) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Check if config object has SameSite
|
|
556
|
+
if (!configObject || !this.hasSameSiteInText(configObject)) {
|
|
557
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
558
|
+
violations.push({
|
|
559
|
+
rule: this.ruleId,
|
|
560
|
+
source: filePath,
|
|
561
|
+
category: this.category,
|
|
562
|
+
line: lineNumber,
|
|
563
|
+
column: 1,
|
|
564
|
+
message: `Insecure session cookie: Session cookie "${cookieName}" (Nuxt.js) missing SameSite attribute`,
|
|
565
|
+
severity: "error",
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return violations;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
analyzeNuxtJSSetCookie(content, lines, filePath) {
|
|
574
|
+
const violations = [];
|
|
575
|
+
|
|
576
|
+
// Pattern for Nuxt.js setCookie(event, name, value, options) calls
|
|
577
|
+
const nuxtSetCookiePattern =
|
|
578
|
+
/setCookie\s*\(\s*[^,]+,\s*(['"`])([^'"`]+)\1\s*,\s*[^,]+(?:\s*,\s*(\{[^}]*\}))?\s*\)/gi;
|
|
579
|
+
let match;
|
|
580
|
+
|
|
581
|
+
while ((match = nuxtSetCookiePattern.exec(content)) !== null) {
|
|
582
|
+
const cookieName = match[2];
|
|
583
|
+
const configObject = match[3] || "";
|
|
584
|
+
|
|
585
|
+
if (this.verbose) {
|
|
586
|
+
console.log(
|
|
587
|
+
`🔍 [${
|
|
588
|
+
this.ruleId
|
|
589
|
+
}] Regex: Nuxt.js setCookie Pattern match - cookieName: "${cookieName}", config: "${configObject.substring(
|
|
590
|
+
0,
|
|
591
|
+
50
|
|
592
|
+
)}..."`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Only check session cookies
|
|
597
|
+
if (!this.isSessionCookie(cookieName)) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Check if config object has SameSite
|
|
602
|
+
if (!configObject || !this.hasSameSiteInText(configObject)) {
|
|
603
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
604
|
+
violations.push({
|
|
605
|
+
rule: this.ruleId,
|
|
606
|
+
source: filePath,
|
|
607
|
+
category: this.category,
|
|
608
|
+
line: lineNumber,
|
|
609
|
+
column: 1,
|
|
610
|
+
message: `Insecure session cookie: Session cookie "${cookieName}" (Nuxt.js) missing SameSite attribute`,
|
|
611
|
+
severity: "error",
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return violations;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
analyzeNextAuthConfig(content, lines, filePath) {
|
|
620
|
+
const violations = [];
|
|
621
|
+
|
|
622
|
+
// Simple approach: find NextAuth cookie configurations directly
|
|
623
|
+
// Pattern 1: sessionToken configurations
|
|
624
|
+
const sessionTokenPattern =
|
|
625
|
+
/sessionToken\s*:\s*\{[^}]*options\s*:\s*\{([^}]*)\}/gi;
|
|
626
|
+
let match;
|
|
627
|
+
|
|
628
|
+
while ((match = sessionTokenPattern.exec(content)) !== null) {
|
|
629
|
+
const cookieOptions = match[1];
|
|
630
|
+
|
|
631
|
+
if (this.verbose) {
|
|
632
|
+
console.log(
|
|
633
|
+
`🔍 [${
|
|
634
|
+
this.ruleId
|
|
635
|
+
}] Regex: sessionToken Pattern match - options: "${cookieOptions.substring(
|
|
636
|
+
0,
|
|
637
|
+
50
|
|
638
|
+
)}..."`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (!this.hasSameSiteInText(cookieOptions)) {
|
|
643
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
644
|
+
violations.push({
|
|
645
|
+
rule: this.ruleId,
|
|
646
|
+
source: filePath,
|
|
647
|
+
category: this.category,
|
|
648
|
+
line: lineNumber,
|
|
649
|
+
column: 1,
|
|
650
|
+
message: `Insecure session cookie: NextAuth "sessionToken" cookie missing SameSite attribute`,
|
|
651
|
+
severity: "error",
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Pattern 2: csrfToken configurations
|
|
657
|
+
const csrfTokenPattern =
|
|
658
|
+
/csrfToken\s*:\s*\{[^}]*options\s*:\s*\{([^}]*)\}/gi;
|
|
659
|
+
|
|
660
|
+
while ((match = csrfTokenPattern.exec(content)) !== null) {
|
|
661
|
+
const cookieOptions = match[1];
|
|
662
|
+
|
|
663
|
+
if (this.verbose) {
|
|
664
|
+
console.log(
|
|
665
|
+
`🔍 [${
|
|
666
|
+
this.ruleId
|
|
667
|
+
}] Regex: csrfToken Pattern match - options: "${cookieOptions.substring(
|
|
668
|
+
0,
|
|
669
|
+
50
|
|
670
|
+
)}..."`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if (!this.hasSameSiteInText(cookieOptions)) {
|
|
675
|
+
const lineNumber = this.getLineNumber(content, match.index);
|
|
676
|
+
violations.push({
|
|
677
|
+
rule: this.ruleId,
|
|
678
|
+
source: filePath,
|
|
679
|
+
category: this.category,
|
|
680
|
+
line: lineNumber,
|
|
681
|
+
column: 1,
|
|
682
|
+
message: `Insecure session cookie: NextAuth "csrfToken" cookie missing SameSite attribute`,
|
|
683
|
+
severity: "error",
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return violations;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// This helper method is no longer needed with the simplified approach
|
|
692
|
+
extractNextAuthCookieViolations(
|
|
693
|
+
configContent,
|
|
694
|
+
fullContent,
|
|
695
|
+
baseIndex,
|
|
696
|
+
filePath
|
|
697
|
+
) {
|
|
698
|
+
// Deprecated - using direct pattern matching instead
|
|
699
|
+
return [];
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
module.exports = S033RegexBasedAnalyzer;
|