@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.
- package/origin-rules/common-en.md +14 -0
- package/package.json +1 -1
- package/rules/common/C006_function_naming/analyzer.js +279 -143
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +2 -1
- package/rules/security/S031_secure_session_cookies/analyzer.js +67 -112
- package/rules/security/S041_session_token_invalidation/analyzer.js +1 -1
- package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +21 -2
- package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +97 -4
- package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +0 -296
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* S031 Main Analyzer - Set Secure flag for Session Cookies
|
|
3
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
76
|
-
if (this.
|
|
77
|
-
this.
|
|
56
|
+
// Ensure verbose flag is propagated
|
|
57
|
+
if (this.symbolAnalyzer) {
|
|
58
|
+
this.symbolAnalyzer.verbose = this.verbose;
|
|
78
59
|
}
|
|
79
60
|
|
|
80
|
-
if (
|
|
81
|
-
console.log(
|
|
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
|
-
//
|
|
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]
|
|
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(
|
|
135
|
+
console.log(
|
|
136
|
+
`🔧 [S031] Source file found, analyzing with symbol-based...`
|
|
137
|
+
);
|
|
158
138
|
}
|
|
159
|
-
|
|
139
|
+
|
|
140
|
+
const violations = await this.symbolAnalyzer.analyze(
|
|
160
141
|
sourceFile,
|
|
161
142
|
filePath
|
|
162
143
|
);
|
|
163
144
|
|
|
164
|
-
// Add to
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
166
|
+
console.log(`⚠️ [S031] Source file not found in project`);
|
|
182
167
|
}
|
|
183
168
|
}
|
|
184
169
|
} catch (error) {
|
|
185
|
-
console.warn(
|
|
170
|
+
console.warn(`⚠️ [S031] Symbol analysis failed: ${error.message}`);
|
|
186
171
|
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
*
|
|
193
|
+
* Methods for compatibility with different engine invocation patterns
|
|
237
194
|
*/
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
199
|
+
async analyzeWithSemantics(filePath, options = {}) {
|
|
200
|
+
return this.analyzeFile(filePath, options);
|
|
201
|
+
}
|
|
251
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Get analyzer metadata
|
|
252
205
|
*/
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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:
|
|
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;
|
package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js
CHANGED
|
@@ -25,7 +25,9 @@ class S042SymbolBasedAnalyzer {
|
|
|
25
25
|
/\/dist\//,
|
|
26
26
|
/\/build\//,
|
|
27
27
|
/\.spec\.ts$/,
|
|
28
|
-
/\.
|
|
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:
|
|
688
|
-
column:
|
|
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
|
-
|
|
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) {
|