circle-ir 3.209.0 → 3.212.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.
@@ -13156,9 +13156,16 @@ var DEFAULT_SINKS = [
13156
13156
  // HTML sanitizer bypass markers (known CVE patterns)
13157
13157
  { method: "clean", class: "AntiSamy", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
13158
13158
  { method: "getValidSafeHTML", class: "ESAPI", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
13159
- // Request/session attribute reflection XSS (return value is tainted)
13160
- { method: "getAttribute", class: "HttpServletRequest", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [] },
13161
- { method: "getAttribute", class: "HttpSession", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [] },
13159
+ // NOTE (cognium-dev#270): `HttpServletRequest`/`HttpSession.getAttribute()`
13160
+ // were registered here as xss sinks to model "attribute reflection XSS", but
13161
+ // getAttribute is a *read* its return value is tainted (a SOURCE, already
13162
+ // registered as `io_input` above), never an HTML-output context. With
13163
+ // `arg_positions: []` the entry produced a degenerate source==sink self-flow
13164
+ // (the same getAttribute call flagged as both), firing CWE-79 on route-helper
13165
+ // reads that are never rendered (elide `getPath`). The real sink is wherever
13166
+ // the read value is later written to a response (getWriter/JspWriter/template),
13167
+ // which the io_input source reaches on its own. Same over-broad-sink family as
13168
+ // #268 (StringBuilder.append). Removed.
13162
13169
  // =========================================================================
13163
13170
  // Rust Sinks
13164
13171
  // =========================================================================
@@ -16203,6 +16210,70 @@ function isNonExecutablePython(trimmed) {
16203
16210
  return false;
16204
16211
  }
16205
16212
 
16213
+ // src/analysis/sanitizer-index.ts
16214
+ var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
16215
+ function getSanitizesSet(san) {
16216
+ let s = SANITIZER_SET_CACHE.get(san);
16217
+ if (!s) {
16218
+ s = new Set(san.sanitizes);
16219
+ SANITIZER_SET_CACHE.set(san, s);
16220
+ }
16221
+ return s;
16222
+ }
16223
+ function sanitizerCoversSink(san, sinkType) {
16224
+ return getSanitizesSet(san).has(sinkType);
16225
+ }
16226
+
16227
+ // src/analysis/dfg-walk.ts
16228
+ var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
16229
+ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
16230
+ const maxHops = options.maxHops ?? 32;
16231
+ let perFile = walkBackwardDefsMemo.get(chainsByToDef);
16232
+ if (perFile !== void 0) {
16233
+ const key = `${startDefId}|${maxHops}`;
16234
+ const hit = perFile.get(key);
16235
+ if (hit !== void 0) return hit;
16236
+ }
16237
+ const visited = /* @__PURE__ */ new Set();
16238
+ const lines = /* @__PURE__ */ new Set();
16239
+ let hopCapReached = false;
16240
+ const startDef = defById.get(startDefId);
16241
+ if (!startDef) {
16242
+ return { visited, lines, hopCapReached: false };
16243
+ }
16244
+ visited.add(startDefId);
16245
+ lines.add(startDef.line);
16246
+ const queue = [startDefId];
16247
+ let hops = 0;
16248
+ while (queue.length > 0) {
16249
+ if (hops >= maxHops) {
16250
+ hopCapReached = true;
16251
+ break;
16252
+ }
16253
+ hops++;
16254
+ const currentId = queue.shift();
16255
+ const incoming = chainsByToDef.get(currentId);
16256
+ if (!incoming) continue;
16257
+ for (const chain of incoming) {
16258
+ const fromId = chain.from_def;
16259
+ if (visited.has(fromId)) continue;
16260
+ visited.add(fromId);
16261
+ const fromDef = defById.get(fromId);
16262
+ if (fromDef) {
16263
+ lines.add(fromDef.line);
16264
+ }
16265
+ queue.push(fromId);
16266
+ }
16267
+ }
16268
+ const result = { visited, lines, hopCapReached };
16269
+ if (perFile === void 0) {
16270
+ perFile = /* @__PURE__ */ new Map();
16271
+ walkBackwardDefsMemo.set(chainsByToDef, perFile);
16272
+ }
16273
+ perFile.set(`${startDefId}|${maxHops}`, result);
16274
+ return result;
16275
+ }
16276
+
16206
16277
  // src/analysis/findings.ts
16207
16278
  function canSourceReachSink(sourceType, sinkType) {
16208
16279
  const sourceToSinkMapping = {
@@ -17568,70 +17639,6 @@ var AnalysisPipeline = class {
17568
17639
  }
17569
17640
  };
17570
17641
 
17571
- // src/analysis/dfg-walk.ts
17572
- var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
17573
- function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
17574
- const maxHops = options.maxHops ?? 32;
17575
- let perFile = walkBackwardDefsMemo.get(chainsByToDef);
17576
- if (perFile !== void 0) {
17577
- const key = `${startDefId}|${maxHops}`;
17578
- const hit = perFile.get(key);
17579
- if (hit !== void 0) return hit;
17580
- }
17581
- const visited = /* @__PURE__ */ new Set();
17582
- const lines = /* @__PURE__ */ new Set();
17583
- let hopCapReached = false;
17584
- const startDef = defById.get(startDefId);
17585
- if (!startDef) {
17586
- return { visited, lines, hopCapReached: false };
17587
- }
17588
- visited.add(startDefId);
17589
- lines.add(startDef.line);
17590
- const queue = [startDefId];
17591
- let hops = 0;
17592
- while (queue.length > 0) {
17593
- if (hops >= maxHops) {
17594
- hopCapReached = true;
17595
- break;
17596
- }
17597
- hops++;
17598
- const currentId = queue.shift();
17599
- const incoming = chainsByToDef.get(currentId);
17600
- if (!incoming) continue;
17601
- for (const chain of incoming) {
17602
- const fromId = chain.from_def;
17603
- if (visited.has(fromId)) continue;
17604
- visited.add(fromId);
17605
- const fromDef = defById.get(fromId);
17606
- if (fromDef) {
17607
- lines.add(fromDef.line);
17608
- }
17609
- queue.push(fromId);
17610
- }
17611
- }
17612
- const result = { visited, lines, hopCapReached };
17613
- if (perFile === void 0) {
17614
- perFile = /* @__PURE__ */ new Map();
17615
- walkBackwardDefsMemo.set(chainsByToDef, perFile);
17616
- }
17617
- perFile.set(`${startDefId}|${maxHops}`, result);
17618
- return result;
17619
- }
17620
-
17621
- // src/analysis/sanitizer-index.ts
17622
- var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
17623
- function getSanitizesSet(san) {
17624
- let s = SANITIZER_SET_CACHE.get(san);
17625
- if (!s) {
17626
- s = new Set(san.sanitizes);
17627
- SANITIZER_SET_CACHE.set(san, s);
17628
- }
17629
- return s;
17630
- }
17631
- function sanitizerCoversSink(san, sinkType) {
17632
- return getSanitizesSet(san).has(sinkType);
17633
- }
17634
-
17635
17642
  // src/analysis/taint-propagation.ts
17636
17643
  function buildSanitizersByLine(sanitizers) {
17637
17644
  const out2 = /* @__PURE__ */ new Map();
@@ -32467,6 +32474,43 @@ var SQL_EXEC_METHODS = /* @__PURE__ */ new Set([
32467
32474
  var JAVA_SQL_ASSIGN_RE_TEMPLATE = "\\b(?:String|CharSequence|final\\s+String|var)\\s+SQLVAR\\b\\s*=\\s*(.+?);";
32468
32475
  var REPLACE_ALL_TO_PLACEHOLDER_RE = /\.replaceAll\s*\([\s\S]*?,\s*"[\s,?]*\?[\s,?]*"\s*\)/;
32469
32476
  var HTML_CONTENT_TYPE_RE = /text\/html|TEXT_HTML/;
32477
+ function javaTraversalRejectGuardedLines(code) {
32478
+ const covered = /* @__PURE__ */ new Set();
32479
+ const lines = code.split("\n");
32480
+ const guardOpen = /\bif\s*\(\s*([A-Za-z_]\w*)\s*\.\s*contains\s*\(\s*"[^"]*\.\.[^"]*"\s*\)/;
32481
+ const terminatorRe = /\b(throw|return)\b/;
32482
+ const assignRe = /^\s*(?:[A-Za-z_][\w.<>[\]]*\s+)?([A-Za-z_]\w*)\s*=(?!=)\s*(.*)$/;
32483
+ for (let i2 = 0; i2 < lines.length; i2++) {
32484
+ const m = guardOpen.exec(lines[i2]);
32485
+ if (!m) continue;
32486
+ let hasTerminator = terminatorRe.test(lines[i2]);
32487
+ if (!hasTerminator) {
32488
+ for (let j = i2 + 1; j < Math.min(lines.length, i2 + 6); j++) {
32489
+ if (terminatorRe.test(lines[j])) {
32490
+ hasTerminator = true;
32491
+ break;
32492
+ }
32493
+ if (lines[j].includes("}")) break;
32494
+ }
32495
+ }
32496
+ if (!hasTerminator) continue;
32497
+ const guarded = /* @__PURE__ */ new Set([m[1]]);
32498
+ const mentionsGuarded = (text) => {
32499
+ for (const name2 of guarded) if (new RegExp(`\\b${name2}\\b`).test(text)) return true;
32500
+ return false;
32501
+ };
32502
+ for (let l = i2 + 1; l < lines.length; l++) {
32503
+ const lineText = lines[l];
32504
+ const assign = assignRe.exec(lineText);
32505
+ if (assign) {
32506
+ if (mentionsGuarded(assign[2])) guarded.add(assign[1]);
32507
+ else if (assign[1] !== m[1]) guarded.delete(assign[1]);
32508
+ }
32509
+ if (mentionsGuarded(lineText)) covered.add(l + 1);
32510
+ }
32511
+ }
32512
+ return covered;
32513
+ }
32470
32514
  var JAVA_INLINE_MATCHES_RE = /\.\s*matches\s*\(\s*"((?:[^"\\]|\\.)*)"\s*\)/g;
32471
32515
  function resolveJavaReceiverType(receiver, sinkLine, sourceLines) {
32472
32516
  if (!receiver || !/^[A-Za-z_]\w*$/.test(receiver)) return null;
@@ -33153,6 +33197,14 @@ var SinkFilterPass = class {
33153
33197
  if (language === "java" && !HTML_CONTENT_TYPE_RE.test(ctx.code)) {
33154
33198
  filtered = filtered.filter((sink) => !(sink.type === "xss" && sink.method === "body"));
33155
33199
  }
33200
+ if (language === "java") {
33201
+ const guardedLines = javaTraversalRejectGuardedLines(ctx.code);
33202
+ if (guardedLines.size > 0) {
33203
+ filtered = filtered.filter(
33204
+ (sink) => !(sink.type === "path_traversal" && guardedLines.has(sink.line))
33205
+ );
33206
+ }
33207
+ }
33156
33208
  if (["javascript", "typescript"].includes(language)) {
33157
33209
  const sourceLines = ctx.code.split("\n");
33158
33210
  filtered = filtered.filter((sink) => {
@@ -12551,9 +12551,16 @@ var DEFAULT_SINKS = [
12551
12551
  // HTML sanitizer bypass markers (known CVE patterns)
12552
12552
  { method: "clean", class: "AntiSamy", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
12553
12553
  { method: "getValidSafeHTML", class: "ESAPI", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
12554
- // Request/session attribute reflection XSS (return value is tainted)
12555
- { method: "getAttribute", class: "HttpServletRequest", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [] },
12556
- { method: "getAttribute", class: "HttpSession", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [] },
12554
+ // NOTE (cognium-dev#270): `HttpServletRequest`/`HttpSession.getAttribute()`
12555
+ // were registered here as xss sinks to model "attribute reflection XSS", but
12556
+ // getAttribute is a *read* its return value is tainted (a SOURCE, already
12557
+ // registered as `io_input` above), never an HTML-output context. With
12558
+ // `arg_positions: []` the entry produced a degenerate source==sink self-flow
12559
+ // (the same getAttribute call flagged as both), firing CWE-79 on route-helper
12560
+ // reads that are never rendered (elide `getPath`). The real sink is wherever
12561
+ // the read value is later written to a response (getWriter/JspWriter/template),
12562
+ // which the io_input source reaches on its own. Same over-broad-sink family as
12563
+ // #268 (StringBuilder.append). Removed.
12557
12564
  // =========================================================================
12558
12565
  // Rust Sinks
12559
12566
  // =========================================================================
@@ -12485,9 +12485,16 @@ var DEFAULT_SINKS = [
12485
12485
  // HTML sanitizer bypass markers (known CVE patterns)
12486
12486
  { method: "clean", class: "AntiSamy", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
12487
12487
  { method: "getValidSafeHTML", class: "ESAPI", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [0] },
12488
- // Request/session attribute reflection XSS (return value is tainted)
12489
- { method: "getAttribute", class: "HttpServletRequest", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [] },
12490
- { method: "getAttribute", class: "HttpSession", type: "xss", cwe: "CWE-79", severity: "medium", arg_positions: [] },
12488
+ // NOTE (cognium-dev#270): `HttpServletRequest`/`HttpSession.getAttribute()`
12489
+ // were registered here as xss sinks to model "attribute reflection XSS", but
12490
+ // getAttribute is a *read* its return value is tainted (a SOURCE, already
12491
+ // registered as `io_input` above), never an HTML-output context. With
12492
+ // `arg_positions: []` the entry produced a degenerate source==sink self-flow
12493
+ // (the same getAttribute call flagged as both), firing CWE-79 on route-helper
12494
+ // reads that are never rendered (elide `getPath`). The real sink is wherever
12495
+ // the read value is later written to a response (getWriter/JspWriter/template),
12496
+ // which the io_input source reaches on its own. Same over-broad-sink family as
12497
+ // #268 (StringBuilder.append). Removed.
12491
12498
  // =========================================================================
12492
12499
  // Rust Sinks
12493
12500
  // =========================================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "3.209.0",
3
+ "version": "3.212.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",