@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,478 @@
1
+ /**
2
+ * S034 Regex-based Analyzer - Use __Host- prefix for Session Cookies
3
+ * Fallback analyzer for pattern-based detection
4
+ */
5
+
6
+ const fs = require("fs");
7
+
8
+ class S034RegexBasedAnalyzer {
9
+ constructor() {
10
+ this.ruleId = "S034";
11
+ this.ruleName = "Use __Host- prefix for Session Cookies";
12
+ this.description =
13
+ "Use __Host- prefix for Session Cookies to prevent subdomain sharing";
14
+ this.category = "security";
15
+ this.verbose = process.env.SUNLINT_DEBUG === "true";
16
+
17
+ // Enhanced regex patterns for framework detection
18
+ this.patterns = {
19
+ // Traditional Express.js patterns
20
+ cookieCall: /res\.cookie\s*\(\s*['"`]([^'"`]+)['"`]/g,
21
+ setCookieHeader:
22
+ /res\.setHeader\s*\(\s*['"`]Set-Cookie['"`]\s*,\s*['"`]([^'"`=]+)=/gi,
23
+ setCookieArray:
24
+ /res\.setHeader\s*\(\s*['"`]Set-Cookie['"`]\s*,\s*\[([^\]]+)\]/gi,
25
+ sessionMiddleware:
26
+ /session\s*\(\s*\{[^}]*name\s*:\s*['"`]([^'"`]+)['"`]/g,
27
+
28
+ // NestJS patterns
29
+ nestJSCookieSet:
30
+ /@Res\(\)\s+\w+:\s*Response[\s\S]*?\.cookie\s*\(\s*['"`]([^'"`]+)['"`]/g,
31
+ nestJSCookieDecorator: /@Cookies\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
32
+ nestJSResponseCookie: /response\.cookie\s*\(\s*['"`]([^'"`]+)['"`]/g,
33
+
34
+ // Next.js patterns
35
+ nextJSResponseCookies:
36
+ /response\.cookies\.set\s*\(\s*['"`]([^'"`]+)['"`]/g,
37
+ nextJSCookiesFunction: /cookies\(\)\.set\s*\(\s*['"`]([^'"`]+)['"`]/g,
38
+ nextJSCookieStore: /cookieStore\.set\s*\(\s*['"`]([^'"`]+)['"`]/g,
39
+
40
+ // Nuxt.js patterns
41
+ nuxtCookieSet: /useCookie\s*\(\s*['"`]([^'"`]+)['"`]/g,
42
+ nuxtServerCookie: /setCookie\s*\(\s*\w+\s*,\s*['"`]([^'"`]+)['"`]/g,
43
+ nuxtH3Cookie: /setCookie\s*\(\s*event\s*,\s*['"`]([^'"`]+)['"`]/g,
44
+
45
+ // Session cookie names (enhanced)
46
+ sessionCookieNames:
47
+ /^(session|sessionid|session_id|sid|connect\.sid|auth|auth_token|authentication|jwt|token|csrf|csrf_token|xsrf|login|user|userid|user_id|api_session|admin_session|app_auth|edge_session|server_session|refresh_token|next-auth\.session-token|next-auth\.csrf-token|custom-session-token)$/i,
48
+ };
49
+
50
+ this.violations = [];
51
+ }
52
+
53
+ async initialize(semanticEngine) {
54
+ this.semanticEngine = semanticEngine;
55
+ }
56
+
57
+ async analyze(filePath) {
58
+ if (this.verbose) {
59
+ console.log(
60
+ `🔍 [${this.ruleId}] Regex analysis starting for: ${filePath}`
61
+ );
62
+ }
63
+
64
+ this.violations = [];
65
+
66
+ try {
67
+ const content = fs.readFileSync(filePath, "utf8");
68
+ const lines = content.split("\n");
69
+
70
+ // Framework detection
71
+ const framework = this.detectFramework(content);
72
+
73
+ if (this.verbose) {
74
+ console.log(`🔍 [${this.ruleId}] Detected framework: ${framework}`);
75
+ }
76
+
77
+ // Traditional Express.js patterns
78
+ this.checkCookieCalls(content, lines, filePath);
79
+ this.checkSetCookieHeaders(content, lines, filePath);
80
+ this.checkSessionMiddleware(content, lines, filePath);
81
+
82
+ // Framework-specific patterns
83
+ this.checkNestJSPatterns(content, lines, filePath);
84
+ this.checkNextJSPatterns(content, lines, filePath);
85
+ this.checkNuxtJSPatterns(content, lines, filePath);
86
+ this.analyzeNextAuthConfig(content, lines, filePath);
87
+ } catch (error) {
88
+ console.warn(`⚠ [${this.ruleId}] Regex analysis error:`, error.message);
89
+ }
90
+
91
+ if (this.verbose) {
92
+ console.log(
93
+ `🔍 [${this.ruleId}] Regex analysis completed: ${this.violations.length} violations`
94
+ );
95
+ }
96
+
97
+ return this.violations;
98
+ }
99
+
100
+ detectFramework(content) {
101
+ if (
102
+ content.includes("@nestjs") ||
103
+ content.includes("@Res()") ||
104
+ content.includes("@Cookies")
105
+ ) {
106
+ return "NestJS";
107
+ }
108
+ if (
109
+ content.includes("next/") ||
110
+ content.includes("NextRequest") ||
111
+ content.includes("NextResponse")
112
+ ) {
113
+ return "Next.js";
114
+ }
115
+ if (
116
+ content.includes("#nuxt") ||
117
+ content.includes("useCookie") ||
118
+ content.includes("setCookie")
119
+ ) {
120
+ return "Nuxt.js";
121
+ }
122
+ return "Express.js";
123
+ }
124
+
125
+ checkCookieCalls(content, lines, filePath) {
126
+ let match;
127
+ this.patterns.cookieCall.lastIndex = 0;
128
+
129
+ while ((match = this.patterns.cookieCall.exec(content)) !== null) {
130
+ const cookieName = match[1];
131
+
132
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
133
+ const lineNumber = this.getLineNumber(content, match.index);
134
+ this.addViolation(
135
+ lineNumber,
136
+ 1,
137
+ `Insecure session cookie: Session cookie "${cookieName}" (Express.js) missing __Host- prefix`,
138
+ filePath
139
+ );
140
+ }
141
+ }
142
+ }
143
+
144
+ checkSetCookieHeaders(content, lines, filePath) {
145
+ // Check direct Set-Cookie headers
146
+ let match;
147
+ this.patterns.setCookieHeader.lastIndex = 0;
148
+
149
+ while ((match = this.patterns.setCookieHeader.exec(content)) !== null) {
150
+ const cookieName = match[1];
151
+
152
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
153
+ const lineNumber = this.getLineNumber(content, match.index);
154
+ this.addViolation(
155
+ lineNumber,
156
+ 1,
157
+ `Insecure session cookie: Session cookie "${cookieName}" in Set-Cookie header missing __Host- prefix`,
158
+ filePath
159
+ );
160
+ }
161
+ }
162
+
163
+ // Check Set-Cookie arrays
164
+ this.patterns.setCookieArray.lastIndex = 0;
165
+
166
+ while ((match = this.patterns.setCookieArray.exec(content)) !== null) {
167
+ const arrayContent = match[1];
168
+ const cookieMatches = arrayContent.match(/['"`]([^'"`=]+)=/g);
169
+
170
+ if (cookieMatches) {
171
+ cookieMatches.forEach((cookieMatch) => {
172
+ const cookieName = cookieMatch.replace(/['"`]/g, "").replace("=", "");
173
+
174
+ if (
175
+ this.isSessionCookie(cookieName) &&
176
+ !this.hasHostPrefix(cookieName)
177
+ ) {
178
+ const lineNumber = this.getLineNumber(content, match.index);
179
+ this.addViolation(
180
+ lineNumber,
181
+ 1,
182
+ `Insecure session cookie: Session cookie "${cookieName}" in Set-Cookie array missing __Host- prefix`,
183
+ filePath
184
+ );
185
+ }
186
+ });
187
+ }
188
+ }
189
+ }
190
+
191
+ checkSessionMiddleware(content, lines, filePath) {
192
+ let match;
193
+ this.patterns.sessionMiddleware.lastIndex = 0;
194
+
195
+ while ((match = this.patterns.sessionMiddleware.exec(content)) !== null) {
196
+ const cookieName = match[1];
197
+
198
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
199
+ const lineNumber = this.getLineNumber(content, match.index);
200
+ this.addViolation(
201
+ lineNumber,
202
+ 1,
203
+ `Insecure session cookie: Session middleware cookie name "${cookieName}" missing __Host- prefix`,
204
+ filePath
205
+ );
206
+ }
207
+ }
208
+ }
209
+
210
+ // Framework-specific analysis methods
211
+ checkNestJSPatterns(content, lines, filePath) {
212
+ // NestJS @Res() decorator with cookie setting
213
+ let match;
214
+ this.patterns.nestJSCookieSet.lastIndex = 0;
215
+
216
+ while ((match = this.patterns.nestJSCookieSet.exec(content)) !== null) {
217
+ const cookieName = match[1];
218
+
219
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
220
+ const lineNumber = this.getLineNumber(content, match.index);
221
+ this.addViolation(
222
+ lineNumber,
223
+ 1,
224
+ `Insecure session cookie: Session cookie "${cookieName}" (NestJS) missing __Host- prefix`,
225
+ filePath
226
+ );
227
+ }
228
+ }
229
+
230
+ // NestJS response.cookie() calls
231
+ this.patterns.nestJSResponseCookie.lastIndex = 0;
232
+
233
+ while (
234
+ (match = this.patterns.nestJSResponseCookie.exec(content)) !== null
235
+ ) {
236
+ const cookieName = match[1];
237
+
238
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
239
+ const lineNumber = this.getLineNumber(content, match.index);
240
+ this.addViolation(
241
+ lineNumber,
242
+ 1,
243
+ `Insecure session cookie: Session cookie "${cookieName}" (NestJS) missing __Host- prefix`,
244
+ filePath
245
+ );
246
+ }
247
+ }
248
+
249
+ // NestJS @Cookies decorator
250
+ this.patterns.nestJSCookieDecorator.lastIndex = 0;
251
+
252
+ while (
253
+ (match = this.patterns.nestJSCookieDecorator.exec(content)) !== null
254
+ ) {
255
+ const cookieName = match[1];
256
+
257
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
258
+ const lineNumber = this.getLineNumber(content, match.index);
259
+ this.addViolation(
260
+ lineNumber,
261
+ 1,
262
+ `Insecure session cookie: Session cookie "${cookieName}" (NestJS) missing __Host- prefix`,
263
+ filePath
264
+ );
265
+ }
266
+ }
267
+ }
268
+
269
+ checkNextJSPatterns(content, lines, filePath) {
270
+ // Next.js response.cookies.set()
271
+ let match;
272
+ this.patterns.nextJSResponseCookies.lastIndex = 0;
273
+
274
+ while (
275
+ (match = this.patterns.nextJSResponseCookies.exec(content)) !== null
276
+ ) {
277
+ const cookieName = match[1];
278
+
279
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
280
+ const lineNumber = this.getLineNumber(content, match.index);
281
+ this.addViolation(
282
+ lineNumber,
283
+ 1,
284
+ `Insecure session cookie: Session cookie "${cookieName}" (Next.js) missing __Host- prefix`,
285
+ filePath
286
+ );
287
+ }
288
+ }
289
+
290
+ // Next.js cookies().set()
291
+ this.patterns.nextJSCookiesFunction.lastIndex = 0;
292
+
293
+ while (
294
+ (match = this.patterns.nextJSCookiesFunction.exec(content)) !== null
295
+ ) {
296
+ const cookieName = match[1];
297
+
298
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
299
+ const lineNumber = this.getLineNumber(content, match.index);
300
+ this.addViolation(
301
+ lineNumber,
302
+ 1,
303
+ `Insecure session cookie: Session cookie "${cookieName}" (Next.js) missing __Host- prefix`,
304
+ filePath
305
+ );
306
+ }
307
+ }
308
+
309
+ // Next.js cookieStore.set()
310
+ this.patterns.nextJSCookieStore.lastIndex = 0;
311
+
312
+ while ((match = this.patterns.nextJSCookieStore.exec(content)) !== null) {
313
+ const cookieName = match[1];
314
+
315
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
316
+ const lineNumber = this.getLineNumber(content, match.index);
317
+ this.addViolation(
318
+ lineNumber,
319
+ 1,
320
+ `Insecure session cookie: Session cookie "${cookieName}" (Next.js) missing __Host- prefix`,
321
+ filePath
322
+ );
323
+ }
324
+ }
325
+ }
326
+
327
+ checkNuxtJSPatterns(content, lines, filePath) {
328
+ // Nuxt.js useCookie()
329
+ let match;
330
+ this.patterns.nuxtCookieSet.lastIndex = 0;
331
+
332
+ while ((match = this.patterns.nuxtCookieSet.exec(content)) !== null) {
333
+ const cookieName = match[1];
334
+
335
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
336
+ const lineNumber = this.getLineNumber(content, match.index);
337
+ this.addViolation(
338
+ lineNumber,
339
+ 1,
340
+ `Insecure session cookie: Session cookie "${cookieName}" (Nuxt.js) missing __Host- prefix`,
341
+ filePath
342
+ );
343
+ }
344
+ }
345
+
346
+ // Nuxt.js setCookie() server
347
+ this.patterns.nuxtServerCookie.lastIndex = 0;
348
+
349
+ while ((match = this.patterns.nuxtServerCookie.exec(content)) !== null) {
350
+ const cookieName = match[1];
351
+
352
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
353
+ const lineNumber = this.getLineNumber(content, match.index);
354
+ this.addViolation(
355
+ lineNumber,
356
+ 1,
357
+ `Insecure session cookie: Session cookie "${cookieName}" (Nuxt.js) missing __Host- prefix`,
358
+ filePath
359
+ );
360
+ }
361
+ }
362
+
363
+ // Nuxt.js H3 setCookie()
364
+ this.patterns.nuxtH3Cookie.lastIndex = 0;
365
+
366
+ while ((match = this.patterns.nuxtH3Cookie.exec(content)) !== null) {
367
+ const cookieName = match[1];
368
+
369
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
370
+ const lineNumber = this.getLineNumber(content, match.index);
371
+ this.addViolation(
372
+ lineNumber,
373
+ 1,
374
+ `Insecure session cookie: Session cookie "${cookieName}" (Nuxt.js) missing __Host- prefix`,
375
+ filePath
376
+ );
377
+ }
378
+ }
379
+ }
380
+
381
+ analyzeNextAuthConfig(content, lines, filePath) {
382
+ const violations = [];
383
+
384
+ // Simple approach: find NextAuth cookie configurations directly
385
+ // Pattern 1: sessionToken configurations
386
+ const sessionTokenPattern =
387
+ /sessionToken\s*:\s*\{[^}]*name\s*:\s*['"`]([^'"`]+)['"`]/gi;
388
+ let match;
389
+
390
+ while ((match = sessionTokenPattern.exec(content)) !== null) {
391
+ const cookieName = match[1];
392
+
393
+ if (this.verbose) {
394
+ console.log(
395
+ `🔍 [${this.ruleId}] Regex: sessionToken Pattern match - name: "${cookieName}"`
396
+ );
397
+ }
398
+
399
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
400
+ const lineNumber = this.getLineNumber(content, match.index);
401
+ this.addViolation(
402
+ lineNumber,
403
+ 1,
404
+ `Insecure session cookie: NextAuth "sessionToken" cookie "${cookieName}" missing __Host- prefix`,
405
+ filePath
406
+ );
407
+ }
408
+ }
409
+
410
+ // Pattern 2: csrfToken configurations
411
+ const csrfTokenPattern =
412
+ /csrfToken\s*:\s*\{[^}]*name\s*:\s*['"`]([^'"`]+)['"`]/gi;
413
+
414
+ while ((match = csrfTokenPattern.exec(content)) !== null) {
415
+ const cookieName = match[1];
416
+
417
+ if (this.verbose) {
418
+ console.log(
419
+ `🔍 [${this.ruleId}] Regex: csrfToken Pattern match - name: "${cookieName}"`
420
+ );
421
+ }
422
+
423
+ if (this.isSessionCookie(cookieName) && !this.hasHostPrefix(cookieName)) {
424
+ const lineNumber = this.getLineNumber(content, match.index);
425
+ this.addViolation(
426
+ lineNumber,
427
+ 1,
428
+ `Insecure session cookie: NextAuth "csrfToken" cookie "${cookieName}" missing __Host- prefix`,
429
+ filePath
430
+ );
431
+ }
432
+ }
433
+
434
+ return violations;
435
+ }
436
+
437
+ isSessionCookie(cookieName) {
438
+ return this.patterns.sessionCookieNames.test(cookieName);
439
+ }
440
+
441
+ hasHostPrefix(cookieName) {
442
+ return cookieName.startsWith("__Host-");
443
+ }
444
+
445
+ getLineNumber(content, position) {
446
+ const beforePosition = content.substring(0, position);
447
+ return beforePosition.split("\n").length;
448
+ }
449
+
450
+ addViolation(line, column, message, filePath) {
451
+ if (this.verbose) {
452
+ console.log(
453
+ `🔍 [${
454
+ this.ruleId
455
+ }] Regex violation at line ${line}, column ${column}: ${message.substring(
456
+ 0,
457
+ 50
458
+ )}...`
459
+ );
460
+ }
461
+
462
+ this.violations.push({
463
+ rule: this.ruleId,
464
+ source: filePath,
465
+ category: this.category,
466
+ line: line,
467
+ column: column,
468
+ message: message,
469
+ severity: "warning",
470
+ });
471
+ }
472
+
473
+ cleanup() {
474
+ this.violations = [];
475
+ }
476
+ }
477
+
478
+ module.exports = S034RegexBasedAnalyzer;