@sun-asterisk/sunlint 1.3.29 → 1.3.31

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.
@@ -1,12 +1,10 @@
1
1
  /**
2
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
3
+ * Uses symbol-based analysis only (regex-based removed)
5
4
  * Command: node cli.js --rule=S031 --input=examples/rule-test-fixtures/rules/S031_secure_session_cookies --engine=heuristic
6
5
  */
7
6
 
8
7
  const S031SymbolBasedAnalyzer = require("./symbol-based-analyzer.js");
9
- const S031RegexBasedAnalyzer = require("./regex-based-analyzer.js");
10
8
 
11
9
  class S031Analyzer {
12
10
  constructor(options = {}) {
@@ -26,14 +24,7 @@ class S031Analyzer {
26
24
  this.semanticEngine = options.semanticEngine || null;
27
25
  this.verbose = options.verbose || false;
28
26
 
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
27
+ // Initialize symbol analyzer only
37
28
  try {
38
29
  this.symbolAnalyzer = new S031SymbolBasedAnalyzer(this.semanticEngine);
39
30
  if (process.env.SUNLINT_DEBUG) {
@@ -43,48 +34,39 @@ class S031Analyzer {
43
34
  console.error(`🔧 [S031] Error creating symbol analyzer:`, error);
44
35
  }
45
36
 
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);
37
+ if (process.env.SUNLINT_DEBUG) {
38
+ console.log(`🔧 [S031] Constructor completed`);
53
39
  }
54
40
  }
55
41
 
56
42
  /**
57
43
  * Initialize analyzer with semantic engine
58
-
59
44
  */
60
- async initialize(semanticEngine) {
61
- this.semanticEngine = semanticEngine;
62
-
63
- if (process.env.SUNLINT_DEBUG) {
64
- console.log(`🔧 [S031] Main analyzer initializing...`);
45
+ async initialize(semanticEngine = null) {
46
+ if (semanticEngine) {
47
+ this.semanticEngine = semanticEngine;
65
48
  }
49
+ this.verbose = semanticEngine?.verbose || false;
66
50
 
67
- // Initialize both analyzers
51
+ // Initialize symbol analyzer
68
52
  if (this.symbolAnalyzer) {
69
53
  await this.symbolAnalyzer.initialize?.(semanticEngine);
70
54
  }
71
- if (this.regexAnalyzer) {
72
- await this.regexAnalyzer.initialize?.(semanticEngine);
73
- }
74
55
 
75
- // Clean up if needed
76
- if (this.regexAnalyzer) {
77
- this.regexAnalyzer.cleanup?.();
56
+ // Ensure verbose flag is propagated
57
+ if (this.symbolAnalyzer) {
58
+ this.symbolAnalyzer.verbose = this.verbose;
78
59
  }
79
60
 
80
- if (process.env.SUNLINT_DEBUG) {
81
- console.log(`🔧 [S031] Main analyzer initialized successfully`);
61
+ if (this.verbose) {
62
+ console.log(
63
+ `🔧 [S031] Analyzer initialized - verbose: ${this.verbose}`
64
+ );
82
65
  }
83
66
  }
84
67
 
85
68
  /**
86
69
  * Single file analysis method for testing
87
-
88
70
  */
89
71
  analyzeSingle(filePath, options = {}) {
90
72
  if (process.env.SUNLINT_DEBUG) {
@@ -141,122 +123,95 @@ class S031Analyzer {
141
123
  // Create a Map to track unique violations and prevent duplicates
142
124
  const violationMap = new Map();
143
125
 
144
- // 1. Try Symbol-based analysis first (primary)
145
- if (
146
- this.config.useSymbolBased &&
147
- this.semanticEngine?.project &&
148
- this.semanticEngine?.initialized
149
- ) {
126
+ // Symbol-based analysis only
127
+ if (this.semanticEngine?.project && this.semanticEngine?.initialized) {
150
128
  try {
151
129
  if (process.env.SUNLINT_DEBUG) {
152
- console.log(`🔧 [S031] Trying symbol-based analysis...`);
130
+ console.log(`🔧 [S031] Running symbol-based analysis...`);
153
131
  }
154
132
  const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
155
133
  if (sourceFile) {
156
134
  if (process.env.SUNLINT_DEBUG) {
157
- console.log(`🔧 [S031] Source file found, analyzing...`);
135
+ console.log(
136
+ `🔧 [S031] Source file found, analyzing with symbol-based...`
137
+ );
158
138
  }
159
- const symbolViolations = await this.symbolAnalyzer.analyze(
139
+
140
+ const violations = await this.symbolAnalyzer.analyze(
160
141
  sourceFile,
161
142
  filePath
162
143
  );
163
144
 
164
- // Add to violation map with deduplication
165
- symbolViolations.forEach((violation) => {
166
- // Create a cookie-specific key to allow multiple violations for same cookie at different locations
167
- const cookieName = this.extractCookieName(violation.message) || "";
168
- const key = `cookie:${cookieName}:line:${violation.line}:secure`;
145
+ // Add violations to map to deduplicate and add filePath
146
+ violations.forEach((v) => {
147
+ const key = `${v.line}:${v.column}:${v.message}`;
169
148
  if (!violationMap.has(key)) {
170
- violationMap.set(key, violation);
149
+ v.analysisStrategy = "symbol-based";
150
+ v.filePath = filePath;
151
+ v.file = filePath; // Also add 'file' for compatibility
152
+ violationMap.set(key, v);
171
153
  }
172
154
  });
173
155
 
174
156
  if (process.env.SUNLINT_DEBUG) {
175
157
  console.log(
176
- `🔧 [S031] Symbol analysis completed: ${symbolViolations.length} violations`
158
+ `✅ [S031] Symbol-based analysis: ${violations.length} violations`
177
159
  );
178
160
  }
161
+
162
+ const finalViolations = Array.from(violationMap.values());
163
+ return finalViolations; // Return deduplicated violations with filePath
179
164
  } else {
180
165
  if (process.env.SUNLINT_DEBUG) {
181
- console.log(`🔧 [S031] Source file not found, falling back...`);
166
+ console.log(`⚠️ [S031] Source file not found in project`);
182
167
  }
183
168
  }
184
169
  } catch (error) {
185
- console.warn(`⚠ [S031] Symbol analysis failed:`, error.message);
170
+ console.warn(`⚠️ [S031] Symbol analysis failed: ${error.message}`);
186
171
  }
187
- }
188
-
189
- // 2. Try Regex-based analysis (fallback or additional)
190
- if (this.config.fallbackToRegex || this.config.regexBasedOnly) {
191
- try {
192
- if (process.env.SUNLINT_DEBUG) {
193
- console.log(`🔧 [S031] Trying regex-based analysis...`);
194
- }
195
- const regexViolations = await this.regexAnalyzer.analyze(filePath);
196
-
197
- // Add to violation map with deduplication
198
- regexViolations.forEach((violation) => {
199
- // Create a cookie-specific key to allow multiple violations for same cookie at different locations
200
- const cookieName = this.extractCookieName(violation.message) || "";
201
- const key = `cookie:${cookieName}:line:${violation.line}:secure`;
202
- if (!violationMap.has(key)) {
203
- violationMap.set(key, violation);
204
- }
205
- });
206
-
207
- if (process.env.SUNLINT_DEBUG) {
208
- console.log(
209
- `🔧 [S031] Regex analysis completed: ${regexViolations.length} violations`
210
- );
211
- }
212
- } catch (error) {
213
- console.warn(`⚠ [S031] Regex analysis failed:`, error.message);
172
+ } else {
173
+ if (process.env.SUNLINT_DEBUG) {
174
+ console.log(`🔄 [S031] Symbol analysis conditions check:`);
175
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
176
+ console.log(
177
+ ` - semanticEngine.project: ${!!this.semanticEngine?.project}`
178
+ );
179
+ console.log(
180
+ ` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`
181
+ );
182
+ console.log(`🔄 [S031] Symbol analysis unavailable`);
214
183
  }
215
184
  }
216
185
 
217
- // Convert Map values to array and add filePath to each violation
218
- const finalViolations = Array.from(violationMap.values()).map(
219
- (violation) => ({
220
- ...violation,
221
- filePath: filePath,
222
- file: filePath, // Also add 'file' for compatibility
223
- })
224
- );
225
-
226
186
  if (process.env.SUNLINT_DEBUG) {
227
- console.log(
228
- `🔧 [S031] File analysis completed: ${finalViolations.length} unique violations`
229
- );
187
+ console.log(`🔧 [S031] Analysis completed: ${violationMap.size} violations`);
230
188
  }
231
-
232
- return finalViolations;
189
+ return Array.from(violationMap.values());
233
190
  }
234
191
 
235
192
  /**
236
- * Extract cookie name from violation message for better deduplication
193
+ * Methods for compatibility with different engine invocation patterns
237
194
  */
238
- extractCookieName(message) {
239
- try {
240
- const match = message.match(
241
- /Session cookie "([^"]+)"|Session cookie from "([^"]+)"/
242
- );
243
- return match ? match[1] || match[2] : "";
244
- } catch (error) {
245
- return "";
246
- }
195
+ async analyzeFileWithSymbols(filePath, options = {}) {
196
+ return this.analyzeFile(filePath, options);
247
197
  }
248
198
 
249
- /**
250
- * Clean up resources
199
+ async analyzeWithSemantics(filePath, options = {}) {
200
+ return this.analyzeFile(filePath, options);
201
+ }
251
202
 
203
+ /**
204
+ * Get analyzer metadata
252
205
  */
253
- cleanup() {
254
- if (this.symbolAnalyzer?.cleanup) {
255
- this.symbolAnalyzer.cleanup();
256
- }
257
- if (this.regexAnalyzer?.cleanup) {
258
- this.regexAnalyzer.cleanup();
259
- }
206
+ getMetadata() {
207
+ return {
208
+ rule: "S031",
209
+ name: "Set Secure flag for Session Cookies",
210
+ category: "security",
211
+ type: "symbol-based",
212
+ description:
213
+ "Uses symbol-based analysis to detect session cookies missing Secure flag",
214
+ };
260
215
  }
261
216
  }
262
217
 
@@ -29,7 +29,7 @@ class S041Analyzer {
29
29
  // Configuration
30
30
  this.config = {
31
31
  useSymbolBased: true, // Primary approach
32
- fallbackToRegex: true, // Secondary approach
32
+ fallbackToRegex: false, // Secondary approach
33
33
  regexBasedOnly: false, // Can be set to true for pure mode
34
34
  };
35
35
 
@@ -11,6 +11,17 @@ class S041SymbolBasedAnalyzer {
11
11
  this.ruleId = "S041";
12
12
  this.category = "security";
13
13
 
14
+ this.skipPatterns = [
15
+ /\/node_modules\//,
16
+ /\/tests?\//,
17
+ /\/dist\//,
18
+ /\/build\//,
19
+ /\.spec\.ts$/,
20
+ /\.spec\.tsx$/,
21
+ /\.test\.ts$/,
22
+ /\.test\.tsx$/,
23
+ ];
24
+
14
25
  // Session management methods that should invalidate tokens
15
26
  this.sessionMethods = [
16
27
  "logout",
@@ -106,7 +117,7 @@ class S041SymbolBasedAnalyzer {
106
117
  }
107
118
  }
108
119
 
109
- async analyze(filePath) {
120
+ async analyze(sourceFile, filePath) {
110
121
  if (this.verbose) {
111
122
  console.log(
112
123
  `🔍 [${this.ruleId}] Symbol: Starting analysis for ${filePath}`
@@ -123,7 +134,6 @@ class S041SymbolBasedAnalyzer {
123
134
  }
124
135
 
125
136
  try {
126
- const sourceFile = this.semanticEngine.getSourceFile(filePath);
127
137
  if (!sourceFile) {
128
138
  if (this.verbose) {
129
139
  console.log(
@@ -179,6 +189,11 @@ class S041SymbolBasedAnalyzer {
179
189
  console.log(`🔍 [${this.ruleId}] Symbol: Starting symbol-based analysis`);
180
190
  }
181
191
 
192
+ if (this.shouldIgnoreFile(filePath)) {
193
+ if (verbose) console.log(`[${this.ruleId}] Ignoring ${filePath}`);
194
+ return [];
195
+ }
196
+
182
197
  const callExpressions = sourceFile.getDescendantsOfKind
183
198
  ? sourceFile.getDescendantsOfKind(
184
199
  require("typescript").SyntaxKind.CallExpression
@@ -669,6 +684,10 @@ class S041SymbolBasedAnalyzer {
669
684
  return null;
670
685
  }
671
686
  }
687
+
688
+ shouldIgnoreFile(filePath) {
689
+ return this.skipPatterns.some((pattern) => pattern.test(filePath));
690
+ }
672
691
  }
673
692
 
674
693
  module.exports = S041SymbolBasedAnalyzer;
@@ -25,7 +25,9 @@ class S042SymbolBasedAnalyzer {
25
25
  /\/dist\//,
26
26
  /\/build\//,
27
27
  /\.spec\.ts$/,
28
- /\.test\.ts$/
28
+ /\.spec\.tsx$/,
29
+ /\.test\.ts$/,
30
+ /\.test\.tsx$/,
29
31
  ];
30
32
 
31
33
  // Sensitive action patterns
@@ -679,13 +681,16 @@ class S042SymbolBasedAnalyzer {
679
681
  const hasIdleTimeout = this.hasIdleTimeoutLogic(sourceFile);
680
682
 
681
683
  if (hasSessionConfig && !hasIdleTimeout) {
684
+ // Try to find the actual session configuration location
685
+ const sessionConfigLocation = this.findSessionConfigLocation(sourceFile);
686
+
682
687
  violations.push({
683
688
  ruleId: this.ruleId,
684
689
  ruleName: this.ruleName,
685
690
  severity: 'medium',
686
691
  message: `Session management detected but no idle timeout implementation found.`,
687
- line: 1,
688
- column: 1,
692
+ line: sessionConfigLocation.line,
693
+ column: sessionConfigLocation.column,
689
694
  filePath: sourceFile.getFilePath(),
690
695
  type: 'MISSING_IDLE_TIMEOUT_LOGIC',
691
696
  details: `Implement idle timeout to automatically expire sessions after ${this.formatDuration(this.MAX_IDLE_TIME)} of inactivity.`
@@ -695,6 +700,91 @@ class S042SymbolBasedAnalyzer {
695
700
  return violations;
696
701
  }
697
702
 
703
+ findSessionConfigLocation(sourceFile) {
704
+ const defaultLocation = { line: 1, column: 1 };
705
+
706
+ try {
707
+ // Look for session-related call expressions
708
+ const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
709
+
710
+ for (const callExpr of callExpressions) {
711
+ const expression = callExpr.getExpression();
712
+ const expressionText = expression.getText();
713
+
714
+ // Check for session middleware calls
715
+ if (expressionText.match(/session|express-session|cookie-session/i)) {
716
+ const args = callExpr.getArguments();
717
+
718
+ // If there's a config object argument, use its location
719
+ if (args.length > 0 && args[0].getKind() === SyntaxKind.ObjectLiteralExpression) {
720
+ const configObj = args[0];
721
+ return {
722
+ line: configObj.getStartLineNumber(),
723
+ column: configObj.getStart() - configObj.getStartLinePos() + 1
724
+ };
725
+ }
726
+
727
+ // Otherwise use the call expression location
728
+ return {
729
+ line: callExpr.getStartLineNumber(),
730
+ column: callExpr.getStart() - callExpr.getStartLinePos() + 1
731
+ };
732
+ }
733
+ }
734
+
735
+ // Look for variable declarations with session config
736
+ const variableDeclarations = sourceFile.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
737
+
738
+ for (const varDecl of variableDeclarations) {
739
+ const name = varDecl.getName();
740
+
741
+ if (name.match(/sessionConfig|sessionOptions|sessionMiddleware/i)) {
742
+ const initializer = varDecl.getInitializer();
743
+
744
+ if (initializer) {
745
+ return {
746
+ line: initializer.getStartLineNumber(),
747
+ column: initializer.getStart() - initializer.getStartLinePos() + 1
748
+ };
749
+ }
750
+ }
751
+ }
752
+
753
+ // Look for object literals with session config keys
754
+ const objectLiterals = sourceFile.getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression);
755
+
756
+ for (const objLiteral of objectLiterals) {
757
+ const properties = objLiteral.getProperties();
758
+ let sessionKeyCount = 0;
759
+
760
+ for (const prop of properties) {
761
+ if (prop.getKind() === SyntaxKind.PropertyAssignment) {
762
+ const name = prop.getName();
763
+ if (this.sessionConfigKeys.some(key => name === key)) {
764
+ sessionKeyCount++;
765
+ }
766
+ }
767
+ }
768
+
769
+ // If we found multiple session config keys, this is likely the config
770
+ if (sessionKeyCount >= 2) {
771
+ return {
772
+ line: objLiteral.getStartLineNumber(),
773
+ column: objLiteral.getStart() - objLiteral.getStartLinePos() + 1
774
+ };
775
+ }
776
+ }
777
+
778
+ } catch (error) {
779
+ // Fall back to default location on error
780
+ if (verbose) {
781
+ console.warn(`[${this.ruleId}] Failed to find session config location:`, error.message);
782
+ }
783
+ }
784
+
785
+ return defaultLocation;
786
+ }
787
+
698
788
  // Helper methods
699
789
 
700
790
  isJWTSignCall(expressionText) {
@@ -1046,7 +1136,10 @@ class S042SymbolBasedAnalyzer {
1046
1136
 
1047
1137
  hasSessionConfiguration(sourceFile) {
1048
1138
  const text = sourceFile.getText();
1049
- return /session\(|express-session|cookie-session|getSessionConfig|sessionConfig/i.test(text);
1139
+
1140
+ // More specific patterns for server-side session middleware
1141
+ // Avoid matching client-side hooks like useSession()
1142
+ return /express-session|cookie-session|session\.Session|getSessionConfig|sessionConfig|sessionOptions|sessionMiddleware|session\(\{|require\(['"](express-session|cookie-session)['"]\)/i.test(text);
1050
1143
  }
1051
1144
 
1052
1145
  hasIdleTimeoutLogic(sourceFile) {