@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,316 @@
1
+ /**
2
+ * S035 Main Analyzer - Set Path 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=S035 --input=examples/rule-test-fixtures/rules/S035_path_session_cookies --engine=heuristic
6
+ */
7
+
8
+ const S035SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+ const S035RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
+
11
+ class S035Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S035] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S035] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S035";
23
+ this.ruleName = "Set Path attribute for Session Cookies";
24
+ this.description =
25
+ "Set Path attribute for Session Cookies to limit access scope. This restricts where cookies can be sent, reducing the attack surface by limiting cookie access to specific paths.";
26
+ this.semanticEngine = options.semanticEngine || null;
27
+ this.verbose = options.verbose || false;
28
+
29
+ // Configuration - Use symbol-based as primary, regex for additional coverage
30
+ this.config = {
31
+ useSymbolBased: true, // Primary approach
32
+ fallbackToRegex: true, // Additional coverage
33
+ regexBasedOnly: false, // Can be set to true for pure mode
34
+ };
35
+
36
+ // Initialize analyzers
37
+ try {
38
+ this.symbolAnalyzer = new S035SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG) {
40
+ console.log(`🔧 [S035] Symbol analyzer created successfully`);
41
+ }
42
+ } catch (error) {
43
+ console.error(`🔧 [S035] Error creating symbol analyzer:`, error);
44
+ }
45
+
46
+ try {
47
+ this.regexAnalyzer = new S035RegexBasedAnalyzer(this.semanticEngine);
48
+ if (process.env.SUNLINT_DEBUG) {
49
+ console.log(`🔧 [S035] Regex analyzer created successfully`);
50
+ }
51
+ } catch (error) {
52
+ console.error(`🔧 [S035] 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(`🔧 [S035] 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(`🔧 [S035] 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(`🔍 [S035] 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
+ `🔧 [S035] 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(`🔧 [S035] 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
+ `🔧 [S035] File ${filePath}: Found ${fileViolations.length} violations`
117
+ );
118
+ }
119
+ } catch (error) {
120
+ console.warn(
121
+ `⚠ [S035] Analysis failed for ${filePath}:`,
122
+ error.message
123
+ );
124
+ }
125
+ }
126
+
127
+ if (process.env.SUNLINT_DEBUG) {
128
+ console.log(`🔧 [S035] 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(`🔍 [S035] analyzeFile() called for: ${filePath}`);
137
+ }
138
+
139
+ // Create a Map to track unique violations and prevent duplicates
140
+ const violationMap = new Map();
141
+ const lineToViolationMap = new Map(); // Track which lines already have violations
142
+ const cookieToViolationMap = new Map(); // Track which cookies already have violations
143
+
144
+ // 1. Try Symbol-based analysis first (primary)
145
+ if (
146
+ this.config.useSymbolBased &&
147
+ this.semanticEngine?.project &&
148
+ this.semanticEngine?.initialized
149
+ ) {
150
+ try {
151
+ if (process.env.SUNLINT_DEBUG) {
152
+ console.log(`🔧 [S035] Trying symbol-based analysis...`);
153
+ }
154
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
155
+ if (sourceFile) {
156
+ if (process.env.SUNLINT_DEBUG) {
157
+ console.log(`🔧 [S035] Source file found, analyzing...`);
158
+ }
159
+ const symbolViolations = await this.symbolAnalyzer.analyze(
160
+ sourceFile,
161
+ filePath
162
+ );
163
+
164
+ // Add symbol violations first (higher priority)
165
+ symbolViolations.forEach((violation) => {
166
+ // Extract cookie name from message for better deduplication
167
+ const cookieNameMatch = violation.message.match(
168
+ /(?:Session (?:cookie|middleware cookie name)|Set-Cookie.*?|NextAuth.*?) "([^"]+)"/
169
+ );
170
+ const cookieName = cookieNameMatch ? cookieNameMatch[1] : "unknown";
171
+
172
+ // Use specific key including column for exact match
173
+ const specificKey = `${violation.line}:${
174
+ violation.column || 1
175
+ }:${cookieName}`;
176
+ const lineKey = `${violation.line}:${cookieName}`;
177
+
178
+ if (!violationMap.has(specificKey)) {
179
+ violationMap.set(specificKey, {
180
+ ...violation,
181
+ source: "symbol", // Track source for debugging
182
+ });
183
+
184
+ // Also track by line for regex deduplication
185
+ lineToViolationMap.set(lineKey, specificKey);
186
+
187
+ // Track by cookie name for broader deduplication
188
+ const cookieKey = `${cookieName}:${violation.line}`;
189
+ cookieToViolationMap.set(cookieKey, specificKey);
190
+ }
191
+ });
192
+
193
+ if (process.env.SUNLINT_DEBUG) {
194
+ console.log(
195
+ `🔧 [S035] Symbol analysis completed: ${symbolViolations.length} violations`
196
+ );
197
+ }
198
+ } else {
199
+ if (process.env.SUNLINT_DEBUG) {
200
+ console.log(`🔧 [S035] Source file not found, falling back...`);
201
+ }
202
+ }
203
+ } catch (error) {
204
+ console.warn(`⚠ [S035] Symbol analysis failed:`, error.message);
205
+ }
206
+ }
207
+
208
+ // 2. Try Regex-based analysis (fallback or additional)
209
+ if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
210
+ try {
211
+ if (process.env.SUNLINT_DEBUG) {
212
+ console.log(`🔧 [S035] Trying regex-based analysis...`);
213
+ }
214
+ const regexViolations = await this.regexAnalyzer.analyze(filePath);
215
+
216
+ // Add regex violations only if not already covered by symbol analysis
217
+ regexViolations.forEach((violation) => {
218
+ // Extract cookie name from message for better deduplication
219
+ const cookieNameMatch =
220
+ violation.message.match(
221
+ /(?:Session (?:cookie|middleware cookie name)|Set-Cookie.*?|NextAuth.*?) "([^"]+)"/
222
+ ) ||
223
+ violation.message.match(
224
+ /Insecure session cookie:.*?(?:Session cookie|NextAuth.*?) "([^"]+)"/
225
+ );
226
+ const cookieName = cookieNameMatch ? cookieNameMatch[1] : "unknown";
227
+
228
+ // Check if this line+cookie (or ±1 line) already has a violation from symbol analyzer
229
+ const currentLine = violation.line;
230
+ const hasSymbolViolation = [
231
+ currentLine - 1,
232
+ currentLine,
233
+ currentLine + 1,
234
+ ].some((line) => {
235
+ const lineKey = `${line}:${cookieName}`;
236
+ return lineToViolationMap.has(lineKey);
237
+ });
238
+
239
+ // Also check if this cookie already has a violation nearby (±5 lines for same cookie)
240
+ const hasCookieViolation = Array.from(
241
+ cookieToViolationMap.keys()
242
+ ).some((key) => {
243
+ const [existingCookie, existingLine] = key.split(":");
244
+ return (
245
+ existingCookie === cookieName &&
246
+ Math.abs(parseInt(existingLine) - currentLine) <= 5
247
+ );
248
+ });
249
+
250
+ if (!hasSymbolViolation && !hasCookieViolation) {
251
+ // No symbol violation or nearby cookie violation, add regex violation
252
+ const specificKey = `${violation.line}:${
253
+ violation.column || 1
254
+ }:${cookieName}:regex`;
255
+
256
+ if (!violationMap.has(specificKey)) {
257
+ violationMap.set(specificKey, {
258
+ ...violation,
259
+ source: "regex", // Track source for debugging
260
+ });
261
+
262
+ // Track this regex violation for future deduplication
263
+ const cookieKey = `${cookieName}:${violation.line}`;
264
+ cookieToViolationMap.set(cookieKey, specificKey);
265
+ }
266
+ } else if (process.env.SUNLINT_DEBUG) {
267
+ const reason = hasSymbolViolation
268
+ ? "already covered by symbol"
269
+ : "duplicate cookie violation nearby";
270
+ console.log(
271
+ `🔧 [S035] Skipping duplicate regex violation at line ${currentLine} for cookie "${cookieName}" (${reason})`
272
+ );
273
+ }
274
+ });
275
+ if (process.env.SUNLINT_DEBUG) {
276
+ console.log(
277
+ `🔧 [S035] Regex analysis completed: ${regexViolations.length} violations`
278
+ );
279
+ }
280
+ } catch (error) {
281
+ console.warn(`⚠ [S035] Regex analysis failed:`, error.message);
282
+ }
283
+ }
284
+
285
+ // Convert Map values to array and add filePath to each violation
286
+ const finalViolations = Array.from(violationMap.values()).map(
287
+ (violation) => ({
288
+ ...violation,
289
+ filePath: filePath,
290
+ file: filePath, // Also add 'file' for compatibility
291
+ })
292
+ );
293
+
294
+ if (process.env.SUNLINT_DEBUG) {
295
+ console.log(
296
+ `🔧 [S035] File analysis completed: ${finalViolations.length} unique violations`
297
+ );
298
+ }
299
+
300
+ return finalViolations;
301
+ }
302
+
303
+ /**
304
+ * Clean up resources
305
+ */
306
+ cleanup() {
307
+ if (this.symbolAnalyzer?.cleanup) {
308
+ this.symbolAnalyzer.cleanup();
309
+ }
310
+ if (this.regexAnalyzer?.cleanup) {
311
+ this.regexAnalyzer.cleanup();
312
+ }
313
+ }
314
+ }
315
+
316
+ module.exports = S035Analyzer;
@@ -0,0 +1,99 @@
1
+ {
2
+ "id": "S035",
3
+ "name": "Set Path attribute for Session Cookies",
4
+ "category": "security",
5
+ "description": "S035 - Set Path attribute for Session Cookies to limit access scope. This restricts where cookies can be sent, reducing the attack surface by limiting cookie access to specific paths.",
6
+ "severity": "warning",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"],
15
+ "exclude": [
16
+ "**/*.test.js",
17
+ "**/*.test.ts",
18
+ "**/*.spec.js",
19
+ "**/*.spec.ts",
20
+ "**/node_modules/**",
21
+ "**/dist/**",
22
+ "**/build/**"
23
+ ]
24
+ },
25
+ "analysis": {
26
+ "approach": "symbol-based-primary",
27
+ "fallback": "regex-based",
28
+ "depth": 2,
29
+ "timeout": 5000
30
+ },
31
+ "validation": {
32
+ "cookieMethods": [
33
+ "setCookie",
34
+ "cookie",
35
+ "set",
36
+ "append",
37
+ "session",
38
+ "setHeader",
39
+ "writeHead"
40
+ ],
41
+ "cookieLibraries": [
42
+ "express",
43
+ "koa",
44
+ "fastify",
45
+ "hapi",
46
+ "next",
47
+ "nuxt",
48
+ "cookie",
49
+ "cookie-parser",
50
+ "express-session",
51
+ "connect-session",
52
+ "passport"
53
+ ],
54
+ "sessionIndicators": [
55
+ "session",
56
+ "sessionid",
57
+ "sessid",
58
+ "jsessionid",
59
+ "phpsessid",
60
+ "asp.net_sessionid",
61
+ "connect.sid",
62
+ "auth",
63
+ "token",
64
+ "jwt",
65
+ "csrf",
66
+ "refresh",
67
+ "user",
68
+ "login",
69
+ "authentication"
70
+ ],
71
+ "pathPatterns": [
72
+ "path:\\s*['\"][^'\"]*['\"]",
73
+ "path:['\"][^'\"]*['\"]",
74
+ "Path=[^;\\s]+",
75
+ "path=[\\/][^;\\s]*"
76
+ ],
77
+ "insecurePatterns": [
78
+ "(?<!path[\\s=:]+)(?<!Path=)Set-Cookie",
79
+ "res\\.cookie\\([^)]*\\)(?![^{]*path)",
80
+ "document\\.cookie\\s*="
81
+ ],
82
+ "acceptableValues": [
83
+ "/",
84
+ "/app",
85
+ "/admin",
86
+ "/api",
87
+ "/auth",
88
+ "/user",
89
+ "/secure"
90
+ ],
91
+ "recommendedValues": ["/app", "/admin", "/api", "/auth"],
92
+ "securityRequirements": {
93
+ "mustHavePath": true,
94
+ "mustNotBeRoot": false,
95
+ "specificPathRequired": true,
96
+ "pathValidation": true
97
+ }
98
+ }
99
+ }