@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,127 @@
1
+ # S031 - Set Secure flag for Session Cookies
2
+
3
+ ## Rule Description
4
+
5
+ **S031** ensures that session cookies have the `Secure` flag set to protect them via HTTPS. This prevents cookies from being transmitted over unencrypted connections, reducing the risk of session hijacking and cookie interception.
6
+
7
+ ## Security Impact
8
+
9
+ - **High**: Session cookies without Secure flag can be intercepted over HTTP
10
+ - **Attack Vector**: Man-in-the-middle attacks, network sniffing
11
+ - **Compliance**: OWASP Top 10, PCI DSS requirements
12
+
13
+ ## Detection Patterns
14
+
15
+ ### Vulnerable Code Examples
16
+
17
+ ```javascript
18
+ // ❌ Express.js - Missing Secure flag
19
+ res.cookie("sessionid", sessionValue, {
20
+ httpOnly: true,
21
+ // Missing: secure: true
22
+ });
23
+
24
+ // ❌ Set-Cookie header - No Secure flag
25
+ res.setHeader("Set-Cookie", "sessionid=abc123; HttpOnly");
26
+
27
+ // ❌ Document.cookie - Insecure assignment
28
+ document.cookie = "auth=token123; path=/";
29
+
30
+ // ❌ Session middleware - Missing security
31
+ app.use(
32
+ session({
33
+ secret: "secret-key",
34
+ name: "sessionid",
35
+ // Missing: cookie: { secure: true }
36
+ })
37
+ );
38
+ ```
39
+
40
+ ### Secure Code Examples
41
+
42
+ ```javascript
43
+ // ✅ Express.js - With Secure flag
44
+ res.cookie("sessionid", sessionValue, {
45
+ httpOnly: true,
46
+ secure: true,
47
+ sameSite: "strict",
48
+ });
49
+
50
+ // ✅ Set-Cookie header - With Secure flag
51
+ res.setHeader("Set-Cookie", "sessionid=abc123; HttpOnly; Secure");
52
+
53
+ // ✅ Session middleware - Secure configuration
54
+ app.use(
55
+ session({
56
+ secret: "secret-key",
57
+ name: "sessionid",
58
+ cookie: {
59
+ secure: true,
60
+ httpOnly: true,
61
+ sameSite: "strict",
62
+ },
63
+ })
64
+ );
65
+
66
+ // ✅ Conditional Secure flag based on environment
67
+ res.cookie("sessionid", sessionValue, {
68
+ httpOnly: true,
69
+ secure: process.env.NODE_ENV === "production",
70
+ });
71
+ ```
72
+
73
+ ## Session Cookie Indicators
74
+
75
+ The rule detects cookies that are likely session-related based on:
76
+
77
+ - **Cookie Names**: `session`, `sessionid`, `sessid`, `jsessionid`, `phpsessid`
78
+ - **Framework Patterns**: `connect.sid`, `asp.net_sessionid`
79
+ - **Authentication**: `auth`, `token`, `jwt`, `csrf`
80
+
81
+ ## Supported Frameworks
82
+
83
+ - **Express.js**: `res.cookie()`, `res.setHeader()`
84
+ - **Koa**: Cookie setting methods
85
+ - **Fastify**: Cookie plugins
86
+ - **Next.js**: API routes cookie handling
87
+ - **Native**: `document.cookie`, Set-Cookie headers
88
+
89
+ ## Configuration
90
+
91
+ The rule can be configured in `config.json`:
92
+
93
+ ```json
94
+ {
95
+ "validation": {
96
+ "sessionIndicators": ["session", "sessionid", "auth", "token"],
97
+ "cookieMethods": ["setCookie", "cookie", "setHeader"]
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Best Practices
103
+
104
+ 1. **Always use Secure flag in production**
105
+ 2. **Combine with HttpOnly flag** to prevent XSS access
106
+ 3. **Use SameSite attribute** for CSRF protection
107
+ 4. **Conditional setting** based on environment:
108
+ ```javascript
109
+ const isProduction = process.env.NODE_ENV === "production";
110
+ res.cookie("session", value, {
111
+ secure: isProduction,
112
+ httpOnly: true,
113
+ sameSite: "strict",
114
+ });
115
+ ```
116
+
117
+ ## Related Rules
118
+
119
+ - **S032**: HttpOnly flag for cookies
120
+ - **S033**: SameSite attribute for CSRF protection
121
+ - **S034**: Cookie expiration and domain settings
122
+
123
+ ## References
124
+
125
+ - [OWASP Session Management](https://owasp.org/www-project-cheat-sheets/cheatsheets/Session_Management_Cheat_Sheet.html)
126
+ - [MDN Secure Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies)
127
+ - [RFC 6265 - HTTP State Management](https://tools.ietf.org/html/rfc6265)
@@ -0,0 +1,245 @@
1
+ /**
2
+ * S031 Main Analyzer - Set Secure flag for Session Cookies
3
+ * Primary: Symbol-based analysis (when available)
4
+ * Fallback: Regex-based for all other cases
5
+ * Command: node cli.js --rule=S031 --input=examples/rule-test-fixtures/rules/S031_secure_session_cookies --engine=heuristic
6
+ */
7
+
8
+ const S031SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
+ const S031RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
+
11
+ class S031Analyzer {
12
+ constructor(options = {}) {
13
+ if (process.env.SUNLINT_DEBUG) {
14
+ console.log(`🔧 [S031] Constructor called with options:`, !!options);
15
+ console.log(
16
+ `🔧 [S031] Options type:`,
17
+ typeof options,
18
+ Object.keys(options || {})
19
+ );
20
+ }
21
+
22
+ this.ruleId = "S031";
23
+ this.ruleName = "Set Secure flag for Session Cookies";
24
+ this.description =
25
+ "Set Secure flag for Session Cookies to protect via HTTPS. This ensures cookies are only transmitted over secure connections, preventing interception.";
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 S031SymbolBasedAnalyzer(this.semanticEngine);
39
+ if (process.env.SUNLINT_DEBUG) {
40
+ console.log(`🔧 [S031] Symbol analyzer created successfully`);
41
+ }
42
+ } catch (error) {
43
+ console.error(`🔧 [S031] Error creating symbol analyzer:`, error);
44
+ }
45
+
46
+ try {
47
+ this.regexAnalyzer = new S031RegexBasedAnalyzer(this.semanticEngine);
48
+ if (process.env.SUNLINT_DEBUG) {
49
+ console.log(`🔧 [S031] Regex analyzer created successfully`);
50
+ }
51
+ } catch (error) {
52
+ console.error(`🔧 [S031] Error creating regex analyzer:`, error);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Initialize analyzer with semantic engine
58
+
59
+ */
60
+ async initialize(semanticEngine) {
61
+ this.semanticEngine = semanticEngine;
62
+
63
+ if (process.env.SUNLINT_DEBUG) {
64
+ console.log(`🔧 [S031] Main analyzer initializing...`);
65
+ }
66
+
67
+ // Initialize both analyzers
68
+ if (this.symbolAnalyzer) {
69
+ await this.symbolAnalyzer.initialize?.(semanticEngine);
70
+ }
71
+ if (this.regexAnalyzer) {
72
+ await this.regexAnalyzer.initialize?.(semanticEngine);
73
+ }
74
+
75
+ // Clean up if needed
76
+ if (this.regexAnalyzer) {
77
+ this.regexAnalyzer.cleanup?.();
78
+ }
79
+
80
+ if (process.env.SUNLINT_DEBUG) {
81
+ console.log(`🔧 [S031] Main analyzer initialized successfully`);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Single file analysis method for testing
87
+
88
+ */
89
+ analyzeSingle(filePath, options = {}) {
90
+ if (process.env.SUNLINT_DEBUG) {
91
+ console.log(`🔍 [S031] analyzeSingle() called for: ${filePath}`);
92
+ }
93
+
94
+ // Return result using same format as analyze method
95
+ return this.analyze([filePath], "typescript", options);
96
+ }
97
+
98
+ async analyze(files, language, options = {}) {
99
+ if (process.env.SUNLINT_DEBUG) {
100
+ console.log(
101
+ `🔧 [S031] analyze() method called with ${files.length} files, language: ${language}`
102
+ );
103
+ }
104
+
105
+ const violations = [];
106
+
107
+ for (const filePath of files) {
108
+ try {
109
+ if (process.env.SUNLINT_DEBUG) {
110
+ console.log(`🔧 [S031] Processing file: ${filePath}`);
111
+ }
112
+
113
+ const fileViolations = await this.analyzeFile(filePath, options);
114
+ violations.push(...fileViolations);
115
+
116
+ if (process.env.SUNLINT_DEBUG) {
117
+ console.log(
118
+ `🔧 [S031] File ${filePath}: Found ${fileViolations.length} violations`
119
+ );
120
+ }
121
+ } catch (error) {
122
+ console.warn(
123
+ `⚠ [S031] Analysis failed for ${filePath}:`,
124
+ error.message
125
+ );
126
+ }
127
+ }
128
+
129
+ if (process.env.SUNLINT_DEBUG) {
130
+ console.log(`🔧 [S031] Total violations found: ${violations.length}`);
131
+ }
132
+
133
+ return violations;
134
+ }
135
+
136
+ async analyzeFile(filePath, options = {}) {
137
+ if (process.env.SUNLINT_DEBUG) {
138
+ console.log(`🔍 [S031] analyzeFile() called for: ${filePath}`);
139
+ }
140
+
141
+ // Create a Map to track unique violations and prevent duplicates
142
+ const violationMap = new Map();
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(`🔧 [S031] 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(`🔧 [S031] Source file found, analyzing...`);
158
+ }
159
+ const symbolViolations = await this.symbolAnalyzer.analyze(
160
+ sourceFile,
161
+ filePath
162
+ );
163
+
164
+ // Add to violation map with deduplication
165
+ symbolViolations.forEach((violation) => {
166
+ const key = `${violation.line}:${violation.column}:${violation.message}`;
167
+ if (!violationMap.has(key)) {
168
+ violationMap.set(key, violation);
169
+ }
170
+ });
171
+
172
+ if (process.env.SUNLINT_DEBUG) {
173
+ console.log(
174
+ `🔧 [S031] Symbol analysis completed: ${symbolViolations.length} violations`
175
+ );
176
+ }
177
+ } else {
178
+ if (process.env.SUNLINT_DEBUG) {
179
+ console.log(`🔧 [S031] Source file not found, falling back...`);
180
+ }
181
+ }
182
+ } catch (error) {
183
+ console.warn(`⚠ [S031] Symbol analysis failed:`, error.message);
184
+ }
185
+ }
186
+
187
+ // 2. Try Regex-based analysis (fallback or additional)
188
+ if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
189
+ try {
190
+ if (process.env.SUNLINT_DEBUG) {
191
+ console.log(`🔧 [S031] Trying regex-based analysis...`);
192
+ }
193
+ const regexViolations = await this.regexAnalyzer.analyze(filePath);
194
+
195
+ // Add to violation map with deduplication
196
+ regexViolations.forEach((violation) => {
197
+ const key = `${violation.line}:${violation.column}:${violation.message}`;
198
+ if (!violationMap.has(key)) {
199
+ violationMap.set(key, violation);
200
+ }
201
+ });
202
+
203
+ if (process.env.SUNLINT_DEBUG) {
204
+ console.log(
205
+ `🔧 [S031] Regex analysis completed: ${regexViolations.length} violations`
206
+ );
207
+ }
208
+ } catch (error) {
209
+ console.warn(`⚠ [S031] Regex analysis failed:`, error.message);
210
+ }
211
+ }
212
+
213
+ // Convert Map values to array and add filePath to each violation
214
+ const finalViolations = Array.from(violationMap.values()).map(
215
+ (violation) => ({
216
+ ...violation,
217
+ filePath: filePath,
218
+ file: filePath, // Also add 'file' for compatibility
219
+ })
220
+ );
221
+
222
+ if (process.env.SUNLINT_DEBUG) {
223
+ console.log(
224
+ `🔧 [S031] File analysis completed: ${finalViolations.length} unique violations`
225
+ );
226
+ }
227
+
228
+ return finalViolations;
229
+ }
230
+
231
+ /**
232
+ * Clean up resources
233
+
234
+ */
235
+ cleanup() {
236
+ if (this.symbolAnalyzer?.cleanup) {
237
+ this.symbolAnalyzer.cleanup();
238
+ }
239
+ if (this.regexAnalyzer?.cleanup) {
240
+ this.regexAnalyzer.cleanup();
241
+ }
242
+ }
243
+ }
244
+
245
+ module.exports = S031Analyzer;
@@ -0,0 +1,86 @@
1
+ {
2
+ "id": "S031",
3
+ "name": "Set Secure flag for Session Cookies",
4
+ "category": "security",
5
+ "description": "S031 - Set Secure flag for Session Cookies to protect via HTTPS. This ensures cookies are only transmitted over secure connections, preventing interception.",
6
+ "severity": "error",
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
+ ],
67
+ "securePatterns": [
68
+ "secure:\\s*true",
69
+ "secure:true",
70
+ "Secure",
71
+ "secure=true",
72
+ "httpOnly:\\s*true",
73
+ "httpOnly:true",
74
+ "HttpOnly",
75
+ "httpOnly=true"
76
+ ],
77
+ "insecurePatterns": [
78
+ "secure:\\s*false",
79
+ "secure:false",
80
+ "secure=false",
81
+ "(?<!secure[\\s=:]+)(?<!Secure[\\s;])Set-Cookie",
82
+ "res\\.cookie\\([^)]*\\)(?![^{]*secure)",
83
+ "document\\.cookie\\s*="
84
+ ]
85
+ }
86
+ }
@@ -0,0 +1,196 @@
1
+ /**
2
+ * S031 Regex-Based Analyzer - Set Secure flag for Session Cookies
3
+ * Fallback analysis using regex patterns
4
+
5
+ */
6
+
7
+ const fs = require("fs");
8
+
9
+ class S031RegexBasedAnalyzer {
10
+ constructor(semanticEngine = null) {
11
+ this.semanticEngine = semanticEngine;
12
+ this.ruleId = "S031";
13
+ this.category = "security";
14
+
15
+ // Session cookie indicators
16
+ this.sessionIndicators = [
17
+ "session",
18
+ "sessionid",
19
+ "sessid",
20
+ "jsessionid",
21
+ "phpsessid",
22
+ "asp.net_sessionid",
23
+ "connect.sid",
24
+ "auth",
25
+ "token",
26
+ "jwt",
27
+ "csrf",
28
+ ];
29
+
30
+ // Regex patterns for cookie detection
31
+ this.cookiePatterns = [
32
+ // Express/Node.js patterns
33
+ /res\.cookie\s*\(\s*['"`]([^'"`]+)['"`]\s*,([^)]+)\)/gi,
34
+ /response\.cookie\s*\(\s*['"`]([^'"`]+)['"`]\s*,([^)]+)\)/gi,
35
+ /\.setCookie\s*\(\s*['"`]([^'"`]+)['"`]\s*,([^)]+)\)/gi,
36
+
37
+ // Set-Cookie header patterns
38
+ /setHeader\s*\(\s*['"`]Set-Cookie['"`]\s*,\s*['"`]([^'"`]+)['"`]\s*\)/gi,
39
+ /writeHead\s*\([^,]*,\s*{[^}]*['"`]Set-Cookie['"`]\s*:\s*['"`]([^'"`]+)['"`]/gi,
40
+
41
+ // Document.cookie assignments
42
+ /document\.cookie\s*=\s*['"`]([^'"`]+)['"`]/gi,
43
+
44
+ // Session middleware patterns
45
+ /session\s*\(\s*{([^}]+)}/gi,
46
+ /\.use\s*\(\s*session\s*\(\s*{([^}]+)}/gi,
47
+ ];
48
+ }
49
+
50
+ /**
51
+ * Initialize analyzer
52
+
53
+ */
54
+ async initialize(semanticEngine) {
55
+ this.semanticEngine = semanticEngine;
56
+ if (process.env.SUNLINT_DEBUG) {
57
+ console.log(`🔧 [S031] Regex-based analyzer initialized`);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Analyze file content using regex patterns
63
+
64
+ */
65
+ async analyze(filePath) {
66
+ if (process.env.SUNLINT_DEBUG) {
67
+ console.log(`🔍 [S031] Regex-based analysis for: ${filePath}`);
68
+ }
69
+
70
+ let content;
71
+ try {
72
+ content = fs.readFileSync(filePath, "utf8");
73
+ } catch (error) {
74
+ if (process.env.SUNLINT_DEBUG) {
75
+ console.error(`❌ [S031] File read error:`, error);
76
+ }
77
+ throw error;
78
+ }
79
+
80
+ const violations = [];
81
+ const lines = content.split("\n");
82
+
83
+ // Check each pattern
84
+ for (const pattern of this.cookiePatterns) {
85
+ this.checkPattern(pattern, content, lines, violations, filePath);
86
+ }
87
+
88
+ return violations;
89
+ }
90
+
91
+ /**
92
+ * Check specific regex pattern for violations
93
+
94
+ */
95
+ checkPattern(pattern, content, lines, violations, filePath) {
96
+ let match;
97
+ pattern.lastIndex = 0; // Reset regex state
98
+
99
+ while ((match = pattern.exec(content)) !== null) {
100
+ const matchText = match[0];
101
+ const cookieName = match[1] || "";
102
+ const cookieOptions = match[2] || match[1] || "";
103
+
104
+ // Check if this is a session cookie
105
+ if (!this.isSessionCookie(cookieName, matchText)) {
106
+ continue;
107
+ }
108
+
109
+ // Check if secure flag is present
110
+ if (!this.hasSecureFlag(cookieOptions, matchText)) {
111
+ const lineNumber = this.getLineNumber(content, match.index);
112
+
113
+ this.addViolation(
114
+ matchText,
115
+ lineNumber,
116
+ violations,
117
+ `Session cookie "${cookieName || "unknown"}" missing Secure flag`
118
+ );
119
+ }
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Check if cookie name or context indicates session cookie
125
+
126
+ */
127
+ isSessionCookie(cookieName, matchText) {
128
+ const textToCheck = (cookieName + " " + matchText).toLowerCase();
129
+ return this.sessionIndicators.some((indicator) =>
130
+ textToCheck.includes(indicator.toLowerCase())
131
+ );
132
+ }
133
+
134
+ /**
135
+ * Check if secure flag is present in cookie options
136
+ */
137
+ hasSecureFlag(cookieOptions, fullMatch) {
138
+ const textToCheck = cookieOptions + " " + fullMatch;
139
+
140
+ // Check for secure config references (likely safe)
141
+ const secureConfigPatterns = [
142
+ /\bcookieConfig\b/i,
143
+ /\bsecureConfig\b/i,
144
+ /\bsafeConfig\b/i,
145
+ /\bdefaultConfig\b/i,
146
+ /\.\.\..*config/i, // spread operator with config
147
+ /config.*secure/i,
148
+ ];
149
+
150
+ // If using a secure config reference, assume it's safe
151
+ if (secureConfigPatterns.some((pattern) => pattern.test(textToCheck))) {
152
+ return true;
153
+ }
154
+
155
+ // Check for various secure flag patterns
156
+ const securePatterns = [
157
+ /secure\s*:\s*true/i,
158
+ /secure\s*=\s*true/i,
159
+ /;\s*secure\s*[;\s]/i,
160
+ /;\s*secure$/i,
161
+ /['"`]\s*secure\s*['"`]/i,
162
+ /"secure"\s*:\s*true/i,
163
+ /'secure'\s*:\s*true/i,
164
+ /\bsecure\b/i, // Simple secure keyword
165
+ ];
166
+
167
+ return securePatterns.some((pattern) => pattern.test(textToCheck));
168
+ }
169
+
170
+ /**
171
+ * Get line number from content position
172
+
173
+ */
174
+ getLineNumber(content, position) {
175
+ const beforeMatch = content.substring(0, position);
176
+ return beforeMatch.split("\n").length;
177
+ }
178
+
179
+ /**
180
+ * Add violation to results
181
+
182
+ */
183
+ addViolation(source, lineNumber, violations, message) {
184
+ violations.push({
185
+ ruleId: this.ruleId,
186
+ source: source.trim(),
187
+ category: this.category,
188
+ line: lineNumber,
189
+ column: 1,
190
+ message: `Insecure session cookie: ${message}`,
191
+ severity: "error",
192
+ });
193
+ }
194
+ }
195
+
196
+ module.exports = S031RegexBasedAnalyzer;