circle-ir 3.168.0 → 3.174.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 (46) hide show
  1. package/configs/sinks/prompt_injection.yaml +50 -0
  2. package/dist/analysis/config-loader.d.ts.map +1 -1
  3. package/dist/analysis/config-loader.js +157 -0
  4. package/dist/analysis/config-loader.js.map +1 -1
  5. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  6. package/dist/analysis/passes/cross-file-pass.js +3 -2
  7. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  8. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  9. package/dist/analysis/passes/interprocedural-pass.js +3 -2
  10. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  11. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  12. package/dist/analysis/passes/sink-filter-pass.js +2 -1
  13. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  14. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  15. package/dist/analysis/passes/taint-propagation-pass.js +29 -15
  16. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  17. package/dist/analysis/rules.d.ts.map +1 -1
  18. package/dist/analysis/rules.js +9 -0
  19. package/dist/analysis/rules.js.map +1 -1
  20. package/dist/analysis/sanitizer-index.d.ts +34 -0
  21. package/dist/analysis/sanitizer-index.d.ts.map +1 -0
  22. package/dist/analysis/sanitizer-index.js +48 -0
  23. package/dist/analysis/sanitizer-index.js.map +1 -0
  24. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  25. package/dist/analysis/taint-matcher.js +93 -45
  26. package/dist/analysis/taint-matcher.js.map +1 -1
  27. package/dist/analysis/taint-propagation.d.ts.map +1 -1
  28. package/dist/analysis/taint-propagation.js +5 -2
  29. package/dist/analysis/taint-propagation.js.map +1 -1
  30. package/dist/analyzer.d.ts.map +1 -1
  31. package/dist/analyzer.js +51 -2
  32. package/dist/analyzer.js.map +1 -1
  33. package/dist/browser/circle-ir.js +336 -95
  34. package/dist/core/circle-ir-core.cjs +272 -73
  35. package/dist/core/circle-ir-core.js +272 -73
  36. package/dist/core/extractors/cfg.d.ts +11 -1
  37. package/dist/core/extractors/cfg.d.ts.map +1 -1
  38. package/dist/core/extractors/cfg.js +18 -9
  39. package/dist/core/extractors/cfg.js.map +1 -1
  40. package/dist/core/extractors/imports.d.ts +9 -1
  41. package/dist/core/extractors/imports.d.ts.map +1 -1
  42. package/dist/core/extractors/imports.js +29 -22
  43. package/dist/core/extractors/imports.js.map +1 -1
  44. package/dist/types/index.d.ts +1 -1
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/package.json +1 -1
@@ -7497,33 +7497,33 @@ function detectLanguage2(tree) {
7497
7497
  if (pythonScore > jsScore && pythonScore > javaScore) return "python";
7498
7498
  return jsScore > javaScore ? "javascript" : "java";
7499
7499
  }
7500
- function extractImports(tree, language) {
7500
+ function extractImports(tree, language, cache) {
7501
7501
  const effectiveLanguage = language ?? detectLanguage2(tree);
7502
7502
  const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
7503
7503
  const isPython = effectiveLanguage === "python";
7504
7504
  const isRust = effectiveLanguage === "rust";
7505
7505
  if (effectiveLanguage === "go") {
7506
- return extractGoImports(tree);
7506
+ return extractGoImports(tree, cache);
7507
7507
  }
7508
7508
  if (isRust) {
7509
- return extractRustImports(tree);
7509
+ return extractRustImports(tree, cache);
7510
7510
  }
7511
7511
  if (isPython) {
7512
- return extractPythonImports(tree);
7512
+ return extractPythonImports(tree, cache);
7513
7513
  }
7514
7514
  if (isJavaScript) {
7515
- return extractJavaScriptImports(tree);
7515
+ return extractJavaScriptImports(tree, cache);
7516
7516
  }
7517
- return extractJavaImports(tree);
7517
+ return extractJavaImports(tree, cache);
7518
7518
  }
7519
- function extractJavaScriptImports(tree) {
7519
+ function extractJavaScriptImports(tree, cache) {
7520
7520
  const imports = [];
7521
- const importStatements = findNodes(tree.rootNode, "import_statement");
7521
+ const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
7522
7522
  for (const importStmt of importStatements) {
7523
7523
  const importInfos = extractJSImportInfo(importStmt);
7524
7524
  imports.push(...importInfos);
7525
7525
  }
7526
- const exportStatements = findNodes(tree.rootNode, "export_statement");
7526
+ const exportStatements = getNodesFromCache(tree.rootNode, "export_statement", cache);
7527
7527
  for (const exportStmt of exportStatements) {
7528
7528
  const sourceNode = exportStmt.childForFieldName("source");
7529
7529
  if (!sourceNode) continue;
@@ -7537,13 +7537,13 @@ function extractJavaScriptImports(tree) {
7537
7537
  line_number: exportStmt.startPosition.row + 1
7538
7538
  });
7539
7539
  }
7540
- const requireCalls = findRequireCalls(tree);
7540
+ const requireCalls = findRequireCalls(tree, cache);
7541
7541
  imports.push(...requireCalls);
7542
7542
  return imports;
7543
7543
  }
7544
- function extractJavaImports(tree) {
7544
+ function extractJavaImports(tree, cache) {
7545
7545
  const imports = [];
7546
- const importDecls = findNodes(tree.rootNode, "import_declaration");
7546
+ const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
7547
7547
  for (const importDecl of importDecls) {
7548
7548
  const importInfo = extractJavaImportInfo(importDecl);
7549
7549
  if (importInfo) {
@@ -7691,9 +7691,9 @@ function extractJSImportInfo(node) {
7691
7691
  }
7692
7692
  return imports;
7693
7693
  }
7694
- function findRequireCalls(tree) {
7694
+ function findRequireCalls(tree, cache) {
7695
7695
  const imports = [];
7696
- const callExpressions = findNodes(tree.rootNode, "call_expression");
7696
+ const callExpressions = getNodesFromCache(tree.rootNode, "call_expression", cache);
7697
7697
  for (const call of callExpressions) {
7698
7698
  const funcNode = call.childForFieldName("function");
7699
7699
  if (!funcNode || getNodeText(funcNode) !== "require") continue;
@@ -7807,14 +7807,14 @@ function parseImportPath(fullPath, isWildcard) {
7807
7807
  fromPackage: fullPath.substring(0, lastDot)
7808
7808
  };
7809
7809
  }
7810
- function extractPythonImports(tree) {
7810
+ function extractPythonImports(tree, cache) {
7811
7811
  const imports = [];
7812
- const importStatements = findNodes(tree.rootNode, "import_statement");
7812
+ const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
7813
7813
  for (const stmt of importStatements) {
7814
7814
  const importInfos = extractPythonImportStatement(stmt);
7815
7815
  imports.push(...importInfos);
7816
7816
  }
7817
- const importFromStatements = findNodes(tree.rootNode, "import_from_statement");
7817
+ const importFromStatements = getNodesFromCache(tree.rootNode, "import_from_statement", cache);
7818
7818
  for (const stmt of importFromStatements) {
7819
7819
  const importInfos = extractPythonFromImportStatement(stmt);
7820
7820
  imports.push(...importInfos);
@@ -7908,9 +7908,9 @@ function extractPythonFromImportStatement(node) {
7908
7908
  }
7909
7909
  return imports;
7910
7910
  }
7911
- function extractRustImports(tree) {
7911
+ function extractRustImports(tree, cache) {
7912
7912
  const imports = [];
7913
- const useDecls = findNodes(tree.rootNode, "use_declaration");
7913
+ const useDecls = getNodesFromCache(tree.rootNode, "use_declaration", cache);
7914
7914
  for (const useDecl of useDecls) {
7915
7915
  const useImports = extractRustUseDecl(useDecl);
7916
7916
  imports.push(...useImports);
@@ -8052,9 +8052,9 @@ function extractRustScopedUseList(node, lineNumber) {
8052
8052
  }
8053
8053
  return imports;
8054
8054
  }
8055
- function extractGoImports(tree) {
8055
+ function extractGoImports(tree, cache) {
8056
8056
  const imports = [];
8057
- const importDecls = findNodes(tree.rootNode, "import_declaration");
8057
+ const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
8058
8058
  for (const decl of importDecls) {
8059
8059
  const singleSpec = findGoChildByType(decl, "import_spec");
8060
8060
  if (singleSpec) {
@@ -8189,7 +8189,7 @@ function detectLanguage3(tree) {
8189
8189
  }
8190
8190
  return jsScore > javaScore ? "javascript" : "java";
8191
8191
  }
8192
- function buildCFG(tree, language) {
8192
+ function buildCFG(tree, language, cache) {
8193
8193
  const effectiveLanguage = language ?? detectLanguage3(tree);
8194
8194
  const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
8195
8195
  const allBlocks = [];
@@ -8203,11 +8203,11 @@ function buildCFG(tree, language) {
8203
8203
  }
8204
8204
  if (isJavaScript) {
8205
8205
  const functions = [
8206
- ...findNodes(tree.rootNode, "function_declaration"),
8207
- ...findNodes(tree.rootNode, "arrow_function"),
8208
- ...findNodes(tree.rootNode, "method_definition"),
8209
- ...findNodes(tree.rootNode, "function"),
8210
- ...findNodes(tree.rootNode, "function_expression")
8206
+ ...getNodesFromCache(tree.rootNode, "function_declaration", cache),
8207
+ ...getNodesFromCache(tree.rootNode, "arrow_function", cache),
8208
+ ...getNodesFromCache(tree.rootNode, "method_definition", cache),
8209
+ ...getNodesFromCache(tree.rootNode, "function", cache),
8210
+ ...getNodesFromCache(tree.rootNode, "function_expression", cache)
8211
8211
  ];
8212
8212
  for (const func2 of functions) {
8213
8213
  const body2 = func2.childForFieldName("body");
@@ -8229,8 +8229,8 @@ function buildCFG(tree, language) {
8229
8229
  }
8230
8230
  } else {
8231
8231
  const methods = [
8232
- ...findNodes(tree.rootNode, "method_declaration"),
8233
- ...findNodes(tree.rootNode, "constructor_declaration")
8232
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache),
8233
+ ...getNodesFromCache(tree.rootNode, "constructor_declaration", cache)
8234
8234
  ];
8235
8235
  for (const method of methods) {
8236
8236
  const body2 = method.childForFieldName("body");
@@ -11134,7 +11134,86 @@ var DEFAULT_SOURCES = [
11134
11134
  { method: "recv", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
11135
11135
  { method: "read", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
11136
11136
  { method: "read_to_end", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
11137
- { method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true }
11137
+ { method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
11138
+ // =========================================================================
11139
+ // Modern taint sources (cognium-dev #242 — 3.170.0)
11140
+ // GraphQL resolver args, gRPC request metadata, cache reads (second-order
11141
+ // taint), and JWT claims (unverified decode). These attacker-influenced
11142
+ // surfaces were previously silent — a resolver that concatenated an
11143
+ // `Args`-annotated field into a SQL string produced zero flows.
11144
+ // =========================================================================
11145
+ // --- GraphQL resolver argument sources (JS / TS — Apollo, TypeGraphQL, NestJS) ---
11146
+ // TypeGraphQL / NestJS annotate query/mutation/subscription methods; every
11147
+ // parameter on the annotated method carries attacker input from the GraphQL
11148
+ // POST body's `variables` field.
11149
+ { method_annotation: "Query", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
11150
+ { method_annotation: "Mutation", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
11151
+ { method_annotation: "Subscription", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
11152
+ { method_annotation: "FieldResolver", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
11153
+ { method_annotation: "ResolveField", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
11154
+ // Parameter-level: `@Arg('id') id: string`, `@Args('input') input: FooDto`.
11155
+ { annotation: "Arg", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
11156
+ { annotation: "Args", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
11157
+ // --- GraphQL resolver argument sources (Python — Strawberry, Graphene, Ariadne) ---
11158
+ { method_annotation: "strawberry.field", type: "http_body", severity: "high", languages: ["python"] },
11159
+ { method_annotation: "strawberry.mutation", type: "http_body", severity: "high", languages: ["python"] },
11160
+ { method_annotation: "strawberry.subscription", type: "http_body", severity: "high", languages: ["python"] },
11161
+ // --- GraphQL resolver argument sources (Java — Netflix DGS, SPQR, graphql-java-annotations) ---
11162
+ { method_annotation: "DgsQuery", type: "http_body", severity: "high", languages: ["java"] },
11163
+ { method_annotation: "DgsMutation", type: "http_body", severity: "high", languages: ["java"] },
11164
+ { method_annotation: "DgsSubscription", type: "http_body", severity: "high", languages: ["java"] },
11165
+ { method_annotation: "DgsData", type: "http_body", severity: "high", languages: ["java"] },
11166
+ { method_annotation: "GraphQLQuery", type: "http_body", severity: "high", languages: ["java"] },
11167
+ { method_annotation: "GraphQLMutation", type: "http_body", severity: "high", languages: ["java"] },
11168
+ { annotation: "InputArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
11169
+ { annotation: "GraphQLArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
11170
+ // --- gRPC request metadata (Python — grpcio) ---
11171
+ // `context.invocation_metadata()` returns the caller-supplied metadata tuple.
11172
+ // No `class` filter — receiver names vary (`context`, `ctx`, `servicer_context`).
11173
+ { method: "invocation_metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
11174
+ // --- gRPC request metadata (JS / TS — @grpc/grpc-js) ---
11175
+ // `call.metadata.get(key)` / `call.metadata.getMap()` inside a service handler.
11176
+ { method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
11177
+ { method: "getMap", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
11178
+ // --- gRPC request metadata (Go — google.golang.org/grpc/metadata) ---
11179
+ // `md, _ := metadata.FromIncomingContext(ctx)` pulls the caller's headers.
11180
+ { method: "FromIncomingContext", class: "metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] },
11181
+ // --- gRPC request metadata (Java — io.grpc.Metadata) ---
11182
+ // Server-side interceptor receives `Metadata headers`; `headers.get(KEY)`
11183
+ // returns caller-supplied header values.
11184
+ { method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
11185
+ // --- Cache reads (second-order taint — Redis / Memcached / Django cache) ---
11186
+ // The cache round-trip is a canonical second-order sink: whatever was
11187
+ // written previously (potentially attacker-controlled) resurfaces on read.
11188
+ // Severity 'medium' — cache contents are usually filtered by the writer but
11189
+ // the read side often forgets that guarantee. Class-scoped to `Redis`,
11190
+ // `Jedis`, `cache` to avoid colliding with generic `Map.get()`.
11191
+ { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
11192
+ { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
11193
+ { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
11194
+ { method: "lrange", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
11195
+ { method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
11196
+ { method: "get_many", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
11197
+ { method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
11198
+ { method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
11199
+ { method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
11200
+ { method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
11201
+ { method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
11202
+ { method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
11203
+ // --- JWT claims (unverified decode — PyJWT / jose / jsonwebtoken / auth0 java-jwt / golang-jwt) ---
11204
+ // A JWT's payload is *always* attacker-authored. Even after verification
11205
+ // the *contents* of the claims (username, role, custom fields) are not
11206
+ // trusted for downstream flows into SQL, HTML, shell, etc. The `decode`
11207
+ // variants here return the parsed claims dictionary/object.
11208
+ { method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
11209
+ { method: "get_unverified_claims", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
11210
+ { method: "get_unverified_header", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
11211
+ { method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
11212
+ { method: "decode", class: "jsonwebtoken", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
11213
+ { method: "decodeJwt", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
11214
+ { method: "decodeProtectedHeader", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
11215
+ { method: "decode", class: "JWT", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
11216
+ { method: "ParseUnverified", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] }
11138
11217
  ];
11139
11218
  var DEFAULT_SINKS = [
11140
11219
  // SQL Injection (CWE-89)
@@ -12703,7 +12782,85 @@ var DEFAULT_SINKS = [
12703
12782
  // they iterate after net/http entries and don't hijack `http.Get`.
12704
12783
  { method: "Do", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] },
12705
12784
  // *fasthttp.Client.Do(req)
12706
- { method: "DoTimeout", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] }
12785
+ { method: "DoTimeout", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] },
12786
+ // =========================================================================
12787
+ // cognium-dev #248 — prompt-injection sinks (CWE-1427)
12788
+ //
12789
+ // Tainted data reaching a generative-model prompt-construction API is
12790
+ // classified as `prompt_injection`. v1 uses broad positional matching
12791
+ // (arg_positions [0..3]) because these APIs are kwarg-heavy: Python
12792
+ // `openai.chat.completions.create(model=..., messages=...)` may pass the
12793
+ // messages arg at position 0 or 1 depending on caller order, and JS/TS
12794
+ // object-literal `{ messages: [...], model: '...' }` is a single
12795
+ // positional arg whose taint is inherited from any tainted property.
12796
+ // Argname-precise matching (messages=/prompt=/content=) and
12797
+ // sanitizer credit for prompt-template libraries (PromptTemplate,
12798
+ // ChatPromptTemplate) are follow-ups.
12799
+ //
12800
+ // Class-qualified entries prevent bare `create()` / `generate()` calls
12801
+ // with no receiver from matching (per taint-matcher.ts:1696 guard).
12802
+ // =========================================================================
12803
+ // --- Python: openai (v1 SDK) --------------------------------------------
12804
+ { method: "create", class: "Completions", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12805
+ { method: "create", class: "Responses", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12806
+ // openai <1.0 legacy top-level API — `openai.ChatCompletion.create(...)` / `openai.Completion.create(...)`
12807
+ { method: "create", class: "ChatCompletion", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12808
+ { method: "create", class: "Completion", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12809
+ // --- Python: anthropic ---------------------------------------------------
12810
+ { method: "create", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12811
+ { method: "stream", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12812
+ // --- Python: litellm (bare functions from the litellm module) -----------
12813
+ { method: "completion", class: "litellm", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12814
+ { method: "acompletion", class: "litellm", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12815
+ // --- Python: langchain chat/llm models ----------------------------------
12816
+ // Common concrete classes; matching the class name catches typical usage
12817
+ // `ChatOpenAI().invoke(prompt)` where the receiver-type resolver identifies
12818
+ // the class from the constructor.
12819
+ { method: "invoke", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12820
+ { method: "predict", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12821
+ { method: "stream", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12822
+ { method: "generate", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12823
+ { method: "invoke", class: "ChatAnthropic", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12824
+ { method: "invoke", class: "ChatGoogleGenerativeAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12825
+ { method: "run", class: "LLMChain", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12826
+ { method: "invoke", class: "LLMChain", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12827
+ // --- JS/TS: openai node SDK ---------------------------------------------
12828
+ { method: "create", class: "Completions", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12829
+ { method: "create", class: "Responses", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12830
+ // --- JS/TS: @anthropic-ai/sdk -------------------------------------------
12831
+ { method: "create", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12832
+ { method: "stream", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12833
+ // --- JS/TS: Vercel AI SDK (bare functions from the `ai` package) --------
12834
+ // generateText/streamText/generateObject take a single options object;
12835
+ // taint reaches the sink when a tainted variable flows into any property.
12836
+ { method: "generateText", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
12837
+ { method: "streamText", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
12838
+ { method: "generateObject", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
12839
+ { method: "streamObject", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
12840
+ // --- JS/TS: langchain.js ------------------------------------------------
12841
+ { method: "invoke", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12842
+ { method: "invoke", class: "ChatAnthropic", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12843
+ { method: "stream", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12844
+ // --- Java: LangChain4j --------------------------------------------------
12845
+ { method: "generate", class: "ChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12846
+ { method: "chat", class: "ChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12847
+ { method: "generate", class: "StreamingChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12848
+ { method: "chat", class: "StreamingChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12849
+ // --- Java: Spring AI ----------------------------------------------------
12850
+ { method: "prompt", class: "ChatClient", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12851
+ { method: "call", class: "ChatClient", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12852
+ { method: "call", class: "ChatModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12853
+ // --- Java: OpenAI Java SDK (theokanning) --------------------------------
12854
+ { method: "createChatCompletion", class: "OpenAiService", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12855
+ { method: "createCompletion", class: "OpenAiService", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12856
+ // --- Go: go-openai (sashabaranov/go-openai) -----------------------------
12857
+ { method: "CreateChatCompletion", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12858
+ { method: "CreateCompletion", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12859
+ { method: "CreateChatCompletionStream", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12860
+ // --- Go: langchaingo ----------------------------------------------------
12861
+ { method: "Call", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12862
+ { method: "Generate", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12863
+ { method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] }
12707
12864
  ];
12708
12865
  var DEFAULT_SANITIZERS = [
12709
12866
  // SQL Injection - proper parameter binding sanitizes input
@@ -13274,6 +13431,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
13274
13431
  for (const param of method.parameters) {
13275
13432
  for (const pattern of patterns) {
13276
13433
  if (pattern.annotation && pattern.param_tainted) {
13434
+ if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
13435
+ continue;
13436
+ }
13277
13437
  if (matchesAnnotation(param.annotations, pattern.annotation)) {
13278
13438
  const paramLine = param.line ?? method.start_line;
13279
13439
  sources.push({
@@ -13294,6 +13454,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
13294
13454
  for (const method of type.methods) {
13295
13455
  for (const pattern of patterns) {
13296
13456
  if (!pattern.method_annotation) continue;
13457
+ if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
13458
+ continue;
13459
+ }
13297
13460
  if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
13298
13461
  for (const param of method.parameters) {
13299
13462
  const paramLine = param.line ?? method.start_line;
@@ -13558,7 +13721,7 @@ function isParameterizedQueryCall(call, pattern) {
13558
13721
  const secondArg = call.arguments.find((a) => a.position === 1);
13559
13722
  if (secondArg?.expression) {
13560
13723
  const expr = secondArg.expression.trim();
13561
- if (expr.startsWith("[")) {
13724
+ if (expr.startsWith("[") && !/^\[\s*\]$/.test(expr)) {
13562
13725
  return true;
13563
13726
  }
13564
13727
  }
@@ -13999,52 +14162,74 @@ function matchesSourcePattern(call, pattern) {
13999
14162
  }
14000
14163
  return false;
14001
14164
  }
14165
+ var JS_SOURCE_PATTERN_CACHE = /* @__PURE__ */ new WeakMap();
14166
+ var JS_FALLBACK_COMPILED = (() => {
14167
+ const bases = [
14168
+ { base: "req\\.params", sourceType: "http_param" },
14169
+ { base: "req\\.query", sourceType: "http_param" },
14170
+ { base: "req\\.body", sourceType: "http_body" },
14171
+ { base: "req\\.headers", sourceType: "http_header" },
14172
+ { base: "req\\.cookies", sourceType: "http_cookie" },
14173
+ { base: "req\\.url", sourceType: "http_path" },
14174
+ { base: "req\\.path", sourceType: "http_path" },
14175
+ { base: "req\\.originalUrl", sourceType: "http_path" },
14176
+ { base: "req\\.file", sourceType: "file_input" },
14177
+ { base: "req\\.files", sourceType: "file_input" },
14178
+ { base: "request\\.params", sourceType: "http_param" },
14179
+ { base: "request\\.query", sourceType: "http_param" },
14180
+ { base: "request\\.body", sourceType: "http_body" },
14181
+ { base: "request\\.headers", sourceType: "http_header" },
14182
+ { base: "process\\.env", sourceType: "env_input" },
14183
+ { base: "process\\.argv", sourceType: "io_input" },
14184
+ { base: "ctx\\.query", sourceType: "http_param" },
14185
+ { base: "ctx\\.params", sourceType: "http_param" },
14186
+ { base: "ctx\\.request", sourceType: "http_body" }
14187
+ ];
14188
+ const exact = [];
14189
+ const contained = [];
14190
+ for (const { base, sourceType } of bases) {
14191
+ exact.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
14192
+ contained.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
14193
+ }
14194
+ return { exact, contained };
14195
+ })();
14196
+ function compileJsSourcePatterns(sourcePatterns) {
14197
+ const cached = JS_SOURCE_PATTERN_CACHE.get(sourcePatterns);
14198
+ if (cached) return cached;
14199
+ const exact = [];
14200
+ const contained = [];
14201
+ for (const sp of sourcePatterns) {
14202
+ if (sp.property && sp.object && sp.property_tainted) {
14203
+ exact.push({
14204
+ pattern: new RegExp(`^${sp.object}\\.${sp.property}\\b`),
14205
+ sourceType: sp.type
14206
+ });
14207
+ contained.push({
14208
+ pattern: new RegExp(`\\b${sp.object}\\.${sp.property}\\b`),
14209
+ sourceType: sp.type
14210
+ });
14211
+ }
14212
+ }
14213
+ const compiled = { exact, contained };
14214
+ JS_SOURCE_PATTERN_CACHE.set(sourcePatterns, compiled);
14215
+ return compiled;
14216
+ }
14002
14217
  function isJavaScriptTaintedArgument(argExpression, sourcePatterns) {
14003
- const exactPatterns = [];
14004
- const containedPatterns = [];
14005
- if (sourcePatterns) {
14006
- for (const sp of sourcePatterns) {
14007
- if (sp.property && sp.object && sp.property_tainted) {
14008
- const exactRegex = new RegExp(`^${sp.object}\\.${sp.property}\\b`);
14009
- exactPatterns.push({ pattern: exactRegex, sourceType: sp.type });
14010
- const containedRegex = new RegExp(`\\b${sp.object}\\.${sp.property}\\b`);
14011
- containedPatterns.push({ pattern: containedRegex, sourceType: sp.type });
14012
- }
14013
- }
14014
- }
14015
- if (exactPatterns.length === 0) {
14016
- const basePatterns = [
14017
- { base: "req\\.params", sourceType: "http_param" },
14018
- { base: "req\\.query", sourceType: "http_param" },
14019
- { base: "req\\.body", sourceType: "http_body" },
14020
- { base: "req\\.headers", sourceType: "http_header" },
14021
- { base: "req\\.cookies", sourceType: "http_cookie" },
14022
- { base: "req\\.url", sourceType: "http_path" },
14023
- { base: "req\\.path", sourceType: "http_path" },
14024
- { base: "req\\.originalUrl", sourceType: "http_path" },
14025
- { base: "req\\.file", sourceType: "file_input" },
14026
- { base: "req\\.files", sourceType: "file_input" },
14027
- { base: "request\\.params", sourceType: "http_param" },
14028
- { base: "request\\.query", sourceType: "http_param" },
14029
- { base: "request\\.body", sourceType: "http_body" },
14030
- { base: "request\\.headers", sourceType: "http_header" },
14031
- { base: "process\\.env", sourceType: "env_input" },
14032
- { base: "process\\.argv", sourceType: "io_input" },
14033
- { base: "ctx\\.query", sourceType: "http_param" },
14034
- { base: "ctx\\.params", sourceType: "http_param" },
14035
- { base: "ctx\\.request", sourceType: "http_body" }
14036
- ];
14037
- for (const { base, sourceType } of basePatterns) {
14038
- exactPatterns.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
14039
- containedPatterns.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
14218
+ let compiled;
14219
+ if (sourcePatterns && sourcePatterns.length > 0) {
14220
+ compiled = compileJsSourcePatterns(sourcePatterns);
14221
+ if (compiled.exact.length === 0) {
14222
+ compiled = JS_FALLBACK_COMPILED;
14040
14223
  }
14224
+ } else {
14225
+ compiled = JS_FALLBACK_COMPILED;
14041
14226
  }
14042
- for (const { pattern, sourceType } of exactPatterns) {
14227
+ for (const { pattern, sourceType } of compiled.exact) {
14043
14228
  if (pattern.test(argExpression)) {
14044
14229
  return { isTainted: true, sourceType };
14045
14230
  }
14046
14231
  }
14047
- for (const { pattern, sourceType } of containedPatterns) {
14232
+ for (const { pattern, sourceType } of compiled.contained) {
14048
14233
  if (pattern.test(argExpression)) {
14049
14234
  return { isTainted: true, sourceType };
14050
14235
  }
@@ -16351,6 +16536,20 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
16351
16536
  return { visited, lines, hopCapReached };
16352
16537
  }
16353
16538
 
16539
+ // src/analysis/sanitizer-index.ts
16540
+ var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
16541
+ function getSanitizesSet(san) {
16542
+ let s = SANITIZER_SET_CACHE.get(san);
16543
+ if (!s) {
16544
+ s = new Set(san.sanitizes);
16545
+ SANITIZER_SET_CACHE.set(san, s);
16546
+ }
16547
+ return s;
16548
+ }
16549
+ function sanitizerCoversSink(san, sinkType) {
16550
+ return getSanitizesSet(san).has(sinkType);
16551
+ }
16552
+
16354
16553
  // src/analysis/taint-propagation.ts
16355
16554
  function buildSanitizersByLine(sanitizers) {
16356
16555
  const out2 = /* @__PURE__ */ new Map();
@@ -16587,7 +16786,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
16587
16786
  if (sanitizersAtTarget && sanitizersAtTarget.length > 0) {
16588
16787
  for (const san of sanitizersAtTarget) {
16589
16788
  if (isKnownSinkType) {
16590
- if (san.sanitizes.includes(sinkType)) {
16789
+ if (sanitizerCoversSink(san, sinkType)) {
16591
16790
  return { sanitized: true, sanitizer: san };
16592
16791
  }
16593
16792
  } else if (san.sanitizes.length > 0) {
@@ -16609,7 +16808,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
16609
16808
  if (!sansAtLine || sansAtLine.length === 0) continue;
16610
16809
  for (const san of sansAtLine) {
16611
16810
  if (isKnownSinkType) {
16612
- if (san.sanitizes.includes(sinkType)) {
16811
+ if (sanitizerCoversSink(san, sinkType)) {
16613
16812
  return { sanitized: true, sanitizer: san };
16614
16813
  }
16615
16814
  } else if (san.sanitizes.length > 0) {
@@ -32194,7 +32393,7 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
32194
32393
  const lineSanitizers = sanitizersByLine.get(sink.line);
32195
32394
  if (!lineSanitizers || lineSanitizers.length === 0) return true;
32196
32395
  for (const san of lineSanitizers) {
32197
- if (san.sanitizes.includes(sink.type)) {
32396
+ if (sanitizerCoversSink(san, sink.type)) {
32198
32397
  const lineCalls = callsByLine.get(sink.line) ?? [];
32199
32398
  for (const call of lineCalls) {
32200
32399
  for (const arg of call.arguments) {
@@ -32687,15 +32886,23 @@ var TaintPropagationPass = class {
32687
32886
  confidence: flow.confidence,
32688
32887
  sanitized: flow.sanitized
32689
32888
  }));
32889
+ const flowKey = (x) => `${x.source_line}|${x.sink_line}|${x.sink_type}`;
32890
+ const flowKeys = /* @__PURE__ */ new Set();
32891
+ for (const x of flows) flowKeys.add(flowKey(x));
32892
+ const pushIfNew = (f) => {
32893
+ const k = flowKey(f);
32894
+ if (flowKeys.has(k)) return false;
32895
+ flowKeys.add(k);
32896
+ flows.push(f);
32897
+ return true;
32898
+ };
32690
32899
  const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines, types) ?? [];
32691
32900
  for (const f of arrayFlows) {
32692
- if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type)) {
32693
- flows.push(f);
32694
- }
32901
+ pushIfNew(f);
32695
32902
  }
32696
32903
  const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
32697
32904
  for (const f of collectionFlows) {
32698
- if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type)) continue;
32905
+ if (flowKeys.has(flowKey(f))) continue;
32699
32906
  const flowForCheck = {
32700
32907
  source: { line: f.source_line },
32701
32908
  sink: { line: f.sink_line },
@@ -32710,19 +32917,15 @@ var TaintPropagationPass = class {
32710
32917
  }
32711
32918
  }
32712
32919
  if (isFP) continue;
32713
- flows.push(f);
32920
+ pushIfNew(f);
32714
32921
  }
32715
32922
  const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
32716
32923
  for (const f of paramFlows) {
32717
- if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type)) {
32718
- flows.push(f);
32719
- }
32924
+ pushIfNew(f);
32720
32925
  }
32721
32926
  const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
32722
32927
  for (const f of exprScanFlows) {
32723
- if (flows.some(
32724
- (x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type
32725
- )) continue;
32928
+ if (flowKeys.has(flowKey(f))) continue;
32726
32929
  const flowForCheck = {
32727
32930
  source: { line: f.source_line },
32728
32931
  sink: { line: f.sink_line },
@@ -32737,7 +32940,7 @@ var TaintPropagationPass = class {
32737
32940
  }
32738
32941
  }
32739
32942
  if (isFP) continue;
32740
- flows.push(f);
32943
+ pushIfNew(f);
32741
32944
  }
32742
32945
  const sanitizedNames = constProp.sanitizedVars;
32743
32946
  let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
@@ -32765,7 +32968,7 @@ var TaintPropagationPass = class {
32765
32968
  const sansAtLine = sanitizersByLine.get(line);
32766
32969
  if (!sansAtLine) continue;
32767
32970
  for (const san of sansAtLine) {
32768
- if (san.sanitizes.includes(f.sink_type)) {
32971
+ if (sanitizerCoversSink(san, f.sink_type)) {
32769
32972
  return false;
32770
32973
  }
32771
32974
  }
@@ -32775,7 +32978,7 @@ var TaintPropagationPass = class {
32775
32978
  const sansAtSink = sanitizersByLine.get(f.sink_line);
32776
32979
  if (sansAtSink && sansAtSink.length > 0) {
32777
32980
  for (const san of sansAtSink) {
32778
- if (san.sanitizes.includes(f.sink_type)) {
32981
+ if (sanitizerCoversSink(san, f.sink_type)) {
32779
32982
  return false;
32780
32983
  }
32781
32984
  }
@@ -32799,7 +33002,7 @@ var TaintPropagationPass = class {
32799
33002
  const sansAtLine = sanitizersByLine.get(line);
32800
33003
  if (!sansAtLine || sansAtLine.length === 0) continue;
32801
33004
  for (const san of sansAtLine) {
32802
- if (san.sanitizes.includes(f.sink_type)) {
33005
+ if (sanitizerCoversSink(san, f.sink_type)) {
32803
33006
  return false;
32804
33007
  }
32805
33008
  }
@@ -33764,7 +33967,7 @@ var InterproceduralPass = class {
33764
33967
  const sansAtLine = sanitizersByLine.get(line);
33765
33968
  if (!sansAtLine) continue;
33766
33969
  for (const san of sansAtLine) {
33767
- if (san.sanitizes.includes(f.sink_type)) {
33970
+ if (sanitizerCoversSink(san, f.sink_type)) {
33768
33971
  sanitizedSinkKeys.add(`${f.sink_line}:${f.sink_type}`);
33769
33972
  return false;
33770
33973
  }
@@ -33775,7 +33978,7 @@ var InterproceduralPass = class {
33775
33978
  const sansAtSink = sanitizersByLine.get(f.sink_line);
33776
33979
  if (!sansAtSink || sansAtSink.length === 0) return true;
33777
33980
  for (const san of sansAtSink) {
33778
- if (san.sanitizes.includes(f.sink_type)) {
33981
+ if (sanitizerCoversSink(san, f.sink_type)) {
33779
33982
  return false;
33780
33983
  }
33781
33984
  }
@@ -42186,7 +42389,13 @@ function getNodeTypesForLanguage(language) {
42186
42389
  "jsx_self_closing_element",
42187
42390
  "jsx_opening_element",
42188
42391
  "jsx_attribute",
42189
- "jsx_expression"
42392
+ "jsx_expression",
42393
+ // buildCFG containers (3.172.0 T2-A) — the two below are legacy
42394
+ // tree-sitter-javascript node types for anonymous / expression
42395
+ // functions; declaring them here lets `collectAllNodes` populate
42396
+ // the cache in the same pass so `buildCFG` skips its per-type walk.
42397
+ "function",
42398
+ "function_expression"
42190
42399
  ]);
42191
42400
  case "bash":
42192
42401
  return /* @__PURE__ */ new Set([
@@ -42240,7 +42449,9 @@ function getNodeTypesForLanguage(language) {
42240
42449
  "field_declaration",
42241
42450
  "import_declaration",
42242
42451
  "interface_declaration",
42243
- "enum_declaration"
42452
+ "enum_declaration",
42453
+ "package_declaration",
42454
+ "local_variable_declaration"
42244
42455
  ]);
42245
42456
  }
42246
42457
  }
@@ -42264,10 +42475,20 @@ async function analyze(code, filePath, language, options = {}) {
42264
42475
  }
42265
42476
  }
42266
42477
  logger.debug("Analyzing file", { filePath, language, parseGrammar, codeLength: code.length });
42478
+ const phaseTimingEnabled = globalThis.__circleIrPassTiming === true;
42479
+ const phaseStart = () => phaseTimingEnabled ? Date.now() : 0;
42480
+ const phaseEnd = (label, t0) => {
42481
+ if (!phaseTimingEnabled) return;
42482
+ console.error(`[phase-timing] ${label} ${Date.now() - t0}ms file=${filePath}`);
42483
+ };
42484
+ const tParse = phaseStart();
42267
42485
  const tree = await parse(code, parseGrammar);
42486
+ phaseEnd("parse", tParse);
42268
42487
  try {
42269
42488
  logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
42489
+ const tParseStatus = phaseStart();
42270
42490
  const parseStatus = extractParseStatus(tree);
42491
+ phaseEnd("extractParseStatus", tParseStatus);
42271
42492
  if (parseStatus.has_errors) {
42272
42493
  logger.warn("Partial parse \u2014 IR may be incomplete", {
42273
42494
  filePath,
@@ -42276,18 +42497,37 @@ async function analyze(code, filePath, language, options = {}) {
42276
42497
  firstErrorLine: parseStatus.error_locations[0]?.line
42277
42498
  });
42278
42499
  }
42500
+ const tCollect = phaseStart();
42279
42501
  const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
42502
+ phaseEnd("collectAllNodes", tCollect);
42503
+ const tMeta = phaseStart();
42280
42504
  const meta = extractMeta(code, tree, filePath, language);
42281
42505
  if (options.projectProfile !== void 0) {
42282
42506
  meta.projectProfile = makeProfileResolver(options.projectProfile)(filePath);
42283
42507
  }
42508
+ phaseEnd("extractMeta", tMeta);
42509
+ const tTypes = phaseStart();
42284
42510
  const types = extractTypes(tree, nodeCache, language);
42511
+ phaseEnd("extractTypes", tTypes);
42512
+ const tCalls = phaseStart();
42285
42513
  const calls = extractCalls(tree, nodeCache, language);
42286
- const imports = extractImports(tree, language);
42514
+ phaseEnd("extractCalls", tCalls);
42515
+ const tImports = phaseStart();
42516
+ const imports = extractImports(tree, language, nodeCache);
42517
+ phaseEnd("extractImports", tImports);
42518
+ const tExports = phaseStart();
42287
42519
  const exports = extractExports(types);
42288
- const cfg = buildCFG(tree, language);
42520
+ phaseEnd("extractExports", tExports);
42521
+ const tCFG = phaseStart();
42522
+ const cfg = buildCFG(tree, language, nodeCache);
42523
+ phaseEnd("buildCFG", tCFG);
42524
+ const tDFG = phaseStart();
42289
42525
  const dfg = buildDFG(tree, nodeCache, language);
42526
+ phaseEnd("buildDFG", tDFG);
42527
+ const tRuntime = phaseStart();
42290
42528
  const runtimeRegistrations = extractRuntimeRegistrations(tree, nodeCache, language, imports);
42529
+ phaseEnd("extractRuntimeRegistrations", tRuntime);
42530
+ const tGraph = phaseStart();
42291
42531
  const graph = new CodeGraph({
42292
42532
  meta,
42293
42533
  types,
@@ -42300,6 +42540,7 @@ async function analyze(code, filePath, language, options = {}) {
42300
42540
  unresolved: [],
42301
42541
  enriched: {}
42302
42542
  });
42543
+ phaseEnd("buildCodeGraph", tGraph);
42303
42544
  const config = options.taintConfig ?? getDefaultConfig();
42304
42545
  const disabledPasses = new Set(options.disabledPasses ?? []);
42305
42546
  const passOpts = options.passOptions ?? {};