@sun-asterisk/sunlint 1.3.7 → 1.3.9

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 (51) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/config/defaults/default.json +2 -1
  3. package/config/rule-analysis-strategies.js +20 -0
  4. package/config/rules/enhanced-rules-registry.json +247 -53
  5. package/core/file-targeting-service.js +98 -7
  6. package/package.json +1 -1
  7. package/rules/common/C065_one_behavior_per_test/analyzer.js +851 -0
  8. package/rules/common/C065_one_behavior_per_test/config.json +95 -0
  9. package/rules/security/S020_no_eval_dynamic_code/README.md +136 -0
  10. package/rules/security/S020_no_eval_dynamic_code/analyzer.js +263 -0
  11. package/rules/security/S020_no_eval_dynamic_code/config.json +54 -0
  12. package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +307 -0
  13. package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +280 -0
  14. package/rules/security/S024_xpath_xxe_protection/symbol-based-analyzer.js +3 -3
  15. package/rules/security/S025_server_side_validation/symbol-based-analyzer.js +3 -4
  16. package/rules/security/S030_directory_browsing_protection/README.md +128 -0
  17. package/rules/security/S030_directory_browsing_protection/analyzer.js +264 -0
  18. package/rules/security/S030_directory_browsing_protection/config.json +63 -0
  19. package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +483 -0
  20. package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +539 -0
  21. package/rules/security/S033_samesite_session_cookies/symbol-based-analyzer.js +8 -9
  22. package/rules/security/S037_cache_headers/README.md +128 -0
  23. package/rules/security/S037_cache_headers/analyzer.js +263 -0
  24. package/rules/security/S037_cache_headers/config.json +50 -0
  25. package/rules/security/S037_cache_headers/regex-based-analyzer.js +463 -0
  26. package/rules/security/S037_cache_headers/symbol-based-analyzer.js +546 -0
  27. package/rules/security/S038_no_version_headers/README.md +234 -0
  28. package/rules/security/S038_no_version_headers/analyzer.js +262 -0
  29. package/rules/security/S038_no_version_headers/config.json +49 -0
  30. package/rules/security/S038_no_version_headers/regex-based-analyzer.js +339 -0
  31. package/rules/security/S038_no_version_headers/symbol-based-analyzer.js +375 -0
  32. package/rules/security/S039_no_session_tokens_in_url/README.md +198 -0
  33. package/rules/security/S039_no_session_tokens_in_url/analyzer.js +262 -0
  34. package/rules/security/S039_no_session_tokens_in_url/config.json +92 -0
  35. package/rules/security/S039_no_session_tokens_in_url/regex-based-analyzer.js +337 -0
  36. package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +443 -0
  37. package/rules/security/S049_short_validity_tokens/analyzer.js +175 -0
  38. package/rules/security/S049_short_validity_tokens/config.json +124 -0
  39. package/rules/security/S049_short_validity_tokens/regex-based-analyzer.js +295 -0
  40. package/rules/security/S049_short_validity_tokens/symbol-based-analyzer.js +389 -0
  41. package/rules/security/S051_password_length_policy/analyzer.js +410 -0
  42. package/rules/security/S051_password_length_policy/config.json +83 -0
  43. package/rules/security/S052_weak_otp_entropy/analyzer.js +403 -0
  44. package/rules/security/S052_weak_otp_entropy/config.json +57 -0
  45. package/rules/security/S054_no_default_accounts/README.md +129 -0
  46. package/rules/security/S054_no_default_accounts/analyzer.js +792 -0
  47. package/rules/security/S054_no_default_accounts/config.json +101 -0
  48. package/rules/security/S056_log_injection_protection/analyzer.js +242 -0
  49. package/rules/security/S056_log_injection_protection/config.json +148 -0
  50. package/rules/security/S056_log_injection_protection/regex-based-analyzer.js +120 -0
  51. package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +246 -0
@@ -0,0 +1,539 @@
1
+ /**
2
+ * S030 Symbol-Based Analyzer - Disable directory browsing and protect sensitive metadata files
3
+ * Enhanced to analyze static file serving configurations and middleware
4
+ */
5
+
6
+ class S030SymbolBasedAnalyzer {
7
+ constructor(semanticEngine) {
8
+ this.ruleId = "S030";
9
+ this.semanticEngine = semanticEngine;
10
+
11
+ // Web framework static serving methods
12
+ this.staticMethods = [
13
+ "static",
14
+ "serveStatic",
15
+ "staticFiles",
16
+ "useStaticAssets",
17
+ ];
18
+ this.frameworkMethods = {
19
+ express: ["static", "use"],
20
+ koa: ["static"],
21
+ fastify: ["register"],
22
+ hapi: ["route"],
23
+ nestjs: ["useStaticAssets", "useGlobalPrefix"],
24
+ nextjs: ["static", "use"],
25
+ };
26
+
27
+ // Sensitive file patterns that should be protected
28
+ this.sensitiveFiles = [
29
+ ".env",
30
+ ".git",
31
+ ".svn",
32
+ ".hg",
33
+ ".bzr",
34
+ ".CVS",
35
+ "config",
36
+ "settings",
37
+ "secrets",
38
+ "keys",
39
+ "backup",
40
+ "database",
41
+ ".aws",
42
+ ".ssh",
43
+ "credentials",
44
+ "private",
45
+ ];
46
+
47
+ // Directory listing indicators (dangerous configurations)
48
+ this.directoryListingPatterns = [
49
+ "autoIndex",
50
+ "directory",
51
+ "listing",
52
+ "browse",
53
+ "index",
54
+ "serveIndex",
55
+ "list",
56
+ "dotfiles",
57
+ "hidden",
58
+ ];
59
+
60
+ // Middleware that enables directory browsing
61
+ this.dangerousMiddleware = [
62
+ "serveIndex",
63
+ "serve-index",
64
+ "directory",
65
+ "autoIndex",
66
+ ];
67
+ }
68
+
69
+ async initialize() {}
70
+
71
+ analyze(sourceFile, filePath) {
72
+ const violations = [];
73
+
74
+ // Skip files that are unlikely to contain server configurations
75
+ const skipPatterns = [
76
+ /\.d\.ts$/,
77
+ /\.types\.ts$/,
78
+ /\.interface\.ts$/,
79
+ /\.constants?\.ts$/,
80
+ /\.spec\.ts$/,
81
+ /\.test\.ts$/,
82
+ /\.model\.ts$/,
83
+ /\.entity\.ts$/,
84
+ /\.dto\.ts$/,
85
+ ];
86
+
87
+ const shouldSkip = skipPatterns.some((pattern) => pattern.test(filePath));
88
+ if (shouldSkip) {
89
+ return violations;
90
+ }
91
+
92
+ try {
93
+ const { SyntaxKind } = require("ts-morph");
94
+
95
+ // Find static file serving configurations
96
+ const callExpressions = sourceFile.getDescendantsOfKind(
97
+ SyntaxKind.CallExpression
98
+ );
99
+
100
+ for (const call of callExpressions) {
101
+ try {
102
+ // Analyze static file serving configurations
103
+ this.analyzeStaticFileServing(call, violations, filePath);
104
+
105
+ // Analyze middleware registration
106
+ this.analyzeMiddlewareUsage(call, violations, filePath);
107
+
108
+ // Analyze route handlers for sensitive paths
109
+ this.analyzeSensitiveRoutes(call, violations, filePath);
110
+ } catch (error) {
111
+ console.warn(
112
+ `⚠ [S030] Call expression analysis failed:`,
113
+ error.message
114
+ );
115
+ }
116
+ }
117
+
118
+ // Analyze NestJS decorators
119
+ const decorators = sourceFile.getDescendantsOfKind(SyntaxKind.Decorator);
120
+ for (const decorator of decorators) {
121
+ try {
122
+ this.analyzeNestJSDecorators(decorator, violations, filePath);
123
+ } catch (error) {
124
+ console.warn(`⚠ [S030] Decorator analysis failed:`, error.message);
125
+ }
126
+ }
127
+
128
+ // Analyze NextJS export functions (API routes)
129
+ const functionDeclarations = sourceFile.getDescendantsOfKind(
130
+ SyntaxKind.FunctionDeclaration
131
+ );
132
+ for (const func of functionDeclarations) {
133
+ try {
134
+ this.analyzeNextJSAPIRoutes(func, violations, filePath);
135
+ } catch (error) {
136
+ console.warn(
137
+ `⚠ [S030] NextJS API route analysis failed:`,
138
+ error.message
139
+ );
140
+ }
141
+ }
142
+
143
+ // Check for variable declarations that might expose sensitive paths
144
+ const variableDeclarations = sourceFile.getDescendantsOfKind(
145
+ SyntaxKind.VariableDeclaration
146
+ );
147
+
148
+ for (const variable of variableDeclarations) {
149
+ try {
150
+ this.analyzeSensitivePathVariables(variable, violations, filePath);
151
+ } catch (error) {
152
+ console.warn(`⚠ [S030] Variable analysis failed:`, error.message);
153
+ }
154
+ }
155
+
156
+ // Check property assignments in object literals (config objects)
157
+ const objectLiterals = sourceFile.getDescendantsOfKind(
158
+ SyntaxKind.ObjectLiteralExpression
159
+ );
160
+
161
+ for (const objLiteral of objectLiterals) {
162
+ try {
163
+ this.analyzeConfigurationObjects(objLiteral, violations, filePath);
164
+ } catch (error) {
165
+ console.warn(
166
+ `⚠ [S030] Object literal analysis failed:`,
167
+ error.message
168
+ );
169
+ }
170
+ }
171
+ } catch (error) {
172
+ console.warn(
173
+ `⚠ [S030] Symbol analysis failed for ${filePath}:`,
174
+ error.message
175
+ );
176
+ }
177
+
178
+ return violations;
179
+ }
180
+
181
+ analyzeStaticFileServing(call, violations, filePath) {
182
+ const { SyntaxKind } = require("ts-morph");
183
+
184
+ const expression = call.getExpression();
185
+ let methodName = null;
186
+ let objectName = null;
187
+
188
+ // Handle property access: app.use(), express.static()
189
+ if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
190
+ methodName = expression.getName();
191
+ const object = expression.getExpression();
192
+ if (object.getKind() === SyntaxKind.Identifier) {
193
+ objectName = object.getText();
194
+ }
195
+ }
196
+
197
+ // Check for static file serving patterns
198
+ if (this.staticMethods.includes(methodName) || methodName === "use") {
199
+ const args = call.getArguments();
200
+
201
+ for (const arg of args) {
202
+ // Check if serving sensitive directories
203
+ if (arg.getKind() === SyntaxKind.StringLiteral) {
204
+ const path = arg.getLiteralValue();
205
+ if (this.isSensitivePath(path)) {
206
+ const startLine = call.getStartLineNumber();
207
+ violations.push({
208
+ ruleId: this.ruleId,
209
+ message: `Static file serving exposes sensitive path '${path}' - this could leak sensitive metadata files`,
210
+ severity: "error",
211
+ line: startLine,
212
+ column: 1,
213
+ });
214
+ }
215
+ }
216
+
217
+ // Check configuration objects for dangerous settings
218
+ if (arg.getKind() === SyntaxKind.ObjectLiteralExpression) {
219
+ this.analyzeStaticConfigObject(
220
+ arg,
221
+ violations,
222
+ call.getStartLineNumber()
223
+ );
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ analyzeMiddlewareUsage(call, violations, filePath) {
230
+ const { SyntaxKind } = require("ts-morph");
231
+
232
+ const expression = call.getExpression();
233
+
234
+ // Check for dangerous middleware usage
235
+ const args = call.getArguments();
236
+ for (const arg of args) {
237
+ if (arg.getKind() === SyntaxKind.CallExpression) {
238
+ const innerExpression = arg.getExpression();
239
+
240
+ if (innerExpression.getKind() === SyntaxKind.Identifier) {
241
+ const middlewareName = innerExpression.getText();
242
+ if (this.dangerousMiddleware.includes(middlewareName)) {
243
+ const startLine = call.getStartLineNumber();
244
+ violations.push({
245
+ ruleId: this.ruleId,
246
+ message: `Dangerous middleware '${middlewareName}' enables directory browsing - disable or configure securely`,
247
+ severity: "error",
248
+ line: startLine,
249
+ column: 1,
250
+ });
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ analyzeSensitiveRoutes(call, violations, filePath) {
258
+ const { SyntaxKind } = require("ts-morph");
259
+
260
+ const expression = call.getExpression();
261
+
262
+ // Check for route definitions that expose sensitive paths
263
+ if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
264
+ const methodName = expression.getName();
265
+ const httpMethods = ["get", "post", "put", "delete", "patch", "all"];
266
+
267
+ if (httpMethods.includes(methodName)) {
268
+ const args = call.getArguments();
269
+ if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {
270
+ const routePath = args[0].getLiteralValue();
271
+
272
+ if (this.isSensitivePath(routePath)) {
273
+ const startLine = call.getStartLineNumber();
274
+ violations.push({
275
+ ruleId: this.ruleId,
276
+ message: `Route '${routePath}' exposes sensitive path - implement proper access controls`,
277
+ severity: "error",
278
+ line: startLine,
279
+ column: 1,
280
+ });
281
+ }
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ analyzeStaticConfigObject(configObj, violations, lineNumber) {
288
+ const properties = configObj.getProperties();
289
+
290
+ for (const prop of properties) {
291
+ if (
292
+ prop.getKind() === require("ts-morph").SyntaxKind.PropertyAssignment
293
+ ) {
294
+ const name = prop.getName();
295
+ const value = prop.getInitializer();
296
+
297
+ // Check for dangerous configuration options
298
+ if (this.directoryListingPatterns.includes(name)) {
299
+ if (value && this.isTruthyValue(value)) {
300
+ violations.push({
301
+ ruleId: this.ruleId,
302
+ message: `Configuration option '${name}' enables directory browsing - set to false or remove`,
303
+ severity: "error",
304
+ line: lineNumber,
305
+ column: 1,
306
+ });
307
+ }
308
+ }
309
+
310
+ // Check for dotfiles configuration
311
+ if (name === "dotfiles" && value) {
312
+ const dotfilesValue = this.getValueAsString(value);
313
+ if (dotfilesValue === "allow" || dotfilesValue === true) {
314
+ violations.push({
315
+ ruleId: this.ruleId,
316
+ message:
317
+ "Dotfiles access is enabled - set dotfiles to 'deny' to protect sensitive files",
318
+ severity: "warning",
319
+ line: lineNumber,
320
+ column: 1,
321
+ });
322
+ }
323
+ }
324
+ }
325
+ }
326
+ }
327
+
328
+ analyzeSensitivePathVariables(variable, violations, filePath) {
329
+ const initializer = variable.getInitializer();
330
+ const varName = variable.getName();
331
+
332
+ if (
333
+ initializer &&
334
+ initializer.getKind() === require("ts-morph").SyntaxKind.StringLiteral
335
+ ) {
336
+ const path = initializer.getLiteralValue();
337
+
338
+ if (this.isSensitivePath(path)) {
339
+ const startLine = variable.getStartLineNumber();
340
+ violations.push({
341
+ ruleId: this.ruleId,
342
+ message: `Variable '${varName}' contains sensitive path '${path}' - ensure proper access controls`,
343
+ severity: "warning",
344
+ line: startLine,
345
+ column: 1,
346
+ });
347
+ }
348
+ }
349
+ }
350
+
351
+ analyzeConfigurationObjects(objLiteral, violations, filePath) {
352
+ const properties = objLiteral.getProperties();
353
+
354
+ for (const prop of properties) {
355
+ if (
356
+ prop.getKind() === require("ts-morph").SyntaxKind.PropertyAssignment
357
+ ) {
358
+ const name = prop.getName();
359
+ const value = prop.getInitializer();
360
+
361
+ // Check for configuration that might enable directory browsing
362
+ if (
363
+ name === "list" ||
364
+ name === "directory" ||
365
+ name === "autoIndex" ||
366
+ name === "listing"
367
+ ) {
368
+ if (value && this.isTruthyValue(value)) {
369
+ const startLine = prop.getStartLineNumber();
370
+ violations.push({
371
+ ruleId: this.ruleId,
372
+ message: `Configuration '${name}: true' enables directory browsing - disable for security`,
373
+ severity: "error",
374
+ line: startLine,
375
+ column: 1,
376
+ });
377
+ }
378
+ }
379
+
380
+ // Check for hidden files access
381
+ if (name === "hidden") {
382
+ if (value && this.isTruthyValue(value)) {
383
+ const startLine = prop.getStartLineNumber();
384
+ violations.push({
385
+ ruleId: this.ruleId,
386
+ message: `Configuration '${name}: true' enables hidden files access - disable for security`,
387
+ severity: "warning",
388
+ line: startLine,
389
+ column: 1,
390
+ });
391
+ }
392
+ }
393
+ }
394
+ }
395
+ }
396
+
397
+ isSensitivePath(path) {
398
+ if (!path || typeof path !== "string") return false;
399
+
400
+ const normalizedPath = path.toLowerCase();
401
+
402
+ return this.sensitiveFiles.some((sensitive) => {
403
+ // Check if path contains sensitive file/directory patterns
404
+ return (
405
+ normalizedPath.includes(sensitive.toLowerCase()) ||
406
+ normalizedPath.startsWith("/" + sensitive.toLowerCase()) ||
407
+ normalizedPath.startsWith("./" + sensitive.toLowerCase()) ||
408
+ normalizedPath.endsWith("/" + sensitive.toLowerCase())
409
+ );
410
+ });
411
+ }
412
+
413
+ isTruthyValue(valueNode) {
414
+ const { SyntaxKind } = require("ts-morph");
415
+
416
+ if (valueNode.getKind() === SyntaxKind.TrueKeyword) return true;
417
+ if (valueNode.getKind() === SyntaxKind.StringLiteral) {
418
+ const value = valueNode.getLiteralValue();
419
+ return value === "true" || value === "allow" || value === "yes";
420
+ }
421
+ if (valueNode.getKind() === SyntaxKind.NumericLiteral) {
422
+ return valueNode.getLiteralValue() !== 0;
423
+ }
424
+
425
+ return false;
426
+ }
427
+
428
+ getValueAsString(valueNode) {
429
+ const { SyntaxKind } = require("ts-morph");
430
+
431
+ if (valueNode.getKind() === SyntaxKind.StringLiteral) {
432
+ return valueNode.getLiteralValue();
433
+ }
434
+ if (valueNode.getKind() === SyntaxKind.TrueKeyword) return true;
435
+ if (valueNode.getKind() === SyntaxKind.FalseKeyword) return false;
436
+
437
+ return valueNode.getText();
438
+ }
439
+
440
+ analyzeNestJSDecorators(decorator, violations, filePath) {
441
+ const { SyntaxKind } = require("ts-morph");
442
+
443
+ const expression = decorator.getCallExpression();
444
+ if (!expression) return;
445
+
446
+ const decoratorName = expression.getExpression().getText();
447
+
448
+ // Check for route decorators with sensitive paths
449
+ if (["Get", "Post", "Put", "Delete", "Patch"].includes(decoratorName)) {
450
+ const args = expression.getArguments();
451
+ if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {
452
+ const routePath = args[0].getLiteralValue();
453
+
454
+ if (this.isSensitivePath(routePath)) {
455
+ const startLine = decorator.getStartLineNumber();
456
+ violations.push({
457
+ ruleId: this.ruleId,
458
+ message: `NestJS route '${routePath}' exposes sensitive path - implement proper access controls`,
459
+ severity: "error",
460
+ line: startLine,
461
+ column: 1,
462
+ });
463
+ }
464
+ }
465
+ }
466
+
467
+ // Check for Controller decorator with sensitive paths
468
+ if (decoratorName === "Controller") {
469
+ const args = expression.getArguments();
470
+ if (args.length > 0 && args[0].getKind() === SyntaxKind.StringLiteral) {
471
+ const controllerPath = args[0].getLiteralValue();
472
+
473
+ if (this.isSensitivePath(controllerPath)) {
474
+ const startLine = decorator.getStartLineNumber();
475
+ violations.push({
476
+ ruleId: this.ruleId,
477
+ message: `NestJS controller path '${controllerPath}' exposes sensitive path - review controller design`,
478
+ severity: "warning",
479
+ line: startLine,
480
+ column: 1,
481
+ });
482
+ }
483
+ }
484
+ }
485
+ }
486
+
487
+ analyzeNextJSAPIRoutes(func, violations, filePath) {
488
+ const { SyntaxKind } = require("ts-morph");
489
+
490
+ // Check if this is an exported API route function
491
+ const exportKeyword = func
492
+ .getFirstAncestorByKind(SyntaxKind.SourceFile)
493
+ ?.getExportedDeclarations()
494
+ ?.get(func.getName() || "default");
495
+
496
+ if (!exportKeyword) return;
497
+
498
+ // Check if function name suggests HTTP method (GET, POST, etc.)
499
+ const funcName = func.getName();
500
+ const httpMethods = ["GET", "POST", "PUT", "DELETE", "PATCH"];
501
+
502
+ if (httpMethods.includes(funcName)) {
503
+ // Check function body for sensitive file operations
504
+ const body = func.getBody();
505
+ if (body) {
506
+ const bodyText = body.getText();
507
+
508
+ // Check for sensitive file paths in the function body
509
+ const sensitivePatterns = [
510
+ /['"`][^'"`]*\.env[^'"`]*['"`]/g,
511
+ /['"`][^'"`]*\.git[^'"`]*['"`]/g,
512
+ /['"`][^'"`]*config[^'"`]*['"`]/g,
513
+ /['"`][^'"`]*backup[^'"`]*['"`]/g,
514
+ /['"`][^'"`]*secret[^'"`]*['"`]/g,
515
+ /['"`][^'"`]*\.ssh[^'"`]*['"`]/g,
516
+ ];
517
+
518
+ for (const pattern of sensitivePatterns) {
519
+ const matches = [...bodyText.matchAll(pattern)];
520
+ if (matches.length > 0) {
521
+ const startLine = func.getStartLineNumber();
522
+ violations.push({
523
+ ruleId: this.ruleId,
524
+ message: `NextJS API route '${funcName}' contains sensitive file references - ensure proper access controls`,
525
+ severity: "error",
526
+ line: startLine,
527
+ column: 1,
528
+ });
529
+ break; // Only report once per function
530
+ }
531
+ }
532
+ }
533
+ }
534
+ }
535
+
536
+ cleanup() {}
537
+ }
538
+
539
+ module.exports = S030SymbolBasedAnalyzer;
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * S033 Symbol-Based Analyzer - Set SameSite attribute for Session Cookies
3
- * Uses TypeScript compiler API for semantic analysis
3
+ * Uses ts-morph for semantic analysis (consistent with SunLint architecture)
4
4
  */
5
5
 
6
- const ts = require("typescript");
6
+ const { SyntaxKind } = require("ts-morph");
7
7
 
8
8
  class S033SymbolBasedAnalyzer {
9
9
  constructor(semanticEngine = null) {
@@ -78,7 +78,7 @@ class S033SymbolBasedAnalyzer {
78
78
  }
79
79
 
80
80
  try {
81
- const sourceFile = this.semanticEngine.getSourceFile(filePath);
81
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
82
82
  if (!sourceFile) {
83
83
  if (this.verbose) {
84
84
  console.log(
@@ -684,13 +684,12 @@ class S033SymbolBasedAnalyzer {
684
684
  /**
685
685
  * Enhanced method name detection with framework support
686
686
  */
687
- getMorphMethodName(callNode) {
687
+ extractMethodName(callExpression) {
688
688
  try {
689
- const expression = callNode.getExpression();
690
- const ts = require("typescript");
689
+ const expression = callExpression.getExpression();
691
690
 
692
691
  // Handle property access expressions (obj.method)
693
- if (expression.getKind() === ts.SyntaxKind.PropertyAccessExpression) {
692
+ if (expression.getKind() === SyntaxKind.PropertyAccessExpression) {
694
693
  const propertyName = expression.getNameNode().getText();
695
694
 
696
695
  // Check for chained method calls like response.cookies.set
@@ -698,7 +697,7 @@ class S033SymbolBasedAnalyzer {
698
697
  const objectExpression = expression.getExpression();
699
698
  if (
700
699
  objectExpression.getKind() ===
701
- ts.SyntaxKind.PropertyAccessExpression
700
+ SyntaxKind.PropertyAccessExpression
702
701
  ) {
703
702
  const parentProperty = objectExpression.getNameNode().getText();
704
703
  if (parentProperty === "cookies") {
@@ -712,7 +711,7 @@ class S033SymbolBasedAnalyzer {
712
711
  }
713
712
 
714
713
  // Handle direct function calls
715
- if (expression.getKind() === ts.SyntaxKind.Identifier) {
714
+ if (expression.getKind() === SyntaxKind.Identifier) {
716
715
  return expression.getText();
717
716
  }
718
717