circle-ir 4.0.0 → 4.2.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.
@@ -16657,33 +16657,39 @@ function canSourceReachSink(sourceType, sinkType) {
16657
16657
  // disagreed. log_injection (CWE-117) and format_string (CWE-134) are
16658
16658
  // reachable from any user-controlled value that is logged / used as a
16659
16659
  // format string; nosql_injection (CWE-943) mirrors sql_injection's sources.
16660
- http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection"],
16661
- http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection"],
16662
- http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection"],
16663
- http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection"],
16660
+ // prompt_injection (CWE-1427 / OWASP LLM01) added cognium-ai#281: an
16661
+ // attacker-controlled value reaching an LLM prompt sink (CreateChatCompletion
16662
+ // et al.) is prompt injection. The reach map omitted it entirely, so the
16663
+ // scan path (`generateFindings`) dropped every `http_*/io/network/interproc
16664
+ // prompt_injection` flow even though `taint.flows` / the trust pass reported
16665
+ // it — the same "two paths disagree" shape as #129. Now emitted from scan too.
16666
+ http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16667
+ http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16668
+ http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16669
+ http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16664
16670
  // xss added cognium-dev 3.163.0: URL path components (getRequestURI,
16665
16671
  // getRequestURL, getPathInfo, getServletPath) reflected back into HTML
16666
16672
  // output are a classic reflected-XSS vector — cf. Basic35 in
16667
16673
  // SecuriBench Micro where `writer.println(req.getRequestURL())` is
16668
16674
  // annotated `/* BAD */`. Prior to 3.163.0 the reach map omitted xss
16669
16675
  // so http_path → xss inline-colocation flows were silently dropped.
16670
- http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary", "xss", "log_injection", "format_string"],
16671
- http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection"],
16676
+ http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect", "trust_boundary", "xss", "log_injection", "format_string", "prompt_injection"],
16677
+ http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "deserialization", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16672
16678
  // ssrf added Sprint 57 #200: bash CGI/webhook handlers and scripts that
16673
16679
  // take a URL on stdin or as a positional CLI arg (`curl "$1"`,
16674
16680
  // `wget "$(read line)"`) and curl/wget it server-side are textbook SSRF
16675
16681
  // (CVE-2022-41040 ProxyShell-class). Cross-language: `socket.urlopen(input())`
16676
16682
  // (Python), `axios.get(readline())` (JS) etc. also benefit.
16677
- io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf", "log_injection", "format_string"],
16683
+ io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf", "log_injection", "format_string", "prompt_injection"],
16678
16684
  env_input: ["command_injection", "path_traversal"],
16679
16685
  db_input: ["xss", "sql_injection", "log_injection"],
16680
16686
  // Second-order injection
16681
16687
  file_input: ["deserialization", "xxe", "path_traversal", "command_injection", "code_injection"],
16682
- network_input: ["sql_injection", "command_injection", "xss", "ssrf", "log_injection", "format_string", "nosql_injection"],
16688
+ network_input: ["sql_injection", "command_injection", "xss", "ssrf", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16683
16689
  config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf", "log_injection", "format_string"],
16684
16690
  // Servlet init params
16685
- interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection"],
16686
- // Cross-method taint; Sprint 82 (#189) — open_redirect added; Sprint 91 (#117) — trust_boundary added; cognium-ai#129 — log_injection/format_string/nosql_injection added
16691
+ interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect", "trust_boundary", "log_injection", "format_string", "nosql_injection", "prompt_injection"],
16692
+ // Cross-method taint; Sprint 82 (#189) — open_redirect added; Sprint 91 (#117) — trust_boundary added; cognium-ai#129 — log_injection/format_string/nosql_injection added; cognium-ai#281 — prompt_injection added
16687
16693
  plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection", "log_injection", "format_string"]
16688
16694
  // Plugin/config parameters
16689
16695
  };
@@ -21635,6 +21641,10 @@ function applyLibraryApiSurfaceDowngrade(findings) {
21635
21641
  }
21636
21642
 
21637
21643
  // src/analysis/note-coalescer.ts
21644
+ var CLICKJACKING_PAIR = /* @__PURE__ */ new Set(["missing-x-frame-options", "missing-csp-frame-ancestors"]);
21645
+ function levelRank(level) {
21646
+ return level === "error" ? 0 : level === "warning" ? 1 : 2;
21647
+ }
21638
21648
  function coalesceNoteLevelFindings(findings) {
21639
21649
  if (findings.length < 2) return [...findings];
21640
21650
  const groups = /* @__PURE__ */ new Map();
@@ -21651,11 +21661,30 @@ function coalesceNoteLevelFindings(findings) {
21651
21661
  }
21652
21662
  const out2 = [];
21653
21663
  for (const key of order) {
21654
- const bucket = groups.get(key);
21664
+ let bucket = groups.get(key);
21655
21665
  if (bucket.length === 1) {
21656
21666
  out2.push(bucket[0]);
21657
21667
  continue;
21658
21668
  }
21669
+ const cj = bucket.filter((f) => CLICKJACKING_PAIR.has(f.rule_id));
21670
+ if (new Set(cj.map((f) => f.rule_id)).size === CLICKJACKING_PAIR.size) {
21671
+ const primary2 = [...cj].sort(
21672
+ (a, b) => levelRank(a.level) - levelRank(b.level) || a.rule_id.localeCompare(b.rule_id)
21673
+ )[0];
21674
+ const labels = Array.from(
21675
+ /* @__PURE__ */ new Set([
21676
+ ...primary2.labels ?? [],
21677
+ ...cj.flatMap((f) => [f.rule_id, ...f.labels ?? []])
21678
+ ])
21679
+ ).filter((l) => l !== primary2.rule_id);
21680
+ out2.push({ ...primary2, labels });
21681
+ bucket = bucket.filter((f) => !CLICKJACKING_PAIR.has(f.rule_id));
21682
+ if (bucket.length === 0) continue;
21683
+ if (bucket.length === 1) {
21684
+ out2.push(bucket[0]);
21685
+ continue;
21686
+ }
21687
+ }
21659
21688
  const allNote = bucket.every((f) => f.level === "note");
21660
21689
  if (!allNote) {
21661
21690
  for (const f of bucket) out2.push(f);
@@ -26977,13 +27006,15 @@ function findJavaScriptAssignmentSources(sourceCode, language) {
26977
27006
  if (!["javascript", "typescript"].includes(language)) return [];
26978
27007
  const sources = [];
26979
27008
  const lines = sourceCode.split("\n");
27009
+ const isRouteHandler2 = /export\s+(?:async\s+)?(?:function\s+(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|loader|action)\b|const\s+(?:GET|POST|PUT|PATCH|DELETE|loader|action)\b)/.test(sourceCode) && /\{[^}]*\bparams\b[^}]*\}/.test(sourceCode);
27010
+ const patterns = isRouteHandler2 ? [...JS_TAINTED_PATTERNS, { pattern: /\bparams\s*[.[]/, type: "http_path" }] : JS_TAINTED_PATTERNS;
26980
27011
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
26981
27012
  const line = lines[lineNum];
26982
27013
  const lineNumber = lineNum + 1;
26983
27014
  const assignmentMatch = line.match(/(?:(?:var|let|const)\s+)?(\w+)\s*=\s*(.+)/);
26984
27015
  if (!assignmentMatch) continue;
26985
27016
  const [, varName, rhs] = assignmentMatch;
26986
- for (const { pattern, type } of JS_TAINTED_PATTERNS) {
27017
+ for (const { pattern, type } of patterns) {
26987
27018
  if (pattern.test(rhs)) {
26988
27019
  const alreadyExists = sources.some((s) => s.line === lineNumber && s.type === type);
26989
27020
  if (!alreadyExists) {
package/docs/SPEC.md CHANGED
@@ -90,7 +90,7 @@ File metadata and version information.
90
90
  interface Meta {
91
91
  circle_ir: "3.0";
92
92
  file: string;
93
- language: "java" | "javascript" | "typescript" | "python" | "go" | "rust" | "bash" | "html";
93
+ language: "java" | "javascript" | "typescript" | "python" | "go" | "rust" | "bash" | "html" | "csharp"; // "csharp" experimental (4.0.0+)
94
94
  loc: number;
95
95
  hash: string; // SHA256 prefix (16 chars)
96
96
  package?: string; // PENDING: Add to implementation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",