circle-ir 3.96.0 → 3.98.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/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +18 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +272 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +130 -0
- package/dist/core/circle-ir-core.cjs +118 -0
- package/dist/core/circle-ir-core.js +118 -0
- package/package.json +1 -1
|
@@ -12522,6 +12522,118 @@ function isFunctionCallbackArgument(arg) {
|
|
|
12522
12522
|
if (/^function\b/.test(body2)) return true;
|
|
12523
12523
|
return false;
|
|
12524
12524
|
}
|
|
12525
|
+
function findGoLocalDeclaredType(varName, callLine, sourceLines) {
|
|
12526
|
+
if (!varName) return null;
|
|
12527
|
+
const WINDOW = 50;
|
|
12528
|
+
const startIdx = Math.max(0, callLine - 2);
|
|
12529
|
+
const endIdx = Math.max(0, startIdx - WINDOW);
|
|
12530
|
+
const name2 = varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12531
|
+
const VAR_DECL_RE = new RegExp(`^\\s*var\\s+${name2}\\s+([^=\\n][^\\n]*)$`);
|
|
12532
|
+
const VAR_INIT_RE = new RegExp(`^\\s*var\\s+${name2}(?:\\s+[^=\\n]*?)?\\s*=\\s*(.+)$`);
|
|
12533
|
+
const SHORT_INIT_RE = new RegExp(`^\\s*${name2}\\s*:=\\s*(.+)$`);
|
|
12534
|
+
for (let i2 = startIdx; i2 >= endIdx; i2--) {
|
|
12535
|
+
const raw = sourceLines[i2];
|
|
12536
|
+
if (raw === void 0) continue;
|
|
12537
|
+
const line = raw.replace(/\/\/.*$/, "").trimEnd();
|
|
12538
|
+
let m = VAR_DECL_RE.exec(line);
|
|
12539
|
+
if (m) {
|
|
12540
|
+
return { kind: "typed-decl", text: m[1].trim() };
|
|
12541
|
+
}
|
|
12542
|
+
m = VAR_INIT_RE.exec(line);
|
|
12543
|
+
if (m) {
|
|
12544
|
+
return classifyGoRhs(m[1].trim());
|
|
12545
|
+
}
|
|
12546
|
+
m = SHORT_INIT_RE.exec(line);
|
|
12547
|
+
if (m) {
|
|
12548
|
+
return classifyGoRhs(m[1].trim());
|
|
12549
|
+
}
|
|
12550
|
+
}
|
|
12551
|
+
return null;
|
|
12552
|
+
}
|
|
12553
|
+
function classifyGoRhs(rhs) {
|
|
12554
|
+
const body2 = rhs.startsWith("&") ? rhs.slice(1).trimStart() : rhs;
|
|
12555
|
+
if (/^make\s*\(/.test(body2)) {
|
|
12556
|
+
return { kind: "untyped" };
|
|
12557
|
+
}
|
|
12558
|
+
const parenIdx = body2.indexOf("(");
|
|
12559
|
+
const braceIdx = body2.indexOf("{");
|
|
12560
|
+
if (parenIdx >= 0 && (braceIdx < 0 || parenIdx < braceIdx)) {
|
|
12561
|
+
return { kind: "untyped" };
|
|
12562
|
+
}
|
|
12563
|
+
if (braceIdx > 0) {
|
|
12564
|
+
const tag = body2.slice(0, braceIdx).trim();
|
|
12565
|
+
if (tag.length > 0) {
|
|
12566
|
+
if (/\binterface\s*\{\s*\}/.test(tag)) return { kind: "untyped" };
|
|
12567
|
+
if (/\bany\b/.test(tag)) return { kind: "untyped" };
|
|
12568
|
+
return { kind: "typed-init", text: tag };
|
|
12569
|
+
}
|
|
12570
|
+
}
|
|
12571
|
+
return { kind: "untyped" };
|
|
12572
|
+
}
|
|
12573
|
+
function classifyGoDestinationType(decl) {
|
|
12574
|
+
if (decl === null) return "unknown";
|
|
12575
|
+
if (decl.kind === "untyped") return "unknown";
|
|
12576
|
+
const text = decl.text;
|
|
12577
|
+
if (/\binterface\s*\{\s*\}/.test(text)) return "unsafe";
|
|
12578
|
+
if (/\bany\b/.test(text)) return "unsafe";
|
|
12579
|
+
return "safe";
|
|
12580
|
+
}
|
|
12581
|
+
function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
12582
|
+
if (language !== "go") return false;
|
|
12583
|
+
if (pattern.type !== "deserialization") return false;
|
|
12584
|
+
if (!sourceLines) return false;
|
|
12585
|
+
const method = call.method_name;
|
|
12586
|
+
let destPos;
|
|
12587
|
+
if (method === "Unmarshal" && pattern.class === "json") {
|
|
12588
|
+
destPos = 1;
|
|
12589
|
+
} else if (method === "Decode" && pattern.class === "Decoder") {
|
|
12590
|
+
destPos = 0;
|
|
12591
|
+
} else {
|
|
12592
|
+
return false;
|
|
12593
|
+
}
|
|
12594
|
+
const destArg = call.arguments.find((a) => a.position === destPos);
|
|
12595
|
+
if (!destArg) return false;
|
|
12596
|
+
const expr = (destArg.expression ?? "").trim();
|
|
12597
|
+
if (expr.length === 0) return false;
|
|
12598
|
+
if (expr.startsWith("&")) {
|
|
12599
|
+
const inner = expr.slice(1).trimStart();
|
|
12600
|
+
const compIdx = inner.indexOf("{");
|
|
12601
|
+
if (compIdx > 0) {
|
|
12602
|
+
const decl2 = { kind: "typed-init", text: inner.slice(0, compIdx).trim() };
|
|
12603
|
+
return classifyGoDestinationType(decl2) === "safe";
|
|
12604
|
+
}
|
|
12605
|
+
const m = /^&([A-Za-z_]\w*)$/.exec(expr);
|
|
12606
|
+
if (!m) return false;
|
|
12607
|
+
const varName = m[1];
|
|
12608
|
+
const decl = findGoLocalDeclaredType(varName, call.location.line, sourceLines);
|
|
12609
|
+
return classifyGoDestinationType(decl) === "safe";
|
|
12610
|
+
}
|
|
12611
|
+
const idMatch = /^([A-Za-z_]\w*)$/.exec(expr);
|
|
12612
|
+
if (idMatch) {
|
|
12613
|
+
const decl = findGoLocalDeclaredType(idMatch[1], call.location.line, sourceLines);
|
|
12614
|
+
return classifyGoDestinationType(decl) === "safe";
|
|
12615
|
+
}
|
|
12616
|
+
return false;
|
|
12617
|
+
}
|
|
12618
|
+
var TEMPLATE_LITERAL_RECEIVER_RE = /^Template\(\s*(?:"[^"\\]*"|'[^'\\]*')\s*\)$/;
|
|
12619
|
+
function isSafeJinjaRenderCall(call, pattern, language) {
|
|
12620
|
+
if (language !== "python") return false;
|
|
12621
|
+
if (pattern.type !== "xss" && pattern.type !== "code_injection") return false;
|
|
12622
|
+
const method = call.method_name;
|
|
12623
|
+
if (method === "render_template_string") {
|
|
12624
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
12625
|
+
return typeof arg0?.literal === "string";
|
|
12626
|
+
}
|
|
12627
|
+
if (method === "Template" || method === "from_string") {
|
|
12628
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
12629
|
+
return typeof arg0?.literal === "string";
|
|
12630
|
+
}
|
|
12631
|
+
if (method === "render") {
|
|
12632
|
+
const receiver = (call.receiver ?? "").trim();
|
|
12633
|
+
return TEMPLATE_LITERAL_RECEIVER_RE.test(receiver);
|
|
12634
|
+
}
|
|
12635
|
+
return false;
|
|
12636
|
+
}
|
|
12525
12637
|
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
12526
12638
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
12527
12639
|
for (const call of calls) {
|
|
@@ -12560,6 +12672,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
12560
12672
|
continue;
|
|
12561
12673
|
}
|
|
12562
12674
|
}
|
|
12675
|
+
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
12676
|
+
continue;
|
|
12677
|
+
}
|
|
12678
|
+
if (isSafeJinjaRenderCall(call, pattern, language)) {
|
|
12679
|
+
continue;
|
|
12680
|
+
}
|
|
12563
12681
|
const location = formatCallLocation(call);
|
|
12564
12682
|
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
12565
12683
|
const confidence = calculateSinkConfidence(call, pattern);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.98.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",
|