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
@@ -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)
@@ -12098,7 +12177,85 @@ var DEFAULT_SINKS = [
12098
12177
  // they iterate after net/http entries and don't hijack `http.Get`.
12099
12178
  { method: "Do", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] },
12100
12179
  // *fasthttp.Client.Do(req)
12101
- { method: "DoTimeout", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] }
12180
+ { method: "DoTimeout", class: "Client", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["go"] },
12181
+ // =========================================================================
12182
+ // cognium-dev #248 — prompt-injection sinks (CWE-1427)
12183
+ //
12184
+ // Tainted data reaching a generative-model prompt-construction API is
12185
+ // classified as `prompt_injection`. v1 uses broad positional matching
12186
+ // (arg_positions [0..3]) because these APIs are kwarg-heavy: Python
12187
+ // `openai.chat.completions.create(model=..., messages=...)` may pass the
12188
+ // messages arg at position 0 or 1 depending on caller order, and JS/TS
12189
+ // object-literal `{ messages: [...], model: '...' }` is a single
12190
+ // positional arg whose taint is inherited from any tainted property.
12191
+ // Argname-precise matching (messages=/prompt=/content=) and
12192
+ // sanitizer credit for prompt-template libraries (PromptTemplate,
12193
+ // ChatPromptTemplate) are follow-ups.
12194
+ //
12195
+ // Class-qualified entries prevent bare `create()` / `generate()` calls
12196
+ // with no receiver from matching (per taint-matcher.ts:1696 guard).
12197
+ // =========================================================================
12198
+ // --- Python: openai (v1 SDK) --------------------------------------------
12199
+ { method: "create", class: "Completions", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12200
+ { method: "create", class: "Responses", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12201
+ // openai <1.0 legacy top-level API — `openai.ChatCompletion.create(...)` / `openai.Completion.create(...)`
12202
+ { method: "create", class: "ChatCompletion", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12203
+ { method: "create", class: "Completion", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12204
+ // --- Python: anthropic ---------------------------------------------------
12205
+ { method: "create", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12206
+ { method: "stream", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12207
+ // --- Python: litellm (bare functions from the litellm module) -----------
12208
+ { method: "completion", class: "litellm", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12209
+ { method: "acompletion", class: "litellm", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12210
+ // --- Python: langchain chat/llm models ----------------------------------
12211
+ // Common concrete classes; matching the class name catches typical usage
12212
+ // `ChatOpenAI().invoke(prompt)` where the receiver-type resolver identifies
12213
+ // the class from the constructor.
12214
+ { method: "invoke", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12215
+ { method: "predict", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12216
+ { method: "stream", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12217
+ { method: "generate", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12218
+ { method: "invoke", class: "ChatAnthropic", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12219
+ { method: "invoke", class: "ChatGoogleGenerativeAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12220
+ { method: "run", class: "LLMChain", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12221
+ { method: "invoke", class: "LLMChain", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["python"] },
12222
+ // --- JS/TS: openai node SDK ---------------------------------------------
12223
+ { method: "create", class: "Completions", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12224
+ { method: "create", class: "Responses", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12225
+ // --- JS/TS: @anthropic-ai/sdk -------------------------------------------
12226
+ { method: "create", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12227
+ { method: "stream", class: "Messages", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12228
+ // --- JS/TS: Vercel AI SDK (bare functions from the `ai` package) --------
12229
+ // generateText/streamText/generateObject take a single options object;
12230
+ // taint reaches the sink when a tainted variable flows into any property.
12231
+ { method: "generateText", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
12232
+ { method: "streamText", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
12233
+ { method: "generateObject", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
12234
+ { method: "streamObject", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
12235
+ // --- JS/TS: langchain.js ------------------------------------------------
12236
+ { method: "invoke", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12237
+ { method: "invoke", class: "ChatAnthropic", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12238
+ { method: "stream", class: "ChatOpenAI", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
12239
+ // --- Java: LangChain4j --------------------------------------------------
12240
+ { method: "generate", class: "ChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12241
+ { method: "chat", class: "ChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12242
+ { method: "generate", class: "StreamingChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12243
+ { method: "chat", class: "StreamingChatLanguageModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12244
+ // --- Java: Spring AI ----------------------------------------------------
12245
+ { method: "prompt", class: "ChatClient", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12246
+ { method: "call", class: "ChatClient", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12247
+ { method: "call", class: "ChatModel", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12248
+ // --- Java: OpenAI Java SDK (theokanning) --------------------------------
12249
+ { method: "createChatCompletion", class: "OpenAiService", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12250
+ { method: "createCompletion", class: "OpenAiService", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["java"] },
12251
+ // --- Go: go-openai (sashabaranov/go-openai) -----------------------------
12252
+ { method: "CreateChatCompletion", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12253
+ { method: "CreateCompletion", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12254
+ { method: "CreateChatCompletionStream", class: "Client", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12255
+ // --- Go: langchaingo ----------------------------------------------------
12256
+ { method: "Call", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12257
+ { method: "Generate", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
12258
+ { method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] }
12102
12259
  ];
12103
12260
  var DEFAULT_SANITIZERS = [
12104
12261
  // SQL Injection - proper parameter binding sanitizes input
@@ -12582,6 +12739,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
12582
12739
  for (const param of method.parameters) {
12583
12740
  for (const pattern of patterns) {
12584
12741
  if (pattern.annotation && pattern.param_tainted) {
12742
+ if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12743
+ continue;
12744
+ }
12585
12745
  if (matchesAnnotation(param.annotations, pattern.annotation)) {
12586
12746
  const paramLine = param.line ?? method.start_line;
12587
12747
  sources.push({
@@ -12602,6 +12762,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
12602
12762
  for (const method of type.methods) {
12603
12763
  for (const pattern of patterns) {
12604
12764
  if (!pattern.method_annotation) continue;
12765
+ if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12766
+ continue;
12767
+ }
12605
12768
  if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
12606
12769
  for (const param of method.parameters) {
12607
12770
  const paramLine = param.line ?? method.start_line;
@@ -12866,7 +13029,7 @@ function isParameterizedQueryCall(call, pattern) {
12866
13029
  const secondArg = call.arguments.find((a) => a.position === 1);
12867
13030
  if (secondArg?.expression) {
12868
13031
  const expr = secondArg.expression.trim();
12869
- if (expr.startsWith("[")) {
13032
+ if (expr.startsWith("[") && !/^\[\s*\]$/.test(expr)) {
12870
13033
  return true;
12871
13034
  }
12872
13035
  }
@@ -13307,52 +13470,74 @@ function matchesSourcePattern(call, pattern) {
13307
13470
  }
13308
13471
  return false;
13309
13472
  }
13473
+ var JS_SOURCE_PATTERN_CACHE = /* @__PURE__ */ new WeakMap();
13474
+ var JS_FALLBACK_COMPILED = (() => {
13475
+ const bases = [
13476
+ { base: "req\\.params", sourceType: "http_param" },
13477
+ { base: "req\\.query", sourceType: "http_param" },
13478
+ { base: "req\\.body", sourceType: "http_body" },
13479
+ { base: "req\\.headers", sourceType: "http_header" },
13480
+ { base: "req\\.cookies", sourceType: "http_cookie" },
13481
+ { base: "req\\.url", sourceType: "http_path" },
13482
+ { base: "req\\.path", sourceType: "http_path" },
13483
+ { base: "req\\.originalUrl", sourceType: "http_path" },
13484
+ { base: "req\\.file", sourceType: "file_input" },
13485
+ { base: "req\\.files", sourceType: "file_input" },
13486
+ { base: "request\\.params", sourceType: "http_param" },
13487
+ { base: "request\\.query", sourceType: "http_param" },
13488
+ { base: "request\\.body", sourceType: "http_body" },
13489
+ { base: "request\\.headers", sourceType: "http_header" },
13490
+ { base: "process\\.env", sourceType: "env_input" },
13491
+ { base: "process\\.argv", sourceType: "io_input" },
13492
+ { base: "ctx\\.query", sourceType: "http_param" },
13493
+ { base: "ctx\\.params", sourceType: "http_param" },
13494
+ { base: "ctx\\.request", sourceType: "http_body" }
13495
+ ];
13496
+ const exact = [];
13497
+ const contained = [];
13498
+ for (const { base, sourceType } of bases) {
13499
+ exact.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
13500
+ contained.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
13501
+ }
13502
+ return { exact, contained };
13503
+ })();
13504
+ function compileJsSourcePatterns(sourcePatterns) {
13505
+ const cached = JS_SOURCE_PATTERN_CACHE.get(sourcePatterns);
13506
+ if (cached) return cached;
13507
+ const exact = [];
13508
+ const contained = [];
13509
+ for (const sp of sourcePatterns) {
13510
+ if (sp.property && sp.object && sp.property_tainted) {
13511
+ exact.push({
13512
+ pattern: new RegExp(`^${sp.object}\\.${sp.property}\\b`),
13513
+ sourceType: sp.type
13514
+ });
13515
+ contained.push({
13516
+ pattern: new RegExp(`\\b${sp.object}\\.${sp.property}\\b`),
13517
+ sourceType: sp.type
13518
+ });
13519
+ }
13520
+ }
13521
+ const compiled = { exact, contained };
13522
+ JS_SOURCE_PATTERN_CACHE.set(sourcePatterns, compiled);
13523
+ return compiled;
13524
+ }
13310
13525
  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 });
13526
+ let compiled;
13527
+ if (sourcePatterns && sourcePatterns.length > 0) {
13528
+ compiled = compileJsSourcePatterns(sourcePatterns);
13529
+ if (compiled.exact.length === 0) {
13530
+ compiled = JS_FALLBACK_COMPILED;
13348
13531
  }
13532
+ } else {
13533
+ compiled = JS_FALLBACK_COMPILED;
13349
13534
  }
13350
- for (const { pattern, sourceType } of exactPatterns) {
13535
+ for (const { pattern, sourceType } of compiled.exact) {
13351
13536
  if (pattern.test(argExpression)) {
13352
13537
  return { isTainted: true, sourceType };
13353
13538
  }
13354
13539
  }
13355
- for (const { pattern, sourceType } of containedPatterns) {
13540
+ for (const { pattern, sourceType } of compiled.contained) {
13356
13541
  if (pattern.test(argExpression)) {
13357
13542
  return { isTainted: true, sourceType };
13358
13543
  }
@@ -14344,6 +14529,20 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
14344
14529
  return { visited, lines, hopCapReached };
14345
14530
  }
14346
14531
 
14532
+ // src/analysis/sanitizer-index.ts
14533
+ var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
14534
+ function getSanitizesSet(san) {
14535
+ let s = SANITIZER_SET_CACHE.get(san);
14536
+ if (!s) {
14537
+ s = new Set(san.sanitizes);
14538
+ SANITIZER_SET_CACHE.set(san, s);
14539
+ }
14540
+ return s;
14541
+ }
14542
+ function sanitizerCoversSink(san, sinkType) {
14543
+ return getSanitizesSet(san).has(sinkType);
14544
+ }
14545
+
14347
14546
  // src/analysis/taint-propagation.ts
14348
14547
  function buildSanitizersByLine(sanitizers) {
14349
14548
  const out2 = /* @__PURE__ */ new Map();
@@ -14580,7 +14779,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
14580
14779
  if (sanitizersAtTarget && sanitizersAtTarget.length > 0) {
14581
14780
  for (const san of sanitizersAtTarget) {
14582
14781
  if (isKnownSinkType) {
14583
- if (san.sanitizes.includes(sinkType)) {
14782
+ if (sanitizerCoversSink(san, sinkType)) {
14584
14783
  return { sanitized: true, sanitizer: san };
14585
14784
  }
14586
14785
  } else if (san.sanitizes.length > 0) {
@@ -14602,7 +14801,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
14602
14801
  if (!sansAtLine || sansAtLine.length === 0) continue;
14603
14802
  for (const san of sansAtLine) {
14604
14803
  if (isKnownSinkType) {
14605
- if (san.sanitizes.includes(sinkType)) {
14804
+ if (sanitizerCoversSink(san, sinkType)) {
14606
14805
  return { sanitized: true, sanitizer: san };
14607
14806
  }
14608
14807
  } else if (san.sanitizes.length > 0) {