@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.
Files changed (59) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +5 -3
  3. package/config/rules/enhanced-rules-registry.json +144 -33
  4. package/core/analysis-orchestrator.js +167 -42
  5. package/core/auto-performance-manager.js +243 -0
  6. package/core/cli-action-handler.js +9 -1
  7. package/core/cli-program.js +19 -5
  8. package/core/constants/defaults.js +56 -0
  9. package/core/performance-optimizer.js +271 -0
  10. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +151 -0
  11. package/docs/FILE_LIMITS_EXPLANATION.md +190 -0
  12. package/docs/PERFORMANCE.md +311 -0
  13. package/docs/PERFORMANCE_MIGRATION_GUIDE.md +368 -0
  14. package/docs/PERFORMANCE_OPTIMIZATION_PLAN.md +255 -0
  15. package/docs/QUICK_FILE_LIMITS.md +64 -0
  16. package/docs/SIMPLIFIED_USAGE_GUIDE.md +208 -0
  17. package/engines/heuristic-engine.js +182 -5
  18. package/package.json +2 -1
  19. package/rules/common/C048_no_bypass_architectural_layers/analyzer.js +180 -0
  20. package/rules/common/C048_no_bypass_architectural_layers/config.json +50 -0
  21. package/rules/common/C048_no_bypass_architectural_layers/symbol-based-analyzer.js +235 -0
  22. package/rules/common/C052_parsing_or_data_transformation/analyzer.js +180 -0
  23. package/rules/common/C052_parsing_or_data_transformation/config.json +50 -0
  24. package/rules/common/C052_parsing_or_data_transformation/symbol-based-analyzer.js +132 -0
  25. package/rules/index.js +2 -0
  26. package/rules/security/S017_use_parameterized_queries/README.md +128 -0
  27. package/rules/security/S017_use_parameterized_queries/analyzer.js +286 -0
  28. package/rules/security/S017_use_parameterized_queries/config.json +109 -0
  29. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +541 -0
  30. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +777 -0
  31. package/rules/security/S031_secure_session_cookies/README.md +127 -0
  32. package/rules/security/S031_secure_session_cookies/analyzer.js +245 -0
  33. package/rules/security/S031_secure_session_cookies/config.json +86 -0
  34. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +196 -0
  35. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +1084 -0
  36. package/rules/security/S032_httponly_session_cookies/FRAMEWORK_SUPPORT.md +209 -0
  37. package/rules/security/S032_httponly_session_cookies/README.md +184 -0
  38. package/rules/security/S032_httponly_session_cookies/analyzer.js +282 -0
  39. package/rules/security/S032_httponly_session_cookies/config.json +96 -0
  40. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +715 -0
  41. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +1348 -0
  42. package/rules/security/S033_samesite_session_cookies/README.md +227 -0
  43. package/rules/security/S033_samesite_session_cookies/analyzer.js +242 -0
  44. package/rules/security/S033_samesite_session_cookies/config.json +87 -0
  45. package/rules/security/S033_samesite_session_cookies/regex-based-analyzer.js +703 -0
  46. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +732 -0
  47. package/rules/security/S034_host_prefix_session_cookies/README.md +204 -0
  48. package/rules/security/S034_host_prefix_session_cookies/analyzer.js +290 -0
  49. package/rules/security/S034_host_prefix_session_cookies/config.json +62 -0
  50. package/rules/security/S034_host_prefix_session_cookies/regex-based-analyzer.js +478 -0
  51. package/rules/security/S034_host_prefix_session_cookies/symbol-based-analyzer.js +277 -0
  52. package/rules/security/S035_path_session_cookies/README.md +257 -0
  53. package/rules/security/S035_path_session_cookies/analyzer.js +316 -0
  54. package/rules/security/S035_path_session_cookies/config.json +99 -0
  55. package/rules/security/S035_path_session_cookies/regex-based-analyzer.js +724 -0
  56. package/rules/security/S035_path_session_cookies/symbol-based-analyzer.js +373 -0
  57. package/scripts/batch-processing-demo.js +334 -0
  58. package/scripts/performance-test.js +541 -0
  59. package/scripts/quick-performance-test.js +108 -0
@@ -0,0 +1,732 @@
1
+ /**
2
+ * S033 Symbol-Based Analyzer - Set SameSite attribute for Session Cookies
3
+ * Uses TypeScript compiler API for semantic analysis
4
+ */
5
+
6
+ const ts = require("typescript");
7
+
8
+ class S033SymbolBasedAnalyzer {
9
+ constructor(semanticEngine = null) {
10
+ this.semanticEngine = semanticEngine;
11
+ this.ruleId = "S033";
12
+ this.category = "security";
13
+
14
+ // Session cookie indicators
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
+ "user_session",
30
+ "api_session",
31
+ "login_session",
32
+ "auth_token",
33
+ "csrf_token",
34
+ "refresh_token",
35
+ ];
36
+
37
+ // Cookie methods that need security checking (enhanced with framework support)
38
+ this.cookieMethods = [
39
+ "setCookie", // Nuxt.js H3
40
+ "useCookie", // Nuxt.js composable
41
+ "cookie", // Express.js res.cookie
42
+ "set", // Next.js response.cookies.set
43
+ "append", // Express.js res.append
44
+ "session", // Session middleware
45
+ "setHeader", // Node.js res.setHeader
46
+ "writeHead", // Node.js res.writeHead
47
+ ];
48
+
49
+ // Acceptable SameSite values
50
+ this.acceptableValues = ["strict", "lax", "none"];
51
+ this.recommendedValues = ["strict", "lax"];
52
+ }
53
+
54
+ /**
55
+ * Initialize analyzer with semantic engine
56
+ */
57
+ async initialize(semanticEngine) {
58
+ this.semanticEngine = semanticEngine;
59
+ if (this.verbose) {
60
+ console.log(`🔍 [${this.ruleId}] Symbol: Semantic engine initialized`);
61
+ }
62
+ }
63
+
64
+ async analyze(filePath) {
65
+ if (this.verbose) {
66
+ console.log(
67
+ `🔍 [${this.ruleId}] Symbol: Starting analysis for ${filePath}`
68
+ );
69
+ }
70
+
71
+ if (!this.semanticEngine) {
72
+ if (this.verbose) {
73
+ console.log(
74
+ `🔍 [${this.ruleId}] Symbol: No semantic engine available, skipping`
75
+ );
76
+ }
77
+ return [];
78
+ }
79
+
80
+ try {
81
+ const sourceFile = this.semanticEngine.getSourceFile(filePath);
82
+ if (!sourceFile) {
83
+ if (this.verbose) {
84
+ console.log(
85
+ `🔍 [${this.ruleId}] Symbol: No source file found, trying ts-morph fallback`
86
+ );
87
+ }
88
+ return await this.analyzeTsMorph(filePath);
89
+ }
90
+
91
+ if (this.verbose) {
92
+ console.log(`🔧 [${this.ruleId}] Source file found, analyzing...`);
93
+ }
94
+
95
+ return await this.analyzeSourceFile(sourceFile, filePath);
96
+ } catch (error) {
97
+ if (this.verbose) {
98
+ console.log(
99
+ `🔍 [${this.ruleId}] Symbol: Error in analysis:`,
100
+ error.message
101
+ );
102
+ }
103
+ return [];
104
+ }
105
+ }
106
+
107
+ async analyzeTsMorph(filePath) {
108
+ try {
109
+ if (this.verbose) {
110
+ console.log(`🔍 [${this.ruleId}] Symbol: Starting ts-morph analysis`);
111
+ }
112
+
113
+ const { Project } = require("ts-morph");
114
+ const project = new Project();
115
+ const sourceFile = project.addSourceFileAtPath(filePath);
116
+
117
+ return await this.analyzeSourceFile(sourceFile, filePath);
118
+ } catch (error) {
119
+ if (this.verbose) {
120
+ console.log(
121
+ `🔍 [${this.ruleId}] Symbol: ts-morph analysis failed:`,
122
+ error.message
123
+ );
124
+ }
125
+ return [];
126
+ }
127
+ }
128
+
129
+ async analyzeSourceFile(sourceFile, filePath) {
130
+ const violations = [];
131
+
132
+ try {
133
+ if (this.verbose) {
134
+ console.log(`🔍 [${this.ruleId}] Symbol: Starting ts-morph analysis`);
135
+ }
136
+
137
+ const callExpressions = sourceFile.getDescendantsOfKind
138
+ ? sourceFile.getDescendantsOfKind(
139
+ require("typescript").SyntaxKind.CallExpression
140
+ )
141
+ : [];
142
+
143
+ if (this.verbose) {
144
+ console.log(
145
+ `🔍 [${this.ruleId}] Symbol: Found ${callExpressions.length} call expressions`
146
+ );
147
+ }
148
+
149
+ for (const callNode of callExpressions) {
150
+ try {
151
+ if (this.verbose) {
152
+ const expressionText = callNode.getExpression().getText();
153
+ console.log(
154
+ `🔍 [${this.ruleId}] Symbol: Expression kind: ${callNode
155
+ .getExpression()
156
+ .getKind()}, text: "${expressionText.substring(0, 50)}..."`
157
+ );
158
+ }
159
+
160
+ // Handle property access expressions (e.g., res.cookie, res.setHeader)
161
+ if (
162
+ callNode.getExpression().getKind() ===
163
+ require("typescript").SyntaxKind.PropertyAccessExpression
164
+ ) {
165
+ const methodName = callNode.getExpression().getName();
166
+
167
+ if (this.verbose) {
168
+ console.log(
169
+ `🔍 [${this.ruleId}] Symbol: PropertyAccess method name: "${methodName}"`
170
+ );
171
+ }
172
+
173
+ if (this.verbose) {
174
+ console.log(
175
+ `🔍 [${this.ruleId}] Symbol: ts-morph Method call detected: "${methodName}"`
176
+ );
177
+ }
178
+
179
+ if (!this.cookieMethods.includes(methodName)) {
180
+ if (this.verbose) {
181
+ console.log(
182
+ `🔍 [${this.ruleId}] Symbol: Method "${methodName}" not in cookieMethods list`
183
+ );
184
+ }
185
+ continue;
186
+ }
187
+
188
+ if (this.verbose) {
189
+ console.log(
190
+ `🔍 [${this.ruleId}] Symbol: Method "${methodName}" found in cookieMethods, proceeding...`
191
+ );
192
+ }
193
+
194
+ // Special handling for setHeader method
195
+ if (methodName === "setHeader") {
196
+ const violation = this.analyzeSetHeaderCall(callNode, sourceFile);
197
+ if (violation) {
198
+ violations.push(violation);
199
+ }
200
+ continue;
201
+ }
202
+
203
+ // Analyze cookie method calls
204
+ const violation = this.analyzeCookieCall(
205
+ callNode,
206
+ sourceFile,
207
+ methodName
208
+ );
209
+ if (violation) {
210
+ violations.push(violation);
211
+ }
212
+ }
213
+ } catch (error) {
214
+ if (this.verbose) {
215
+ console.log(
216
+ `🔍 [${this.ruleId}] Symbol: Error analyzing call expression:`,
217
+ error.message
218
+ );
219
+ }
220
+ }
221
+ }
222
+
223
+ if (this.verbose) {
224
+ console.log(
225
+ `🔍 [${this.ruleId}] Symbol: Analysis completed. Found ${violations.length} violations`
226
+ );
227
+ }
228
+
229
+ return violations;
230
+ } catch (error) {
231
+ if (this.verbose) {
232
+ console.log(
233
+ `🔍 [${this.ruleId}] Symbol: Error in source file analysis:`,
234
+ error.message
235
+ );
236
+ }
237
+ return [];
238
+ }
239
+ }
240
+
241
+ analyzeSetHeaderCall(callNode, sourceFile) {
242
+ try {
243
+ const args = callNode.getArguments();
244
+
245
+ if (args.length < 2) return null;
246
+
247
+ const headerName = args[0].getText().replace(/['"]/g, "");
248
+ if (headerName.toLowerCase() !== "set-cookie") return null;
249
+
250
+ const line = sourceFile.getLineAndColumnAtPos(callNode.getStart()).line;
251
+
252
+ if (this.verbose) {
253
+ console.log(
254
+ `🔍 [${this.ruleId}] Symbol: Special setHeader handling triggered for line ${line}`
255
+ );
256
+ }
257
+
258
+ const cookieValue = args[1].getText();
259
+
260
+ // Check for SameSite in Set-Cookie header
261
+ if (
262
+ this.isSessionCookieHeader(cookieValue) &&
263
+ !this.hasSameSiteAttribute(cookieValue)
264
+ ) {
265
+ const cookieName = this.extractCookieNameFromHeader(cookieValue);
266
+ return this.createViolation(
267
+ sourceFile,
268
+ callNode,
269
+ `Session cookie "${cookieName}" in Set-Cookie header missing SameSite attribute`
270
+ );
271
+ }
272
+
273
+ return null;
274
+ } catch (error) {
275
+ if (this.verbose) {
276
+ console.log(
277
+ `🔍 [${this.ruleId}] Symbol: Error analyzing setHeader:`,
278
+ error.message
279
+ );
280
+ }
281
+ return null;
282
+ }
283
+ }
284
+
285
+ analyzeCookieCall(callNode, sourceFile, methodName) {
286
+ try {
287
+ const args = callNode.getArguments();
288
+
289
+ if (args.length < 1) return null;
290
+
291
+ // Get cookie name using enhanced method
292
+ const cookieName = this.extractMorphCookieName(callNode);
293
+
294
+ if (this.verbose) {
295
+ console.log(
296
+ `🔍 [${
297
+ this.ruleId
298
+ }] Symbol: Cookie "${cookieName}" session check: ${this.isSessionCookie(
299
+ cookieName
300
+ )}`
301
+ );
302
+ }
303
+
304
+ // Only analyze session cookies
305
+ if (!this.isSessionCookie(cookieName)) {
306
+ return null;
307
+ }
308
+
309
+ // Get framework context for better messaging
310
+ const framework = this.detectFramework(callNode, sourceFile);
311
+
312
+ // Check if SameSite is configured based on method type
313
+ const hasSameSite = this.checkSameSiteInCall(callNode, methodName);
314
+
315
+ if (!hasSameSite) {
316
+ const frameworkMessage =
317
+ framework !== "Framework" ? ` (${framework})` : "";
318
+ return this.createViolation(
319
+ sourceFile,
320
+ callNode,
321
+ `Session cookie "${cookieName}"${frameworkMessage} missing SameSite attribute`
322
+ );
323
+ }
324
+
325
+ return null;
326
+ } catch (error) {
327
+ if (this.verbose) {
328
+ console.log(
329
+ `🔍 [${this.ruleId}] Symbol: Error analyzing cookie call:`,
330
+ error.message
331
+ );
332
+ }
333
+ return null;
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Check for SameSite attribute in cookie call
339
+ */
340
+ checkSameSiteInCall(callNode, methodName) {
341
+ try {
342
+ const args = callNode.getArguments();
343
+
344
+ // For setCookie(event, name, value, options), options is at index 3
345
+ let optionsIndex = 2;
346
+ if (methodName === "setCookie" && args.length >= 4) {
347
+ optionsIndex = 3; // Options argument for setCookie
348
+ } else if (methodName === "useCookie" && args.length >= 2) {
349
+ optionsIndex = 1; // Options argument for useCookie
350
+ }
351
+
352
+ // Check if options argument exists
353
+ if (args.length <= optionsIndex) {
354
+ // No options object provided
355
+ return false;
356
+ }
357
+
358
+ const optionsArg = args[optionsIndex];
359
+ return this.checkSameSiteInOptions(optionsArg, callNode);
360
+ } catch (error) {
361
+ if (this.verbose) {
362
+ console.log(
363
+ `🔍 [${this.ruleId}] Symbol: Error checking SameSite in call:`,
364
+ error.message
365
+ );
366
+ }
367
+ return false;
368
+ }
369
+ }
370
+
371
+ checkSameSiteInOptions(optionsArg, callNode) {
372
+ try {
373
+ const SyntaxKind = require("typescript").SyntaxKind;
374
+
375
+ if (optionsArg.getKind() === SyntaxKind.ObjectLiteralExpression) {
376
+ let text = optionsArg.getText();
377
+
378
+ if (this.verbose) {
379
+ console.log(
380
+ `🔍 [${
381
+ this.ruleId
382
+ }] Symbol: Checking object literal: ${text.substring(0, 200)}...`
383
+ );
384
+ }
385
+
386
+ // Remove comments to avoid false positives
387
+ const textWithoutComments = text
388
+ .replace(/\/\/.*$/gm, "")
389
+ .replace(/\/\*[\s\S]*?\*\//g, "");
390
+
391
+ // Check for explicitly configured SameSite
392
+ if (this.hasSameSiteInText(textWithoutComments)) {
393
+ if (this.verbose) {
394
+ console.log(
395
+ `🔍 [${this.ruleId}] Symbol: SameSite found in object literal`
396
+ );
397
+ }
398
+ return true;
399
+ }
400
+
401
+ // Check for spread elements within the object literal
402
+ const hasSpreadElements = text.includes("...");
403
+ if (hasSpreadElements) {
404
+ if (this.verbose) {
405
+ console.log(
406
+ `🔍 [${this.ruleId}] Symbol: Object literal contains spread elements, checking each...`
407
+ );
408
+ }
409
+
410
+ const spreadMatches = text.match(/\.\.\.([^,}]+)/g);
411
+ if (spreadMatches) {
412
+ for (const spreadMatch of spreadMatches) {
413
+ const reference = spreadMatch.replace(/^\.\.\./g, "").trim();
414
+ if (this.verbose) {
415
+ console.log(
416
+ `🔍 [${this.ruleId}] Symbol: Checking spread reference: ${reference}`
417
+ );
418
+ }
419
+
420
+ if (this.isSecureConfigReference(reference, callNode)) {
421
+ if (this.verbose) {
422
+ console.log(
423
+ `🔍 [${this.ruleId}] Symbol: ✅ Secure spread reference detected: ${reference}`
424
+ );
425
+ }
426
+ return true;
427
+ }
428
+ }
429
+ }
430
+ }
431
+
432
+ if (this.verbose) {
433
+ console.log(
434
+ `🔍 [${this.ruleId}] Symbol: Object literal missing SameSite and no secure spreads`
435
+ );
436
+ }
437
+ return false;
438
+ } else if (
439
+ optionsArg.getKind() === SyntaxKind.Identifier ||
440
+ optionsArg.getKind() === SyntaxKind.PropertyAccessExpression
441
+ ) {
442
+ const argText = optionsArg.getText();
443
+ if (this.verbose) {
444
+ console.log(
445
+ `🔍 [${this.ruleId}] Symbol: Found reference: ${argText}`
446
+ );
447
+ }
448
+
449
+ if (this.isSecureConfigReference(argText, callNode)) {
450
+ if (this.verbose) {
451
+ console.log(
452
+ `🔍 [${this.ruleId}] Symbol: ✅ Secure config reference detected: ${argText}`
453
+ );
454
+ }
455
+ return true;
456
+ }
457
+ }
458
+
459
+ return false;
460
+ } catch (error) {
461
+ if (this.verbose) {
462
+ console.log(
463
+ `🔍 [${this.ruleId}] Symbol: Error checking SameSite in options:`,
464
+ error.message
465
+ );
466
+ }
467
+ return false;
468
+ }
469
+ }
470
+
471
+ /**
472
+ * Check if reference points to secure configuration
473
+ */
474
+ isSecureConfigReference(argText, callNode) {
475
+ try {
476
+ const sourceFile = callNode.getSourceFile();
477
+ const fileText = sourceFile.getFullText();
478
+
479
+ // Handle this.cookieConfig pattern
480
+ if (argText.includes("cookieConfig") || argText.includes("config")) {
481
+ const configName = argText.split(".").pop();
482
+
483
+ // Look for the exact config definition and check if it contains sameSite
484
+ const configDefPattern = new RegExp(
485
+ `(?:private|public|readonly|const|let|var)\\s+(?:readonly\\s+)?${configName}\\s*=\\s*{[^}]*}`,
486
+ "gis"
487
+ );
488
+
489
+ const configMatch = fileText.match(configDefPattern);
490
+
491
+ if (this.verbose) {
492
+ console.log(
493
+ `🔍 [${this.ruleId}] Symbol: Looking for config definition of "${configName}"`
494
+ );
495
+ console.log(
496
+ `🔍 [${this.ruleId}] Symbol: Config match found:`,
497
+ configMatch ? configMatch[0] : "none"
498
+ );
499
+ }
500
+
501
+ if (configMatch) {
502
+ let configContent = configMatch[0];
503
+
504
+ // Remove comments to avoid false positives
505
+ configContent = configContent
506
+ .replace(/\/\/.*$/gm, "")
507
+ .replace(/\/\*[\s\S]*?\*\//g, "");
508
+
509
+ const hasSameSite = this.hasSameSiteInText(configContent);
510
+
511
+ if (this.verbose) {
512
+ console.log(
513
+ `🔍 [${this.ruleId}] Symbol: Config content (comments removed):`,
514
+ configContent
515
+ );
516
+ console.log(
517
+ `🔍 [${this.ruleId}] Symbol: SameSite found:`,
518
+ hasSameSite
519
+ );
520
+ }
521
+
522
+ return hasSameSite;
523
+ }
524
+
525
+ if (this.verbose) {
526
+ console.log(
527
+ `🔍 [${this.ruleId}] Symbol: No config definition found for "${configName}"`
528
+ );
529
+ }
530
+
531
+ return false;
532
+ }
533
+
534
+ // Handle variable references
535
+ const varPattern = new RegExp(
536
+ `(?:const|let|var)\\s+${argText}\\s*=\\s*{[^}]*sameSite\\s*:`,
537
+ "i"
538
+ );
539
+ return varPattern.test(fileText);
540
+ } catch (error) {
541
+ if (this.verbose) {
542
+ console.log(
543
+ `🔍 [${this.ruleId}] Symbol: Error checking config reference:`,
544
+ error.message
545
+ );
546
+ }
547
+ return false;
548
+ }
549
+ }
550
+
551
+ hasSameSiteInText(text) {
552
+ // Check for sameSite with acceptable values
553
+ const sameSitePatterns = [
554
+ /sameSite\s*:\s*['"](strict|lax|none)['"]|sameSite\s*:\s*(strict|lax|none)/i,
555
+ /sameSite\s*:\s*.*\?\s*['"](strict|lax|none)['"]\s*:\s*['"](strict|lax|none)['"]/i, // Ternary operator
556
+ /sameSite\s*:\s*.*\?\s*(strict|lax|none)\s*:\s*(strict|lax|none)/i, // Ternary without quotes
557
+ ];
558
+
559
+ return sameSitePatterns.some((pattern) => pattern.test(text));
560
+ }
561
+
562
+ hasSameSiteAttribute(cookieValue) {
563
+ // Check for SameSite in Set-Cookie header
564
+ const sameSitePattern = /SameSite=(Strict|Lax|None)/i;
565
+ return sameSitePattern.test(cookieValue);
566
+ }
567
+
568
+ isSessionCookie(cookieName) {
569
+ const name = cookieName.toLowerCase();
570
+ return this.sessionIndicators.some((indicator) =>
571
+ name.includes(indicator.toLowerCase())
572
+ );
573
+ }
574
+
575
+ isSessionCookieHeader(cookieValue) {
576
+ // Extract cookie name from Set-Cookie header value
577
+ const nameMatch = cookieValue.match(/^[^=]+/);
578
+ if (!nameMatch) return false;
579
+
580
+ const cookieName = nameMatch[0].replace(/['"]/g, "").trim();
581
+ return this.isSessionCookie(cookieName);
582
+ }
583
+
584
+ extractCookieNameFromHeader(cookieValue) {
585
+ const nameMatch = cookieValue.match(/^[^=]+/);
586
+ return nameMatch ? nameMatch[0].replace(/['"]/g, "").trim() : "unknown";
587
+ }
588
+
589
+ createViolation(sourceFile, callNode, message) {
590
+ try {
591
+ const start = callNode.getStart();
592
+ const lineAndChar = sourceFile.getLineAndColumnAtPos(start);
593
+
594
+ return {
595
+ rule: this.ruleId,
596
+ source: sourceFile.getFilePath(),
597
+ category: this.category,
598
+ line: lineAndChar.line,
599
+ column: lineAndChar.column,
600
+ message: `Insecure session cookie: ${message}`,
601
+ severity: "error",
602
+ };
603
+ } catch (error) {
604
+ if (this.verbose) {
605
+ console.log(
606
+ `🔍 [${this.ruleId}] Symbol: Error creating violation:`,
607
+ error.message
608
+ );
609
+ }
610
+ return null;
611
+ }
612
+ }
613
+
614
+ /**
615
+ * Detect framework from method call context
616
+ */
617
+ detectFramework(callNode, sourceFile) {
618
+ const callText = callNode.getText();
619
+ const fileContent = sourceFile.getFullText();
620
+
621
+ // Check imports to detect framework
622
+ if (
623
+ fileContent.includes("@nestjs/common") ||
624
+ fileContent.includes("@Res()")
625
+ ) {
626
+ return "NestJS";
627
+ }
628
+
629
+ if (
630
+ fileContent.includes("next/server") ||
631
+ fileContent.includes("NextResponse") ||
632
+ fileContent.includes("NextAuth")
633
+ ) {
634
+ return "Next.js";
635
+ }
636
+
637
+ if (
638
+ callText.includes("useCookie") ||
639
+ fileContent.includes("defineEventHandler") ||
640
+ fileContent.includes("setCookie")
641
+ ) {
642
+ return "Nuxt.js";
643
+ }
644
+
645
+ return "Framework";
646
+ }
647
+
648
+ /**
649
+ * Enhanced cookie name extraction with framework support
650
+ */
651
+ extractMorphCookieName(callNode) {
652
+ try {
653
+ const args = callNode.getArguments();
654
+ if (args && args.length > 0) {
655
+ const methodName = this.getMorphMethodName(callNode);
656
+
657
+ // Handle setCookie(event, "cookieName", "value", options) pattern
658
+ if (methodName === "setCookie" && args.length >= 2) {
659
+ const secondArg = args[1]; // Cookie name is second argument
660
+ if (secondArg && secondArg.getText) {
661
+ const text = secondArg.getText();
662
+ return text.replace(/['"]/g, ""); // Remove quotes
663
+ }
664
+ }
665
+
666
+ // Handle standard cookie methods (cookieName is first argument)
667
+ const firstArg = args[0];
668
+ if (firstArg && firstArg.getText) {
669
+ const text = firstArg.getText();
670
+ return text.replace(/['"]/g, ""); // Remove quotes
671
+ }
672
+ }
673
+ } catch (error) {
674
+ if (this.verbose) {
675
+ console.log(
676
+ `🔍 [${this.ruleId}] Symbol: Error extracting cookie name:`,
677
+ error.message
678
+ );
679
+ }
680
+ }
681
+ return null;
682
+ }
683
+
684
+ /**
685
+ * Enhanced method name detection with framework support
686
+ */
687
+ getMorphMethodName(callNode) {
688
+ try {
689
+ const expression = callNode.getExpression();
690
+ const ts = require("typescript");
691
+
692
+ // Handle property access expressions (obj.method)
693
+ if (expression.getKind() === ts.SyntaxKind.PropertyAccessExpression) {
694
+ const propertyName = expression.getNameNode().getText();
695
+
696
+ // Check for chained method calls like response.cookies.set
697
+ if (propertyName === "set" || propertyName === "cookie") {
698
+ const objectExpression = expression.getExpression();
699
+ if (
700
+ objectExpression.getKind() ===
701
+ ts.SyntaxKind.PropertyAccessExpression
702
+ ) {
703
+ const parentProperty = objectExpression.getNameNode().getText();
704
+ if (parentProperty === "cookies") {
705
+ return "set"; // For cookies.set()
706
+ }
707
+ }
708
+ return propertyName;
709
+ }
710
+
711
+ return propertyName;
712
+ }
713
+
714
+ // Handle direct function calls
715
+ if (expression.getKind() === ts.SyntaxKind.Identifier) {
716
+ return expression.getText();
717
+ }
718
+
719
+ return "";
720
+ } catch (error) {
721
+ if (this.verbose) {
722
+ console.log(
723
+ `🔍 [${this.ruleId}] Symbol: Error getting method name:`,
724
+ error.message
725
+ );
726
+ }
727
+ return "";
728
+ }
729
+ }
730
+ }
731
+
732
+ module.exports = S033SymbolBasedAnalyzer;