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.
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +10 -3
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/findings.d.ts +2 -2
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +77 -1
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +67 -0
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +119 -67
- package/dist/core/circle-ir-core.cjs +10 -3
- package/dist/core/circle-ir-core.js +10 -3
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
13160
|
-
|
|
13161
|
-
|
|
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
|
-
//
|
|
12555
|
-
|
|
12556
|
-
|
|
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
|
-
//
|
|
12489
|
-
|
|
12490
|
-
|
|
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.
|
|
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",
|