circle-ir 3.167.0 → 3.173.0

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 (40) hide show
  1. package/dist/analysis/config-loader.d.ts.map +1 -1
  2. package/dist/analysis/config-loader.js +79 -0
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  5. package/dist/analysis/passes/cross-file-pass.js +3 -2
  6. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  7. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  8. package/dist/analysis/passes/interprocedural-pass.js +3 -2
  9. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  10. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  11. package/dist/analysis/passes/sink-filter-pass.js +23 -2
  12. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  13. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  14. package/dist/analysis/passes/taint-propagation-pass.js +29 -15
  15. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  16. package/dist/analysis/sanitizer-index.d.ts +34 -0
  17. package/dist/analysis/sanitizer-index.d.ts.map +1 -0
  18. package/dist/analysis/sanitizer-index.js +48 -0
  19. package/dist/analysis/sanitizer-index.js.map +1 -0
  20. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  21. package/dist/analysis/taint-matcher.js +93 -45
  22. package/dist/analysis/taint-matcher.js.map +1 -1
  23. package/dist/analysis/taint-propagation.d.ts.map +1 -1
  24. package/dist/analysis/taint-propagation.js +5 -2
  25. package/dist/analysis/taint-propagation.js.map +1 -1
  26. package/dist/analyzer.d.ts.map +1 -1
  27. package/dist/analyzer.js +51 -2
  28. package/dist/analyzer.js.map +1 -1
  29. package/dist/browser/circle-ir.js +319 -95
  30. package/dist/core/circle-ir-core.cjs +193 -72
  31. package/dist/core/circle-ir-core.js +193 -72
  32. package/dist/core/extractors/cfg.d.ts +11 -1
  33. package/dist/core/extractors/cfg.d.ts.map +1 -1
  34. package/dist/core/extractors/cfg.js +18 -9
  35. package/dist/core/extractors/cfg.js.map +1 -1
  36. package/dist/core/extractors/imports.d.ts +9 -1
  37. package/dist/core/extractors/imports.d.ts.map +1 -1
  38. package/dist/core/extractors/imports.js +29 -22
  39. package/dist/core/extractors/imports.js.map +1 -1
  40. package/package.json +1 -1
@@ -7543,33 +7543,33 @@ function detectLanguage2(tree) {
7543
7543
  if (pythonScore > jsScore && pythonScore > javaScore) return "python";
7544
7544
  return jsScore > javaScore ? "javascript" : "java";
7545
7545
  }
7546
- function extractImports(tree, language) {
7546
+ function extractImports(tree, language, cache) {
7547
7547
  const effectiveLanguage = language ?? detectLanguage2(tree);
7548
7548
  const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
7549
7549
  const isPython = effectiveLanguage === "python";
7550
7550
  const isRust = effectiveLanguage === "rust";
7551
7551
  if (effectiveLanguage === "go") {
7552
- return extractGoImports(tree);
7552
+ return extractGoImports(tree, cache);
7553
7553
  }
7554
7554
  if (isRust) {
7555
- return extractRustImports(tree);
7555
+ return extractRustImports(tree, cache);
7556
7556
  }
7557
7557
  if (isPython) {
7558
- return extractPythonImports(tree);
7558
+ return extractPythonImports(tree, cache);
7559
7559
  }
7560
7560
  if (isJavaScript) {
7561
- return extractJavaScriptImports(tree);
7561
+ return extractJavaScriptImports(tree, cache);
7562
7562
  }
7563
- return extractJavaImports(tree);
7563
+ return extractJavaImports(tree, cache);
7564
7564
  }
7565
- function extractJavaScriptImports(tree) {
7565
+ function extractJavaScriptImports(tree, cache) {
7566
7566
  const imports = [];
7567
- const importStatements = findNodes(tree.rootNode, "import_statement");
7567
+ const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
7568
7568
  for (const importStmt of importStatements) {
7569
7569
  const importInfos = extractJSImportInfo(importStmt);
7570
7570
  imports.push(...importInfos);
7571
7571
  }
7572
- const exportStatements = findNodes(tree.rootNode, "export_statement");
7572
+ const exportStatements = getNodesFromCache(tree.rootNode, "export_statement", cache);
7573
7573
  for (const exportStmt of exportStatements) {
7574
7574
  const sourceNode = exportStmt.childForFieldName("source");
7575
7575
  if (!sourceNode) continue;
@@ -7583,13 +7583,13 @@ function extractJavaScriptImports(tree) {
7583
7583
  line_number: exportStmt.startPosition.row + 1
7584
7584
  });
7585
7585
  }
7586
- const requireCalls = findRequireCalls(tree);
7586
+ const requireCalls = findRequireCalls(tree, cache);
7587
7587
  imports.push(...requireCalls);
7588
7588
  return imports;
7589
7589
  }
7590
- function extractJavaImports(tree) {
7590
+ function extractJavaImports(tree, cache) {
7591
7591
  const imports = [];
7592
- const importDecls = findNodes(tree.rootNode, "import_declaration");
7592
+ const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
7593
7593
  for (const importDecl of importDecls) {
7594
7594
  const importInfo = extractJavaImportInfo(importDecl);
7595
7595
  if (importInfo) {
@@ -7737,9 +7737,9 @@ function extractJSImportInfo(node) {
7737
7737
  }
7738
7738
  return imports;
7739
7739
  }
7740
- function findRequireCalls(tree) {
7740
+ function findRequireCalls(tree, cache) {
7741
7741
  const imports = [];
7742
- const callExpressions = findNodes(tree.rootNode, "call_expression");
7742
+ const callExpressions = getNodesFromCache(tree.rootNode, "call_expression", cache);
7743
7743
  for (const call of callExpressions) {
7744
7744
  const funcNode = call.childForFieldName("function");
7745
7745
  if (!funcNode || getNodeText(funcNode) !== "require") continue;
@@ -7853,14 +7853,14 @@ function parseImportPath(fullPath, isWildcard) {
7853
7853
  fromPackage: fullPath.substring(0, lastDot)
7854
7854
  };
7855
7855
  }
7856
- function extractPythonImports(tree) {
7856
+ function extractPythonImports(tree, cache) {
7857
7857
  const imports = [];
7858
- const importStatements = findNodes(tree.rootNode, "import_statement");
7858
+ const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
7859
7859
  for (const stmt of importStatements) {
7860
7860
  const importInfos = extractPythonImportStatement(stmt);
7861
7861
  imports.push(...importInfos);
7862
7862
  }
7863
- const importFromStatements = findNodes(tree.rootNode, "import_from_statement");
7863
+ const importFromStatements = getNodesFromCache(tree.rootNode, "import_from_statement", cache);
7864
7864
  for (const stmt of importFromStatements) {
7865
7865
  const importInfos = extractPythonFromImportStatement(stmt);
7866
7866
  imports.push(...importInfos);
@@ -7954,9 +7954,9 @@ function extractPythonFromImportStatement(node) {
7954
7954
  }
7955
7955
  return imports;
7956
7956
  }
7957
- function extractRustImports(tree) {
7957
+ function extractRustImports(tree, cache) {
7958
7958
  const imports = [];
7959
- const useDecls = findNodes(tree.rootNode, "use_declaration");
7959
+ const useDecls = getNodesFromCache(tree.rootNode, "use_declaration", cache);
7960
7960
  for (const useDecl of useDecls) {
7961
7961
  const useImports = extractRustUseDecl(useDecl);
7962
7962
  imports.push(...useImports);
@@ -8098,9 +8098,9 @@ function extractRustScopedUseList(node, lineNumber) {
8098
8098
  }
8099
8099
  return imports;
8100
8100
  }
8101
- function extractGoImports(tree) {
8101
+ function extractGoImports(tree, cache) {
8102
8102
  const imports = [];
8103
- const importDecls = findNodes(tree.rootNode, "import_declaration");
8103
+ const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
8104
8104
  for (const decl of importDecls) {
8105
8105
  const singleSpec = findGoChildByType(decl, "import_spec");
8106
8106
  if (singleSpec) {
@@ -8235,7 +8235,7 @@ function detectLanguage3(tree) {
8235
8235
  }
8236
8236
  return jsScore > javaScore ? "javascript" : "java";
8237
8237
  }
8238
- function buildCFG(tree, language) {
8238
+ function buildCFG(tree, language, cache) {
8239
8239
  const effectiveLanguage = language ?? detectLanguage3(tree);
8240
8240
  const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
8241
8241
  const allBlocks = [];
@@ -8249,11 +8249,11 @@ function buildCFG(tree, language) {
8249
8249
  }
8250
8250
  if (isJavaScript) {
8251
8251
  const functions = [
8252
- ...findNodes(tree.rootNode, "function_declaration"),
8253
- ...findNodes(tree.rootNode, "arrow_function"),
8254
- ...findNodes(tree.rootNode, "method_definition"),
8255
- ...findNodes(tree.rootNode, "function"),
8256
- ...findNodes(tree.rootNode, "function_expression")
8252
+ ...getNodesFromCache(tree.rootNode, "function_declaration", cache),
8253
+ ...getNodesFromCache(tree.rootNode, "arrow_function", cache),
8254
+ ...getNodesFromCache(tree.rootNode, "method_definition", cache),
8255
+ ...getNodesFromCache(tree.rootNode, "function", cache),
8256
+ ...getNodesFromCache(tree.rootNode, "function_expression", cache)
8257
8257
  ];
8258
8258
  for (const func2 of functions) {
8259
8259
  const body2 = func2.childForFieldName("body");
@@ -8275,8 +8275,8 @@ function buildCFG(tree, language) {
8275
8275
  }
8276
8276
  } else {
8277
8277
  const methods = [
8278
- ...findNodes(tree.rootNode, "method_declaration"),
8279
- ...findNodes(tree.rootNode, "constructor_declaration")
8278
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache),
8279
+ ...getNodesFromCache(tree.rootNode, "constructor_declaration", cache)
8280
8280
  ];
8281
8281
  for (const method of methods) {
8282
8282
  const body2 = method.childForFieldName("body");
@@ -10529,7 +10529,86 @@ var DEFAULT_SOURCES = [
10529
10529
  { method: "recv", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
10530
10530
  { method: "read", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
10531
10531
  { method: "read_to_end", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
10532
- { method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true }
10532
+ { method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
10533
+ // =========================================================================
10534
+ // Modern taint sources (cognium-dev #242 — 3.170.0)
10535
+ // GraphQL resolver args, gRPC request metadata, cache reads (second-order
10536
+ // taint), and JWT claims (unverified decode). These attacker-influenced
10537
+ // surfaces were previously silent — a resolver that concatenated an
10538
+ // `Args`-annotated field into a SQL string produced zero flows.
10539
+ // =========================================================================
10540
+ // --- GraphQL resolver argument sources (JS / TS — Apollo, TypeGraphQL, NestJS) ---
10541
+ // TypeGraphQL / NestJS annotate query/mutation/subscription methods; every
10542
+ // parameter on the annotated method carries attacker input from the GraphQL
10543
+ // POST body's `variables` field.
10544
+ { method_annotation: "Query", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
10545
+ { method_annotation: "Mutation", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
10546
+ { method_annotation: "Subscription", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
10547
+ { method_annotation: "FieldResolver", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
10548
+ { method_annotation: "ResolveField", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
10549
+ // Parameter-level: `@Arg('id') id: string`, `@Args('input') input: FooDto`.
10550
+ { annotation: "Arg", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
10551
+ { annotation: "Args", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
10552
+ // --- GraphQL resolver argument sources (Python — Strawberry, Graphene, Ariadne) ---
10553
+ { method_annotation: "strawberry.field", type: "http_body", severity: "high", languages: ["python"] },
10554
+ { method_annotation: "strawberry.mutation", type: "http_body", severity: "high", languages: ["python"] },
10555
+ { method_annotation: "strawberry.subscription", type: "http_body", severity: "high", languages: ["python"] },
10556
+ // --- GraphQL resolver argument sources (Java — Netflix DGS, SPQR, graphql-java-annotations) ---
10557
+ { method_annotation: "DgsQuery", type: "http_body", severity: "high", languages: ["java"] },
10558
+ { method_annotation: "DgsMutation", type: "http_body", severity: "high", languages: ["java"] },
10559
+ { method_annotation: "DgsSubscription", type: "http_body", severity: "high", languages: ["java"] },
10560
+ { method_annotation: "DgsData", type: "http_body", severity: "high", languages: ["java"] },
10561
+ { method_annotation: "GraphQLQuery", type: "http_body", severity: "high", languages: ["java"] },
10562
+ { method_annotation: "GraphQLMutation", type: "http_body", severity: "high", languages: ["java"] },
10563
+ { annotation: "InputArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
10564
+ { annotation: "GraphQLArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
10565
+ // --- gRPC request metadata (Python — grpcio) ---
10566
+ // `context.invocation_metadata()` returns the caller-supplied metadata tuple.
10567
+ // No `class` filter — receiver names vary (`context`, `ctx`, `servicer_context`).
10568
+ { method: "invocation_metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
10569
+ // --- gRPC request metadata (JS / TS — @grpc/grpc-js) ---
10570
+ // `call.metadata.get(key)` / `call.metadata.getMap()` inside a service handler.
10571
+ { method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
10572
+ { method: "getMap", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
10573
+ // --- gRPC request metadata (Go — google.golang.org/grpc/metadata) ---
10574
+ // `md, _ := metadata.FromIncomingContext(ctx)` pulls the caller's headers.
10575
+ { method: "FromIncomingContext", class: "metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] },
10576
+ // --- gRPC request metadata (Java — io.grpc.Metadata) ---
10577
+ // Server-side interceptor receives `Metadata headers`; `headers.get(KEY)`
10578
+ // returns caller-supplied header values.
10579
+ { method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
10580
+ // --- Cache reads (second-order taint — Redis / Memcached / Django cache) ---
10581
+ // The cache round-trip is a canonical second-order sink: whatever was
10582
+ // written previously (potentially attacker-controlled) resurfaces on read.
10583
+ // Severity 'medium' — cache contents are usually filtered by the writer but
10584
+ // the read side often forgets that guarantee. Class-scoped to `Redis`,
10585
+ // `Jedis`, `cache` to avoid colliding with generic `Map.get()`.
10586
+ { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
10587
+ { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
10588
+ { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
10589
+ { method: "lrange", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
10590
+ { method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
10591
+ { method: "get_many", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
10592
+ { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
10593
+ { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
10594
+ { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
10595
+ { method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
10596
+ { method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
10597
+ { method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
10598
+ // --- JWT claims (unverified decode — PyJWT / jose / jsonwebtoken / auth0 java-jwt / golang-jwt) ---
10599
+ // A JWT's payload is *always* attacker-authored. Even after verification
10600
+ // the *contents* of the claims (username, role, custom fields) are not
10601
+ // trusted for downstream flows into SQL, HTML, shell, etc. The `decode`
10602
+ // variants here return the parsed claims dictionary/object.
10603
+ { method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
10604
+ { method: "get_unverified_claims", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
10605
+ { method: "get_unverified_header", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
10606
+ { method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
10607
+ { method: "decode", class: "jsonwebtoken", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
10608
+ { method: "decodeJwt", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
10609
+ { method: "decodeProtectedHeader", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
10610
+ { method: "decode", class: "JWT", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
10611
+ { method: "ParseUnverified", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] }
10533
10612
  ];
10534
10613
  var DEFAULT_SINKS = [
10535
10614
  // SQL Injection (CWE-89)
@@ -12582,6 +12661,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
12582
12661
  for (const param of method.parameters) {
12583
12662
  for (const pattern of patterns) {
12584
12663
  if (pattern.annotation && pattern.param_tainted) {
12664
+ if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12665
+ continue;
12666
+ }
12585
12667
  if (matchesAnnotation(param.annotations, pattern.annotation)) {
12586
12668
  const paramLine = param.line ?? method.start_line;
12587
12669
  sources.push({
@@ -12602,6 +12684,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
12602
12684
  for (const method of type.methods) {
12603
12685
  for (const pattern of patterns) {
12604
12686
  if (!pattern.method_annotation) continue;
12687
+ if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12688
+ continue;
12689
+ }
12605
12690
  if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
12606
12691
  for (const param of method.parameters) {
12607
12692
  const paramLine = param.line ?? method.start_line;
@@ -12866,7 +12951,7 @@ function isParameterizedQueryCall(call, pattern) {
12866
12951
  const secondArg = call.arguments.find((a) => a.position === 1);
12867
12952
  if (secondArg?.expression) {
12868
12953
  const expr = secondArg.expression.trim();
12869
- if (expr.startsWith("[")) {
12954
+ if (expr.startsWith("[") && !/^\[\s*\]$/.test(expr)) {
12870
12955
  return true;
12871
12956
  }
12872
12957
  }
@@ -13307,52 +13392,74 @@ function matchesSourcePattern(call, pattern) {
13307
13392
  }
13308
13393
  return false;
13309
13394
  }
13395
+ var JS_SOURCE_PATTERN_CACHE = /* @__PURE__ */ new WeakMap();
13396
+ var JS_FALLBACK_COMPILED = (() => {
13397
+ const bases = [
13398
+ { base: "req\\.params", sourceType: "http_param" },
13399
+ { base: "req\\.query", sourceType: "http_param" },
13400
+ { base: "req\\.body", sourceType: "http_body" },
13401
+ { base: "req\\.headers", sourceType: "http_header" },
13402
+ { base: "req\\.cookies", sourceType: "http_cookie" },
13403
+ { base: "req\\.url", sourceType: "http_path" },
13404
+ { base: "req\\.path", sourceType: "http_path" },
13405
+ { base: "req\\.originalUrl", sourceType: "http_path" },
13406
+ { base: "req\\.file", sourceType: "file_input" },
13407
+ { base: "req\\.files", sourceType: "file_input" },
13408
+ { base: "request\\.params", sourceType: "http_param" },
13409
+ { base: "request\\.query", sourceType: "http_param" },
13410
+ { base: "request\\.body", sourceType: "http_body" },
13411
+ { base: "request\\.headers", sourceType: "http_header" },
13412
+ { base: "process\\.env", sourceType: "env_input" },
13413
+ { base: "process\\.argv", sourceType: "io_input" },
13414
+ { base: "ctx\\.query", sourceType: "http_param" },
13415
+ { base: "ctx\\.params", sourceType: "http_param" },
13416
+ { base: "ctx\\.request", sourceType: "http_body" }
13417
+ ];
13418
+ const exact = [];
13419
+ const contained = [];
13420
+ for (const { base, sourceType } of bases) {
13421
+ exact.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
13422
+ contained.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
13423
+ }
13424
+ return { exact, contained };
13425
+ })();
13426
+ function compileJsSourcePatterns(sourcePatterns) {
13427
+ const cached = JS_SOURCE_PATTERN_CACHE.get(sourcePatterns);
13428
+ if (cached) return cached;
13429
+ const exact = [];
13430
+ const contained = [];
13431
+ for (const sp of sourcePatterns) {
13432
+ if (sp.property && sp.object && sp.property_tainted) {
13433
+ exact.push({
13434
+ pattern: new RegExp(`^${sp.object}\\.${sp.property}\\b`),
13435
+ sourceType: sp.type
13436
+ });
13437
+ contained.push({
13438
+ pattern: new RegExp(`\\b${sp.object}\\.${sp.property}\\b`),
13439
+ sourceType: sp.type
13440
+ });
13441
+ }
13442
+ }
13443
+ const compiled = { exact, contained };
13444
+ JS_SOURCE_PATTERN_CACHE.set(sourcePatterns, compiled);
13445
+ return compiled;
13446
+ }
13310
13447
  function isJavaScriptTaintedArgument(argExpression, sourcePatterns) {
13311
- const exactPatterns = [];
13312
- const containedPatterns = [];
13313
- if (sourcePatterns) {
13314
- for (const sp of sourcePatterns) {
13315
- if (sp.property && sp.object && sp.property_tainted) {
13316
- const exactRegex = new RegExp(`^${sp.object}\\.${sp.property}\\b`);
13317
- exactPatterns.push({ pattern: exactRegex, sourceType: sp.type });
13318
- const containedRegex = new RegExp(`\\b${sp.object}\\.${sp.property}\\b`);
13319
- containedPatterns.push({ pattern: containedRegex, sourceType: sp.type });
13320
- }
13321
- }
13322
- }
13323
- if (exactPatterns.length === 0) {
13324
- const basePatterns = [
13325
- { base: "req\\.params", sourceType: "http_param" },
13326
- { base: "req\\.query", sourceType: "http_param" },
13327
- { base: "req\\.body", sourceType: "http_body" },
13328
- { base: "req\\.headers", sourceType: "http_header" },
13329
- { base: "req\\.cookies", sourceType: "http_cookie" },
13330
- { base: "req\\.url", sourceType: "http_path" },
13331
- { base: "req\\.path", sourceType: "http_path" },
13332
- { base: "req\\.originalUrl", sourceType: "http_path" },
13333
- { base: "req\\.file", sourceType: "file_input" },
13334
- { base: "req\\.files", sourceType: "file_input" },
13335
- { base: "request\\.params", sourceType: "http_param" },
13336
- { base: "request\\.query", sourceType: "http_param" },
13337
- { base: "request\\.body", sourceType: "http_body" },
13338
- { base: "request\\.headers", sourceType: "http_header" },
13339
- { base: "process\\.env", sourceType: "env_input" },
13340
- { base: "process\\.argv", sourceType: "io_input" },
13341
- { base: "ctx\\.query", sourceType: "http_param" },
13342
- { base: "ctx\\.params", sourceType: "http_param" },
13343
- { base: "ctx\\.request", sourceType: "http_body" }
13344
- ];
13345
- for (const { base, sourceType } of basePatterns) {
13346
- exactPatterns.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
13347
- containedPatterns.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
13448
+ let compiled;
13449
+ if (sourcePatterns && sourcePatterns.length > 0) {
13450
+ compiled = compileJsSourcePatterns(sourcePatterns);
13451
+ if (compiled.exact.length === 0) {
13452
+ compiled = JS_FALLBACK_COMPILED;
13348
13453
  }
13454
+ } else {
13455
+ compiled = JS_FALLBACK_COMPILED;
13349
13456
  }
13350
- for (const { pattern, sourceType } of exactPatterns) {
13457
+ for (const { pattern, sourceType } of compiled.exact) {
13351
13458
  if (pattern.test(argExpression)) {
13352
13459
  return { isTainted: true, sourceType };
13353
13460
  }
13354
13461
  }
13355
- for (const { pattern, sourceType } of containedPatterns) {
13462
+ for (const { pattern, sourceType } of compiled.contained) {
13356
13463
  if (pattern.test(argExpression)) {
13357
13464
  return { isTainted: true, sourceType };
13358
13465
  }
@@ -14344,6 +14451,20 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
14344
14451
  return { visited, lines, hopCapReached };
14345
14452
  }
14346
14453
 
14454
+ // src/analysis/sanitizer-index.ts
14455
+ var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
14456
+ function getSanitizesSet(san) {
14457
+ let s = SANITIZER_SET_CACHE.get(san);
14458
+ if (!s) {
14459
+ s = new Set(san.sanitizes);
14460
+ SANITIZER_SET_CACHE.set(san, s);
14461
+ }
14462
+ return s;
14463
+ }
14464
+ function sanitizerCoversSink(san, sinkType) {
14465
+ return getSanitizesSet(san).has(sinkType);
14466
+ }
14467
+
14347
14468
  // src/analysis/taint-propagation.ts
14348
14469
  function buildSanitizersByLine(sanitizers) {
14349
14470
  const out2 = /* @__PURE__ */ new Map();
@@ -14580,7 +14701,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
14580
14701
  if (sanitizersAtTarget && sanitizersAtTarget.length > 0) {
14581
14702
  for (const san of sanitizersAtTarget) {
14582
14703
  if (isKnownSinkType) {
14583
- if (san.sanitizes.includes(sinkType)) {
14704
+ if (sanitizerCoversSink(san, sinkType)) {
14584
14705
  return { sanitized: true, sanitizer: san };
14585
14706
  }
14586
14707
  } else if (san.sanitizes.length > 0) {
@@ -14602,7 +14723,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
14602
14723
  if (!sansAtLine || sansAtLine.length === 0) continue;
14603
14724
  for (const san of sansAtLine) {
14604
14725
  if (isKnownSinkType) {
14605
- if (san.sanitizes.includes(sinkType)) {
14726
+ if (sanitizerCoversSink(san, sinkType)) {
14606
14727
  return { sanitized: true, sanitizer: san };
14607
14728
  }
14608
14729
  } else if (san.sanitizes.length > 0) {