@sun-asterisk/sunlint 1.3.7 → 1.3.8
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/CHANGELOG.md +38 -0
- package/config/defaults/default.json +2 -1
- package/config/rule-analysis-strategies.js +20 -0
- package/config/rules/enhanced-rules-registry.json +190 -35
- package/core/file-targeting-service.js +83 -7
- package/package.json +1 -1
- package/rules/common/C065_one_behavior_per_test/analyzer.js +851 -0
- package/rules/common/C065_one_behavior_per_test/config.json +95 -0
- package/rules/security/S037_cache_headers/README.md +128 -0
- package/rules/security/S037_cache_headers/analyzer.js +263 -0
- package/rules/security/S037_cache_headers/config.json +50 -0
- package/rules/security/S037_cache_headers/regex-based-analyzer.js +463 -0
- package/rules/security/S037_cache_headers/symbol-based-analyzer.js +546 -0
- package/rules/security/S038_no_version_headers/README.md +234 -0
- package/rules/security/S038_no_version_headers/analyzer.js +262 -0
- package/rules/security/S038_no_version_headers/config.json +49 -0
- package/rules/security/S038_no_version_headers/regex-based-analyzer.js +339 -0
- package/rules/security/S038_no_version_headers/symbol-based-analyzer.js +375 -0
- package/rules/security/S039_no_session_tokens_in_url/README.md +198 -0
- package/rules/security/S039_no_session_tokens_in_url/analyzer.js +262 -0
- package/rules/security/S039_no_session_tokens_in_url/config.json +92 -0
- package/rules/security/S039_no_session_tokens_in_url/regex-based-analyzer.js +337 -0
- package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +436 -0
- package/rules/security/S049_short_validity_tokens/analyzer.js +175 -0
- package/rules/security/S049_short_validity_tokens/config.json +124 -0
- package/rules/security/S049_short_validity_tokens/regex-based-analyzer.js +295 -0
- package/rules/security/S049_short_validity_tokens/symbol-based-analyzer.js +389 -0
- package/rules/security/S051_password_length_policy/analyzer.js +410 -0
- package/rules/security/S051_password_length_policy/config.json +83 -0
- package/rules/security/S052_weak_otp_entropy/analyzer.js +403 -0
- package/rules/security/S052_weak_otp_entropy/config.json +57 -0
- package/rules/security/S054_no_default_accounts/README.md +129 -0
- package/rules/security/S054_no_default_accounts/analyzer.js +792 -0
- package/rules/security/S054_no_default_accounts/config.json +101 -0
- package/rules/security/S056_log_injection_protection/analyzer.js +242 -0
- package/rules/security/S056_log_injection_protection/config.json +148 -0
- package/rules/security/S056_log_injection_protection/regex-based-analyzer.js +120 -0
- package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +287 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S037 Regex-Based Analyzer - Configure comprehensive cache headers
|
|
3
|
+
* Refactored for per-route evaluation with framework support.
|
|
4
|
+
*/
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
class S037RegexBasedAnalyzer {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.ruleId = "S037";
|
|
10
|
+
|
|
11
|
+
// Framework-specific route patterns
|
|
12
|
+
this.routePatterns = {
|
|
13
|
+
// Express.js
|
|
14
|
+
express:
|
|
15
|
+
/\b(app|router)\.(get|post|put|delete|patch|use)\s*\(\s*['"`][^'"`]+['"`]/,
|
|
16
|
+
// Next.js API routes
|
|
17
|
+
nextjs: /export\s+(default\s+)?async?\s+function\s+handler\s*\(/,
|
|
18
|
+
// Next.js 13+ App Router
|
|
19
|
+
nextjsApp:
|
|
20
|
+
/export\s+async?\s+function\s+(GET|POST|PUT|DELETE|PATCH)\s*\(/,
|
|
21
|
+
// NestJS controllers
|
|
22
|
+
nestjs: /@(Get|Post|Put|Delete|Patch)\s*\(\s*['"`][^'"`]*['"`]?\s*\)/,
|
|
23
|
+
// Nuxt.js server routes
|
|
24
|
+
nuxtjs:
|
|
25
|
+
/export\s+(default\s+|const\s+(GET|POST|PUT|DELETE|PATCH)\s*=\s*)?defineEventHandler\s*\(/,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Header detection patterns
|
|
29
|
+
this.cacheControlPattern =
|
|
30
|
+
/res\.set(Header|)\s*\(\s*['"`]Cache-Control['"`]\s*,\s*['"`]([^'"`]+)['"`]/i;
|
|
31
|
+
this.pragmaPattern =
|
|
32
|
+
/res\.set(Header|)\s*\(\s*['"`]Pragma['"`]\s*,\s*['"`]no-cache['"`]/i;
|
|
33
|
+
this.expiresPattern =
|
|
34
|
+
/res\.set(Header|)\s*\(\s*['"`]Expires['"`]\s*,\s*['"`](0|Thu,? 01 Jan 1970[^'"`]*)['"`]/i;
|
|
35
|
+
this.bulkSetPattern = /res\.set\s*\(\s*\{([^}]+)\}/i;
|
|
36
|
+
|
|
37
|
+
// Next.js specific patterns
|
|
38
|
+
this.nextHeaderPattern =
|
|
39
|
+
/res\.setHeader\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*['"`]([^'"`]+)['"`]/i;
|
|
40
|
+
this.nextHeadersPattern =
|
|
41
|
+
/\w+\.headers\s*\.set\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*['"`]([^'"`]+)['"`]/i;
|
|
42
|
+
|
|
43
|
+
this.requiredDirectives = ["no-store", "no-cache", "must-revalidate"];
|
|
44
|
+
this.sensitiveIndicators =
|
|
45
|
+
/\b(session|auth(?:enticate|orization)?|token|jwt|csrf|login|logout|banking|crypto|salary|ssn|social[-_]?security|medical|prescription|biometric|audit|tax|legal|contract|personal[-_]?data|identity|finance|wallet|private[-_]?key|secret|sensitive[-_]?data|confidential|admin[-_]?panel|payment[-_]?process|credit[-_]?card|oauth|password|reset)\b/i;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async analyze(filePath) {
|
|
49
|
+
// Skip files that are unlikely to be route handlers
|
|
50
|
+
const skipPatterns = [
|
|
51
|
+
/\.dto\.ts$/,
|
|
52
|
+
/\.interface\.ts$/,
|
|
53
|
+
/\.module\.ts$/,
|
|
54
|
+
/\.service\.spec\.ts$/,
|
|
55
|
+
/\.controller\.spec\.ts$/,
|
|
56
|
+
/\.spec\.ts$/,
|
|
57
|
+
/\.test\.ts$/,
|
|
58
|
+
/\.d\.ts$/,
|
|
59
|
+
/\.types\.ts$/,
|
|
60
|
+
/\.constants?\.ts$/,
|
|
61
|
+
/\.config\.ts$/,
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const shouldSkip = skipPatterns.some((pattern) => pattern.test(filePath));
|
|
65
|
+
if (shouldSkip) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
70
|
+
const lines = content.split(/\r?\n/);
|
|
71
|
+
const violations = [];
|
|
72
|
+
|
|
73
|
+
let inRoute = false;
|
|
74
|
+
let braceDepth = 0;
|
|
75
|
+
let routeStartLine = 0;
|
|
76
|
+
let routeType = "";
|
|
77
|
+
let ccRaw = "";
|
|
78
|
+
let hasCC = false;
|
|
79
|
+
let hasPragma = false;
|
|
80
|
+
let hasExpires = false;
|
|
81
|
+
let sawSensitive = false;
|
|
82
|
+
let secureMiddlewareImplied = false;
|
|
83
|
+
let inBulkSet = false;
|
|
84
|
+
let bulkSetContent = "";
|
|
85
|
+
|
|
86
|
+
const reset = () => {
|
|
87
|
+
inRoute = false;
|
|
88
|
+
braceDepth = 0;
|
|
89
|
+
routeStartLine = 0;
|
|
90
|
+
routeType = "";
|
|
91
|
+
ccRaw = "";
|
|
92
|
+
hasCC =
|
|
93
|
+
hasPragma =
|
|
94
|
+
hasExpires =
|
|
95
|
+
sawSensitive =
|
|
96
|
+
secureMiddlewareImplied =
|
|
97
|
+
inBulkSet =
|
|
98
|
+
false;
|
|
99
|
+
bulkSetContent = "";
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const evaluate = () => {
|
|
103
|
+
if (!routeStartLine) return;
|
|
104
|
+
|
|
105
|
+
// Only evaluate if route appears sensitive OR we actually saw headers being set
|
|
106
|
+
const touchedHeaders = hasCC || hasPragma || hasExpires;
|
|
107
|
+
if (!(sawSensitive || touchedHeaders || secureMiddlewareImplied)) return;
|
|
108
|
+
|
|
109
|
+
if (secureMiddlewareImplied) return; // assume middleware sets all headers
|
|
110
|
+
|
|
111
|
+
const missing = [];
|
|
112
|
+
if (!hasCC) missing.push("Cache-Control");
|
|
113
|
+
if (!hasPragma) missing.push("Pragma");
|
|
114
|
+
if (!hasExpires) missing.push("Expires");
|
|
115
|
+
|
|
116
|
+
if (missing.length) {
|
|
117
|
+
violations.push({
|
|
118
|
+
ruleId: this.ruleId,
|
|
119
|
+
message: `Handler missing anti-cache headers: ${missing.join(
|
|
120
|
+
", "
|
|
121
|
+
)} (${routeType})`,
|
|
122
|
+
severity: "warning",
|
|
123
|
+
line: routeStartLine,
|
|
124
|
+
column: 1,
|
|
125
|
+
});
|
|
126
|
+
} else if (hasCC) {
|
|
127
|
+
const lower = ccRaw.toLowerCase();
|
|
128
|
+
const missingDir = this.requiredDirectives.filter(
|
|
129
|
+
(d) => !lower.includes(d)
|
|
130
|
+
);
|
|
131
|
+
if (missingDir.length) {
|
|
132
|
+
violations.push({
|
|
133
|
+
ruleId: this.ruleId,
|
|
134
|
+
message: `Cache-Control missing directives: ${missingDir.join(
|
|
135
|
+
", "
|
|
136
|
+
)} (${routeType})`,
|
|
137
|
+
severity: "warning",
|
|
138
|
+
line: routeStartLine,
|
|
139
|
+
column: 1,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < lines.length; i++) {
|
|
146
|
+
const line = lines[i];
|
|
147
|
+
|
|
148
|
+
// Detect route start for different frameworks
|
|
149
|
+
if (!inRoute) {
|
|
150
|
+
for (const [framework, pattern] of Object.entries(this.routePatterns)) {
|
|
151
|
+
if (pattern.test(line)) {
|
|
152
|
+
if (process.env.SUNLINT_DEBUG)
|
|
153
|
+
console.log(
|
|
154
|
+
`🔧 [S037-Regex] Found ${framework} route at line ${
|
|
155
|
+
i + 1
|
|
156
|
+
}: ${line.trim()}`
|
|
157
|
+
);
|
|
158
|
+
inRoute = true;
|
|
159
|
+
routeStartLine = i + 1;
|
|
160
|
+
routeType = framework;
|
|
161
|
+
braceDepth =
|
|
162
|
+
(line.match(/\{/g) || []).length -
|
|
163
|
+
(line.match(/\}/g) || []).length;
|
|
164
|
+
secureMiddlewareImplied = /secureNoCache|noCache|antiCache/.test(
|
|
165
|
+
line
|
|
166
|
+
);
|
|
167
|
+
if (secureMiddlewareImplied && process.env.SUNLINT_DEBUG) {
|
|
168
|
+
console.log(
|
|
169
|
+
`🔧 [S037-Regex] Secure middleware detected, skipping evaluation`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// If no opening brace on this line, look ahead for it
|
|
174
|
+
if (braceDepth === 0) {
|
|
175
|
+
for (let j = i + 1; j < Math.min(i + 3, lines.length); j++) {
|
|
176
|
+
const nextLine = lines[j];
|
|
177
|
+
if (nextLine.includes("{")) {
|
|
178
|
+
braceDepth =
|
|
179
|
+
(nextLine.match(/\{/g) || []).length -
|
|
180
|
+
(nextLine.match(/\}/g) || []).length;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Track braces for route scope
|
|
192
|
+
for (const ch of line) {
|
|
193
|
+
if (ch === "{") braceDepth++;
|
|
194
|
+
else if (ch === "}") braceDepth--;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check for sensitive indicators
|
|
198
|
+
if (this.sensitiveIndicators.test(line)) sawSensitive = true;
|
|
199
|
+
|
|
200
|
+
// Detect headers based on framework type
|
|
201
|
+
if (routeType === "express") {
|
|
202
|
+
// Check for start of bulk set
|
|
203
|
+
if (/res\.set\s*\(\s*\{/.test(line)) {
|
|
204
|
+
inBulkSet = true;
|
|
205
|
+
bulkSetContent = line;
|
|
206
|
+
if (process.env.SUNLINT_DEBUG)
|
|
207
|
+
console.log(`🔧 [S037-Regex] Starting bulk set detection`);
|
|
208
|
+
} else if (inBulkSet) {
|
|
209
|
+
bulkSetContent += line;
|
|
210
|
+
if (line.includes("}")) {
|
|
211
|
+
inBulkSet = false;
|
|
212
|
+
// Parse accumulated bulk set content
|
|
213
|
+
const cc =
|
|
214
|
+
/['"`]Cache-Control['"`]\s*:\s*['"`]([^'"`]+)['"`]/i.exec(
|
|
215
|
+
bulkSetContent
|
|
216
|
+
);
|
|
217
|
+
if (cc) {
|
|
218
|
+
hasCC = true;
|
|
219
|
+
ccRaw = cc[1];
|
|
220
|
+
if (process.env.SUNLINT_DEBUG)
|
|
221
|
+
console.log(
|
|
222
|
+
`🔧 [S037-Regex] Found bulk Cache-Control: ${ccRaw}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (
|
|
226
|
+
/(['"`]?Pragma['"`]?)\s*:\s*['"`]no-cache['"`]/i.test(
|
|
227
|
+
bulkSetContent
|
|
228
|
+
)
|
|
229
|
+
) {
|
|
230
|
+
hasPragma = true;
|
|
231
|
+
if (process.env.SUNLINT_DEBUG)
|
|
232
|
+
console.log(`🔧 [S037-Regex] Found bulk Pragma`);
|
|
233
|
+
}
|
|
234
|
+
if (
|
|
235
|
+
/(['"`]?Expires['"`]?)\s*:\s*['"`](0|Thu, 01 Jan 1970)/i.test(
|
|
236
|
+
bulkSetContent
|
|
237
|
+
)
|
|
238
|
+
) {
|
|
239
|
+
hasExpires = true;
|
|
240
|
+
if (process.env.SUNLINT_DEBUG)
|
|
241
|
+
console.log(`🔧 [S037-Regex] Found bulk Expires`);
|
|
242
|
+
}
|
|
243
|
+
bulkSetContent = "";
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Express.js patterns
|
|
248
|
+
const bulk = this.bulkSetPattern.exec(line);
|
|
249
|
+
if (bulk) {
|
|
250
|
+
const body = bulk[1];
|
|
251
|
+
const cc = /['"`]Cache-Control['"`]\s*:\s*['"`]([^'"`]+)['"`]/i.exec(
|
|
252
|
+
body
|
|
253
|
+
);
|
|
254
|
+
if (cc) {
|
|
255
|
+
hasCC = true;
|
|
256
|
+
ccRaw = cc[1];
|
|
257
|
+
}
|
|
258
|
+
if (/['"`]Pragma['"`]\s*:\s*['"`]no-cache['"`]/i.test(body))
|
|
259
|
+
hasPragma = true;
|
|
260
|
+
if (/['"`]Expires['"`]\s*:\s*['"`](0|Thu, 01 Jan 1970)/i.test(body))
|
|
261
|
+
hasExpires = true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const ccM = this.cacheControlPattern.exec(line);
|
|
265
|
+
if (ccM) {
|
|
266
|
+
hasCC = true;
|
|
267
|
+
ccRaw = ccM[2];
|
|
268
|
+
if (process.env.SUNLINT_DEBUG)
|
|
269
|
+
console.log(`🔧 [S037-Regex] Found Cache-Control: ${ccRaw}`);
|
|
270
|
+
}
|
|
271
|
+
if (this.pragmaPattern.test(line)) {
|
|
272
|
+
hasPragma = true;
|
|
273
|
+
if (process.env.SUNLINT_DEBUG)
|
|
274
|
+
console.log(`🔧 [S037-Regex] Found Pragma`);
|
|
275
|
+
}
|
|
276
|
+
if (this.expiresPattern.test(line)) {
|
|
277
|
+
hasExpires = true;
|
|
278
|
+
if (process.env.SUNLINT_DEBUG)
|
|
279
|
+
console.log(`🔧 [S037-Regex] Found Expires`);
|
|
280
|
+
}
|
|
281
|
+
} else if (routeType === "nestjs") {
|
|
282
|
+
// NestJS patterns - res.header()
|
|
283
|
+
const nestHeaderPattern =
|
|
284
|
+
/res\.header\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*['"`]([^'"`]+)['"`]/i;
|
|
285
|
+
const headerM = nestHeaderPattern.exec(line);
|
|
286
|
+
if (headerM) {
|
|
287
|
+
const [, headerName, headerValue] = headerM;
|
|
288
|
+
if (headerName.toLowerCase() === "cache-control") {
|
|
289
|
+
hasCC = true;
|
|
290
|
+
ccRaw = headerValue;
|
|
291
|
+
if (process.env.SUNLINT_DEBUG)
|
|
292
|
+
console.log(
|
|
293
|
+
`🔧 [S037-Regex] Found NestJS Cache-Control: ${ccRaw}`
|
|
294
|
+
);
|
|
295
|
+
} else if (
|
|
296
|
+
headerName.toLowerCase() === "pragma" &&
|
|
297
|
+
headerValue.toLowerCase() === "no-cache"
|
|
298
|
+
) {
|
|
299
|
+
hasPragma = true;
|
|
300
|
+
if (process.env.SUNLINT_DEBUG)
|
|
301
|
+
console.log(`🔧 [S037-Regex] Found NestJS Pragma`);
|
|
302
|
+
} else if (
|
|
303
|
+
headerName.toLowerCase() === "expires" &&
|
|
304
|
+
(headerValue === "0" || headerValue.includes("1970"))
|
|
305
|
+
) {
|
|
306
|
+
hasExpires = true;
|
|
307
|
+
if (process.env.SUNLINT_DEBUG)
|
|
308
|
+
console.log(`🔧 [S037-Regex] Found NestJS Expires`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} else if (routeType === "nextjs" || routeType === "nextjsApp") {
|
|
312
|
+
// Next.js patterns - support both res.setHeader and response.headers.set
|
|
313
|
+
const headerM = this.nextHeaderPattern.exec(line);
|
|
314
|
+
if (headerM) {
|
|
315
|
+
const [, headerName, headerValue] = headerM;
|
|
316
|
+
if (headerName.toLowerCase() === "cache-control") {
|
|
317
|
+
hasCC = true;
|
|
318
|
+
ccRaw = headerValue;
|
|
319
|
+
} else if (
|
|
320
|
+
headerName.toLowerCase() === "pragma" &&
|
|
321
|
+
headerValue.toLowerCase() === "no-cache"
|
|
322
|
+
) {
|
|
323
|
+
hasPragma = true;
|
|
324
|
+
} else if (
|
|
325
|
+
headerName.toLowerCase() === "expires" &&
|
|
326
|
+
(headerValue === "0" || headerValue.includes("1970"))
|
|
327
|
+
) {
|
|
328
|
+
hasExpires = true;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const headersM = this.nextHeadersPattern.exec(line);
|
|
333
|
+
if (headersM) {
|
|
334
|
+
const [, headerName, headerValue] = headersM;
|
|
335
|
+
if (headerName.toLowerCase() === "cache-control") {
|
|
336
|
+
hasCC = true;
|
|
337
|
+
ccRaw = headerValue;
|
|
338
|
+
if (process.env.SUNLINT_DEBUG)
|
|
339
|
+
console.log(
|
|
340
|
+
`🔧 [S037-Regex] Found Next.js headers.set Cache-Control: ${ccRaw}`
|
|
341
|
+
);
|
|
342
|
+
} else if (
|
|
343
|
+
headerName.toLowerCase() === "pragma" &&
|
|
344
|
+
headerValue.toLowerCase() === "no-cache"
|
|
345
|
+
) {
|
|
346
|
+
hasPragma = true;
|
|
347
|
+
if (process.env.SUNLINT_DEBUG)
|
|
348
|
+
console.log(`🔧 [S037-Regex] Found Next.js headers.set Pragma`);
|
|
349
|
+
} else if (
|
|
350
|
+
headerName.toLowerCase() === "expires" &&
|
|
351
|
+
(headerValue === "0" || headerValue.includes("1970"))
|
|
352
|
+
) {
|
|
353
|
+
hasExpires = true;
|
|
354
|
+
if (process.env.SUNLINT_DEBUG)
|
|
355
|
+
console.log(`🔧 [S037-Regex] Found Next.js headers.set Expires`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} else if (routeType === "nuxtjs") {
|
|
359
|
+
// Nuxt.js patterns - setHeader(event, name, value)
|
|
360
|
+
const nuxtHeaderPattern =
|
|
361
|
+
/setHeader\s*\(\s*event\s*,\s*['"`]([^'"`]+)['"`]\s*,\s*['"`]([^'"`]+)['"`]/i;
|
|
362
|
+
const nuxtHeaderM = nuxtHeaderPattern.exec(line);
|
|
363
|
+
if (nuxtHeaderM) {
|
|
364
|
+
const [, headerName, headerValue] = nuxtHeaderM;
|
|
365
|
+
if (headerName.toLowerCase() === "cache-control") {
|
|
366
|
+
hasCC = true;
|
|
367
|
+
ccRaw = headerValue;
|
|
368
|
+
if (process.env.SUNLINT_DEBUG)
|
|
369
|
+
console.log(
|
|
370
|
+
`🔧 [S037-Regex] Found Nuxt.js setHeader Cache-Control: ${ccRaw}`
|
|
371
|
+
);
|
|
372
|
+
} else if (
|
|
373
|
+
headerName.toLowerCase() === "pragma" &&
|
|
374
|
+
headerValue.toLowerCase() === "no-cache"
|
|
375
|
+
) {
|
|
376
|
+
hasPragma = true;
|
|
377
|
+
if (process.env.SUNLINT_DEBUG)
|
|
378
|
+
console.log(`🔧 [S037-Regex] Found Nuxt.js setHeader Pragma`);
|
|
379
|
+
} else if (
|
|
380
|
+
headerName.toLowerCase() === "expires" &&
|
|
381
|
+
(headerValue === "0" || headerValue.includes("1970"))
|
|
382
|
+
) {
|
|
383
|
+
hasExpires = true;
|
|
384
|
+
if (process.env.SUNLINT_DEBUG)
|
|
385
|
+
console.log(`🔧 [S037-Regex] Found Nuxt.js setHeader Expires`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Next.js response constructor with headers object
|
|
390
|
+
if (/headers:\s*\{/.test(line)) {
|
|
391
|
+
let j = i;
|
|
392
|
+
while (j < Math.min(i + 10, lines.length)) {
|
|
393
|
+
const headerLine = lines[j];
|
|
394
|
+
if (
|
|
395
|
+
/(['"`]?Cache-Control['"`]?)\s*:\s*['"`]([^'"`]+)['"`]/.test(
|
|
396
|
+
headerLine
|
|
397
|
+
)
|
|
398
|
+
) {
|
|
399
|
+
hasCC = true;
|
|
400
|
+
ccRaw = RegExp.$2;
|
|
401
|
+
if (process.env.SUNLINT_DEBUG)
|
|
402
|
+
console.log(
|
|
403
|
+
`🔧 [S037-Regex] Found Next.js constructor Cache-Control: ${ccRaw}`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (
|
|
407
|
+
/(['"`]?Pragma['"`]?)\s*:\s*['"`]no-cache['"`]/.test(headerLine)
|
|
408
|
+
) {
|
|
409
|
+
hasPragma = true;
|
|
410
|
+
if (process.env.SUNLINT_DEBUG)
|
|
411
|
+
console.log(`🔧 [S037-Regex] Found Next.js constructor Pragma`);
|
|
412
|
+
}
|
|
413
|
+
if (
|
|
414
|
+
/(['"`]?Expires['"`]?)\s*:\s*['"`](0|Thu.*1970)['"`]/.test(
|
|
415
|
+
headerLine
|
|
416
|
+
)
|
|
417
|
+
) {
|
|
418
|
+
hasExpires = true;
|
|
419
|
+
if (process.env.SUNLINT_DEBUG)
|
|
420
|
+
console.log(
|
|
421
|
+
`🔧 [S037-Regex] Found Next.js constructor Expires`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
if (headerLine.includes("}")) break;
|
|
425
|
+
j++;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
} else if (routeType === "nestjs") {
|
|
429
|
+
// NestJS patterns (similar to Express but with decorators)
|
|
430
|
+
if (/res\.header\s*\(\s*['"`]Cache-Control['"`]/.test(line))
|
|
431
|
+
hasCC = true;
|
|
432
|
+
if (/res\.header\s*\(\s*['"`]Pragma['"`]/.test(line)) hasPragma = true;
|
|
433
|
+
if (/res\.header\s*\(\s*['"`]Expires['"`]/.test(line))
|
|
434
|
+
hasExpires = true;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// End of route detection
|
|
438
|
+
if (
|
|
439
|
+
braceDepth <= 0 &&
|
|
440
|
+
(/\)\s*;?\s*$/.test(line) ||
|
|
441
|
+
/^\s*\}\s*$/.test(line) ||
|
|
442
|
+
/^export/.test(line))
|
|
443
|
+
) {
|
|
444
|
+
if (process.env.SUNLINT_DEBUG) {
|
|
445
|
+
console.log(
|
|
446
|
+
`🔧 [S037-Regex] Route ended, evaluating: CC=${hasCC}, Pragma=${hasPragma}, Expires=${hasExpires}, Sensitive=${sawSensitive}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
evaluate();
|
|
450
|
+
reset();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Safety evaluate if unbalanced at file end
|
|
455
|
+
if (inRoute) evaluate();
|
|
456
|
+
|
|
457
|
+
return violations;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
cleanup() {}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
module.exports = S037RegexBasedAnalyzer;
|