circle-ir 3.175.0 → 3.177.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/arg-type-resolver.d.ts +90 -0
- package/dist/analysis/arg-type-resolver.d.ts.map +1 -0
- package/dist/analysis/arg-type-resolver.js +174 -0
- package/dist/analysis/arg-type-resolver.js.map +1 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +75 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/constant-propagation/propagator.d.ts +1 -2
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +28 -30
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/dfg-walk.d.ts.map +1 -1
- package/dist/analysis/dfg-walk.js +28 -1
- package/dist/analysis/dfg-walk.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +71 -2
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +97 -38
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +318 -51
- package/dist/core/circle-ir-core.cjs +277 -50
- package/dist/core/circle-ir-core.js +277 -50
- package/dist/core/extractors/calls.js +131 -1
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/cfg.d.ts.map +1 -1
- package/dist/core/extractors/cfg.js +17 -9
- package/dist/core/extractors/cfg.js.map +1 -1
- package/package.json +1 -1
|
@@ -7415,8 +7415,16 @@ function extractGoCallInfo(node) {
|
|
|
7415
7415
|
if (funcNode.type === "selector_expression") {
|
|
7416
7416
|
const operand = funcNode.childForFieldName("operand");
|
|
7417
7417
|
const field = funcNode.childForFieldName("field");
|
|
7418
|
-
receiver = operand ? getNodeText(operand) : null;
|
|
7419
7418
|
methodName = field ? getNodeText(field) : getNodeText(funcNode);
|
|
7419
|
+
if (operand) {
|
|
7420
|
+
const opText = getNodeText(operand);
|
|
7421
|
+
if (operand.type === "identifier") {
|
|
7422
|
+
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
7423
|
+
receiver = resolved !== null ? resolved : opText;
|
|
7424
|
+
} else {
|
|
7425
|
+
receiver = opText;
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7420
7428
|
} else if (funcNode.type === "identifier") {
|
|
7421
7429
|
methodName = getNodeText(funcNode);
|
|
7422
7430
|
} else {
|
|
@@ -7494,6 +7502,80 @@ function findGoEnclosingFunction(node) {
|
|
|
7494
7502
|
}
|
|
7495
7503
|
return null;
|
|
7496
7504
|
}
|
|
7505
|
+
function resolveGoLocalReceiverType(operandName, callNode) {
|
|
7506
|
+
let cur = callNode.parent;
|
|
7507
|
+
while (cur) {
|
|
7508
|
+
if (cur.type === "method_declaration") {
|
|
7509
|
+
const receiver = cur.childForFieldName("receiver");
|
|
7510
|
+
if (receiver) {
|
|
7511
|
+
const t = extractGoParamTypeForName(receiver, operandName);
|
|
7512
|
+
if (t !== null) return t;
|
|
7513
|
+
}
|
|
7514
|
+
const params = cur.childForFieldName("parameters");
|
|
7515
|
+
if (params) {
|
|
7516
|
+
const t = extractGoParamTypeForName(params, operandName);
|
|
7517
|
+
if (t !== null) return t;
|
|
7518
|
+
}
|
|
7519
|
+
return null;
|
|
7520
|
+
}
|
|
7521
|
+
if (cur.type === "function_declaration" || cur.type === "func_literal") {
|
|
7522
|
+
const params = cur.childForFieldName("parameters");
|
|
7523
|
+
if (params) {
|
|
7524
|
+
const t = extractGoParamTypeForName(params, operandName);
|
|
7525
|
+
if (t !== null) return t;
|
|
7526
|
+
}
|
|
7527
|
+
return null;
|
|
7528
|
+
}
|
|
7529
|
+
cur = cur.parent;
|
|
7530
|
+
}
|
|
7531
|
+
return null;
|
|
7532
|
+
}
|
|
7533
|
+
function extractGoParamTypeForName(list, operandName) {
|
|
7534
|
+
for (let i2 = 0; i2 < list.namedChildCount; i2++) {
|
|
7535
|
+
const child = list.namedChild(i2);
|
|
7536
|
+
if (!child || child.type !== "parameter_declaration") continue;
|
|
7537
|
+
const typeNode = child.childForFieldName("type");
|
|
7538
|
+
if (!typeNode) continue;
|
|
7539
|
+
let matched = false;
|
|
7540
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
7541
|
+
const c = child.namedChild(j);
|
|
7542
|
+
if (!c) continue;
|
|
7543
|
+
if (c.type === "identifier" && getNodeText(c) === operandName) {
|
|
7544
|
+
matched = true;
|
|
7545
|
+
break;
|
|
7546
|
+
}
|
|
7547
|
+
if (c.type === "identifier_list") {
|
|
7548
|
+
for (let k = 0; k < c.namedChildCount; k++) {
|
|
7549
|
+
const id = c.namedChild(k);
|
|
7550
|
+
if (id && id.type === "identifier" && getNodeText(id) === operandName) {
|
|
7551
|
+
matched = true;
|
|
7552
|
+
break;
|
|
7553
|
+
}
|
|
7554
|
+
}
|
|
7555
|
+
if (matched) break;
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
if (!matched) continue;
|
|
7559
|
+
return extractGoTypeLastSegment(typeNode);
|
|
7560
|
+
}
|
|
7561
|
+
return null;
|
|
7562
|
+
}
|
|
7563
|
+
function extractGoTypeLastSegment(typeNode) {
|
|
7564
|
+
if (typeNode.type === "pointer_type") {
|
|
7565
|
+
const inner = typeNode.namedChild(0);
|
|
7566
|
+
return inner ? extractGoTypeLastSegment(inner) : null;
|
|
7567
|
+
}
|
|
7568
|
+
if (typeNode.type === "qualified_type") {
|
|
7569
|
+
const name2 = typeNode.childForFieldName("name");
|
|
7570
|
+
if (name2) return getNodeText(name2);
|
|
7571
|
+
const last = typeNode.namedChild(typeNode.namedChildCount - 1);
|
|
7572
|
+
return last ? getNodeText(last) : null;
|
|
7573
|
+
}
|
|
7574
|
+
if (typeNode.type === "type_identifier" || typeNode.type === "identifier") {
|
|
7575
|
+
return getNodeText(typeNode);
|
|
7576
|
+
}
|
|
7577
|
+
return null;
|
|
7578
|
+
}
|
|
7497
7579
|
|
|
7498
7580
|
// src/core/extractors/imports.ts
|
|
7499
7581
|
function detectLanguage2(tree) {
|
|
@@ -8242,10 +8324,10 @@ function buildCFG(tree, language, cache) {
|
|
|
8242
8324
|
const allEdges = [];
|
|
8243
8325
|
let blockIdCounter = 0;
|
|
8244
8326
|
if (effectiveLanguage === "bash") {
|
|
8245
|
-
return buildBashCFG(tree, blockIdCounter);
|
|
8327
|
+
return buildBashCFG(tree, blockIdCounter, cache);
|
|
8246
8328
|
}
|
|
8247
8329
|
if (effectiveLanguage === "go") {
|
|
8248
|
-
return buildGoCFG(tree, blockIdCounter);
|
|
8330
|
+
return buildGoCFG(tree, blockIdCounter, cache);
|
|
8249
8331
|
}
|
|
8250
8332
|
if (isJavaScript) {
|
|
8251
8333
|
const functions = [
|
|
@@ -8602,11 +8684,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8602
8684
|
nextId: currentId
|
|
8603
8685
|
};
|
|
8604
8686
|
}
|
|
8605
|
-
function buildBashCFG(tree, startId) {
|
|
8687
|
+
function buildBashCFG(tree, startId, cache) {
|
|
8606
8688
|
const allBlocks = [];
|
|
8607
8689
|
const allEdges = [];
|
|
8608
8690
|
let blockIdCounter = startId;
|
|
8609
|
-
const functions =
|
|
8691
|
+
const functions = getNodesFromCache(tree.rootNode, "function_definition", cache);
|
|
8610
8692
|
for (const func2 of functions) {
|
|
8611
8693
|
const body2 = func2.childForFieldName("body");
|
|
8612
8694
|
if (!body2) continue;
|
|
@@ -8723,12 +8805,12 @@ function isStatement(node, isJavaScript) {
|
|
|
8723
8805
|
]);
|
|
8724
8806
|
return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
|
|
8725
8807
|
}
|
|
8726
|
-
function buildGoCFG(tree, blockIdCounter) {
|
|
8808
|
+
function buildGoCFG(tree, blockIdCounter, cache) {
|
|
8727
8809
|
const allBlocks = [];
|
|
8728
8810
|
const allEdges = [];
|
|
8729
8811
|
const functions = [
|
|
8730
|
-
...
|
|
8731
|
-
...
|
|
8812
|
+
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
8813
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache)
|
|
8732
8814
|
];
|
|
8733
8815
|
for (const func2 of functions) {
|
|
8734
8816
|
const body2 = func2.childForFieldName("body");
|
|
@@ -10642,6 +10724,59 @@ var OPEN_REDIRECT_FRAMEWORK_SINKS = [
|
|
|
10642
10724
|
{ method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
10643
10725
|
{ method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
|
|
10644
10726
|
];
|
|
10727
|
+
var DESERIALIZATION_FRAMEWORK_SINKS = [
|
|
10728
|
+
// --- Python: stdlib + popular third-party --------------------------------
|
|
10729
|
+
// pickle: known-dangerous, any unpickle on untrusted bytes is RCE.
|
|
10730
|
+
{ method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10731
|
+
{ method: "load", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10732
|
+
// cPickle alias (Python 2 name, still around in older codebases).
|
|
10733
|
+
{ method: "loads", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10734
|
+
{ method: "load", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10735
|
+
// marshal: stdlib code-object deserializer. Loading a tainted bytestring
|
|
10736
|
+
// as a code object followed by `exec` is arbitrary-code execution.
|
|
10737
|
+
{ method: "loads", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10738
|
+
{ method: "load", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10739
|
+
// dill: pickle superset — same RCE profile.
|
|
10740
|
+
{ method: "loads", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10741
|
+
{ method: "load", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10742
|
+
// jsonpickle: JSON wrapper around pickle — trusts `py/object` marker.
|
|
10743
|
+
{ method: "decode", class: "jsonpickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10744
|
+
// --- Go: encoding/gob + yaml.Unmarshal -----------------------------------
|
|
10745
|
+
// gob.NewDecoder(r).Decode(&v): tainted io.Reader → arbitrary Go values.
|
|
10746
|
+
{ method: "Decode", class: "Decoder", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
10747
|
+
// gopkg.in/yaml.v2 + v3 top-level function; interface{} target is unsafe.
|
|
10748
|
+
{ method: "Unmarshal", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
10749
|
+
// --- JS/TS: node-serialize ------------------------------------------------
|
|
10750
|
+
// Known-dangerous — accepts embedded IIFE that runs during deserialize.
|
|
10751
|
+
{ method: "unserialize", class: "nodeSerialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
|
|
10752
|
+
];
|
|
10753
|
+
var NOSQL_FRAMEWORK_SINKS = [
|
|
10754
|
+
// --- Python: pymongo Collection ------------------------------------------
|
|
10755
|
+
// Every filter-taking Collection method; filter arg[0] is the query dict.
|
|
10756
|
+
{ method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10757
|
+
{ method: "find_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10758
|
+
{ method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10759
|
+
{ method: "update_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
|
|
10760
|
+
{ method: "update_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
|
|
10761
|
+
{ method: "delete_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10762
|
+
{ method: "delete_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10763
|
+
{ method: "count_documents", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10764
|
+
// --- Java: Spring Data MongoTemplate + native MongoCollection ------------
|
|
10765
|
+
{ method: "find", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10766
|
+
{ method: "findOne", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10767
|
+
{ method: "findAll", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10768
|
+
{ method: "find", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10769
|
+
{ method: "aggregate", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10770
|
+
// --- Go: go.mongodb.org/mongo-driver Collection --------------------------
|
|
10771
|
+
// These fire once Go local-receiver type resolution lands (see
|
|
10772
|
+
// taint-matcher.ts + #240 ship 2 Go receiver work). Same gate as the
|
|
10773
|
+
// gin/fiber Ctx sinks in ship 1.
|
|
10774
|
+
{ method: "Find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
|
|
10775
|
+
{ method: "FindOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
|
|
10776
|
+
{ method: "UpdateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
|
|
10777
|
+
{ method: "UpdateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
|
|
10778
|
+
{ method: "DeleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] }
|
|
10779
|
+
];
|
|
10645
10780
|
var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
10646
10781
|
// --- Python: Django cache write -----------------------------------------
|
|
10647
10782
|
{ method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
|
|
@@ -12325,7 +12460,12 @@ var DEFAULT_SINKS = [
|
|
|
12325
12460
|
// keep the DEFAULT_SINKS literal within TypeScript's union-type
|
|
12326
12461
|
// inference complexity limit (TS2590). See ~lines 661-752.
|
|
12327
12462
|
...OPEN_REDIRECT_FRAMEWORK_SINKS,
|
|
12328
|
-
...TRUST_BOUNDARY_FRAMEWORK_SINKS
|
|
12463
|
+
...TRUST_BOUNDARY_FRAMEWORK_SINKS,
|
|
12464
|
+
// cognium-dev #240 ship 2 — extended framework sinks for
|
|
12465
|
+
// deserialization (CWE-502) and nosql_injection (CWE-943). Same
|
|
12466
|
+
// pattern: constants defined near the ship-1 blocks and spread here.
|
|
12467
|
+
...DESERIALIZATION_FRAMEWORK_SINKS,
|
|
12468
|
+
...NOSQL_FRAMEWORK_SINKS
|
|
12329
12469
|
];
|
|
12330
12470
|
var DEFAULT_SANITIZERS = [
|
|
12331
12471
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -12668,6 +12808,64 @@ function getDefaultConfig() {
|
|
|
12668
12808
|
};
|
|
12669
12809
|
}
|
|
12670
12810
|
|
|
12811
|
+
// src/analysis/arg-type-resolver.ts
|
|
12812
|
+
function findEnclosingType(callInMethod, types) {
|
|
12813
|
+
if (!callInMethod) return null;
|
|
12814
|
+
for (const t of types) {
|
|
12815
|
+
for (const m of t.methods) {
|
|
12816
|
+
if (m.name === callInMethod) return t;
|
|
12817
|
+
}
|
|
12818
|
+
}
|
|
12819
|
+
return null;
|
|
12820
|
+
}
|
|
12821
|
+
function resolveIdentifierType(name2, callInMethod, types) {
|
|
12822
|
+
if (!name2) return null;
|
|
12823
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
12824
|
+
if (enclosing) {
|
|
12825
|
+
for (const m of enclosing.methods) {
|
|
12826
|
+
if (m.name !== callInMethod) continue;
|
|
12827
|
+
for (const p of m.parameters) {
|
|
12828
|
+
if (p.name === name2 && p.type) return p.type;
|
|
12829
|
+
}
|
|
12830
|
+
}
|
|
12831
|
+
for (const f of enclosing.fields) {
|
|
12832
|
+
if (f.name === name2 && f.type) return f.type;
|
|
12833
|
+
}
|
|
12834
|
+
return null;
|
|
12835
|
+
}
|
|
12836
|
+
for (const t of types) {
|
|
12837
|
+
for (const m of t.methods) {
|
|
12838
|
+
for (const p of m.parameters) {
|
|
12839
|
+
if (p.name === name2 && p.type) return p.type;
|
|
12840
|
+
}
|
|
12841
|
+
}
|
|
12842
|
+
for (const f of t.fields) {
|
|
12843
|
+
if (f.name === name2 && f.type) return f.type;
|
|
12844
|
+
}
|
|
12845
|
+
}
|
|
12846
|
+
return null;
|
|
12847
|
+
}
|
|
12848
|
+
function resolveCallReturnType(calleeName, callInMethod, types) {
|
|
12849
|
+
if (!calleeName) return null;
|
|
12850
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
12851
|
+
if (enclosing) {
|
|
12852
|
+
for (const m of enclosing.methods) {
|
|
12853
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
12854
|
+
}
|
|
12855
|
+
}
|
|
12856
|
+
for (const t of types) {
|
|
12857
|
+
for (const m of t.methods) {
|
|
12858
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
12859
|
+
}
|
|
12860
|
+
}
|
|
12861
|
+
return null;
|
|
12862
|
+
}
|
|
12863
|
+
function isBoundedClassType(rawType) {
|
|
12864
|
+
if (!rawType) return false;
|
|
12865
|
+
const stripped = rawType.trim().replace(/^(?:java\.lang\.)+/, "");
|
|
12866
|
+
return /^Class(?:\s*<[\s\S]*>)?$/.test(stripped);
|
|
12867
|
+
}
|
|
12868
|
+
|
|
12671
12869
|
// src/analysis/taint-matcher.ts
|
|
12672
12870
|
var PYTHON_TAINTED_PATTERNS = [
|
|
12673
12871
|
{ pattern: /\brequest\.args\b/, sourceType: "http_param" },
|
|
@@ -12693,7 +12891,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
12693
12891
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
12694
12892
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
12695
12893
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
12696
|
-
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
|
|
12894
|
+
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
12697
12895
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
12698
12896
|
return { sources, sinks, sanitizers };
|
|
12699
12897
|
}
|
|
@@ -12787,11 +12985,11 @@ function attachSourceLineCode(sources, sinks, code) {
|
|
|
12787
12985
|
}
|
|
12788
12986
|
function findSources(calls, types, patterns, sourceLines, language) {
|
|
12789
12987
|
const sources = [];
|
|
12988
|
+
const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
|
|
12989
|
+
(p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
|
|
12990
|
+
);
|
|
12790
12991
|
for (const call of calls) {
|
|
12791
|
-
for (const pattern of
|
|
12792
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12793
|
-
continue;
|
|
12794
|
-
}
|
|
12992
|
+
for (const pattern of patternsForLanguage) {
|
|
12795
12993
|
if (matchesSourcePattern(call, pattern)) {
|
|
12796
12994
|
sources.push({
|
|
12797
12995
|
type: pattern.type,
|
|
@@ -12807,11 +13005,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12807
13005
|
for (const type of types) {
|
|
12808
13006
|
for (const method of type.methods) {
|
|
12809
13007
|
for (const param of method.parameters) {
|
|
12810
|
-
for (const pattern of
|
|
13008
|
+
for (const pattern of patternsForLanguage) {
|
|
12811
13009
|
if (pattern.annotation && pattern.param_tainted) {
|
|
12812
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12813
|
-
continue;
|
|
12814
|
-
}
|
|
12815
13010
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
12816
13011
|
const paramLine = param.line ?? method.start_line;
|
|
12817
13012
|
sources.push({
|
|
@@ -12830,11 +13025,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12830
13025
|
}
|
|
12831
13026
|
for (const type of types) {
|
|
12832
13027
|
for (const method of type.methods) {
|
|
12833
|
-
for (const pattern of
|
|
13028
|
+
for (const pattern of patternsForLanguage) {
|
|
12834
13029
|
if (!pattern.method_annotation) continue;
|
|
12835
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12836
|
-
continue;
|
|
12837
|
-
}
|
|
12838
13030
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
12839
13031
|
for (const param of method.parameters) {
|
|
12840
13032
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -13271,13 +13463,30 @@ function isSafeRustCommandCall(call, pattern, language) {
|
|
|
13271
13463
|
return false;
|
|
13272
13464
|
}
|
|
13273
13465
|
var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
|
|
13274
|
-
function argIsClassLiteral(call, position) {
|
|
13466
|
+
function argIsClassLiteral(call, position, types) {
|
|
13275
13467
|
const arg = call.arguments.find((a) => a.position === position);
|
|
13276
13468
|
if (!arg) return false;
|
|
13277
13469
|
const expr = (arg.literal ?? arg.expression ?? "").trim();
|
|
13278
13470
|
if (!expr) return false;
|
|
13279
13471
|
if (CLASS_LITERAL_RE.test(expr)) return true;
|
|
13280
|
-
|
|
13472
|
+
if (TYPE_TOKEN_RE.test(expr)) return true;
|
|
13473
|
+
if (!types || types.length === 0) return false;
|
|
13474
|
+
const argExpr = (arg.expression ?? "").trim();
|
|
13475
|
+
const varName = arg.variable ?? "";
|
|
13476
|
+
const isBareIdentifier = varName !== "" && argExpr === varName && !/[(.]/.test(argExpr);
|
|
13477
|
+
if (isBareIdentifier) {
|
|
13478
|
+
const t = resolveIdentifierType(varName, call.in_method, types);
|
|
13479
|
+
if (isBoundedClassType(t)) return true;
|
|
13480
|
+
}
|
|
13481
|
+
const isCallExpr = varName !== "" && new RegExp(`^${escapeRe(varName)}\\s*\\(`).test(argExpr);
|
|
13482
|
+
if (isCallExpr) {
|
|
13483
|
+
const t = resolveCallReturnType(varName, call.in_method, types);
|
|
13484
|
+
if (isBoundedClassType(t)) return true;
|
|
13485
|
+
}
|
|
13486
|
+
return false;
|
|
13487
|
+
}
|
|
13488
|
+
function escapeRe(s) {
|
|
13489
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13281
13490
|
}
|
|
13282
13491
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
13283
13492
|
function argIsStringLiteral(call, position) {
|
|
@@ -13438,10 +13647,13 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
13438
13647
|
}
|
|
13439
13648
|
return false;
|
|
13440
13649
|
}
|
|
13441
|
-
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
13650
|
+
function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
|
|
13651
|
+
const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
|
|
13652
|
+
(p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
|
|
13653
|
+
);
|
|
13442
13654
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
13443
13655
|
for (const call of calls) {
|
|
13444
|
-
for (const pattern of
|
|
13656
|
+
for (const pattern of patternsForLanguage) {
|
|
13445
13657
|
if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
|
|
13446
13658
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
13447
13659
|
continue;
|
|
@@ -13458,7 +13670,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13458
13670
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
13459
13671
|
continue;
|
|
13460
13672
|
}
|
|
13461
|
-
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
13673
|
+
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
13462
13674
|
continue;
|
|
13463
13675
|
}
|
|
13464
13676
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
@@ -13859,7 +14071,20 @@ function matchesAnnotation(annotations, targetAnnotation) {
|
|
|
13859
14071
|
}
|
|
13860
14072
|
return false;
|
|
13861
14073
|
}
|
|
14074
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE = /* @__PURE__ */ new Map();
|
|
14075
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
|
|
13862
14076
|
function receiverMightBeClass(receiver, className) {
|
|
14077
|
+
const key = receiver + "\0" + className;
|
|
14078
|
+
const cached = RECEIVER_MIGHT_BE_CLASS_CACHE.get(key);
|
|
14079
|
+
if (cached !== void 0) return cached;
|
|
14080
|
+
const result = receiverMightBeClassImpl(receiver, className);
|
|
14081
|
+
if (RECEIVER_MIGHT_BE_CLASS_CACHE.size >= RECEIVER_MIGHT_BE_CLASS_CACHE_CAP) {
|
|
14082
|
+
RECEIVER_MIGHT_BE_CLASS_CACHE.clear();
|
|
14083
|
+
}
|
|
14084
|
+
RECEIVER_MIGHT_BE_CLASS_CACHE.set(key, result);
|
|
14085
|
+
return result;
|
|
14086
|
+
}
|
|
14087
|
+
function receiverMightBeClassImpl(receiver, className) {
|
|
13863
14088
|
if (className.startsWith("*") && className.length > 1) {
|
|
13864
14089
|
const suffix = className.slice(1).toLowerCase();
|
|
13865
14090
|
let simpleReceiver = receiver;
|
|
@@ -14563,8 +14788,15 @@ var CodeGraph = class {
|
|
|
14563
14788
|
};
|
|
14564
14789
|
|
|
14565
14790
|
// src/analysis/dfg-walk.ts
|
|
14791
|
+
var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
|
|
14566
14792
|
function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
14567
14793
|
const maxHops = options.maxHops ?? 32;
|
|
14794
|
+
let perFile = walkBackwardDefsMemo.get(chainsByToDef);
|
|
14795
|
+
if (perFile !== void 0) {
|
|
14796
|
+
const key = `${startDefId}|${maxHops}`;
|
|
14797
|
+
const hit = perFile.get(key);
|
|
14798
|
+
if (hit !== void 0) return hit;
|
|
14799
|
+
}
|
|
14568
14800
|
const visited = /* @__PURE__ */ new Set();
|
|
14569
14801
|
const lines = /* @__PURE__ */ new Set();
|
|
14570
14802
|
let hopCapReached = false;
|
|
@@ -14596,7 +14828,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
14596
14828
|
queue.push(fromId);
|
|
14597
14829
|
}
|
|
14598
14830
|
}
|
|
14599
|
-
|
|
14831
|
+
const result = { visited, lines, hopCapReached };
|
|
14832
|
+
if (perFile === void 0) {
|
|
14833
|
+
perFile = /* @__PURE__ */ new Map();
|
|
14834
|
+
walkBackwardDefsMemo.set(chainsByToDef, perFile);
|
|
14835
|
+
}
|
|
14836
|
+
perFile.set(`${startDefId}|${maxHops}`, result);
|
|
14837
|
+
return result;
|
|
14600
14838
|
}
|
|
14601
14839
|
|
|
14602
14840
|
// src/analysis/sanitizer-index.ts
|
|
@@ -15579,7 +15817,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15579
15817
|
this.constructorParamPositions.clear();
|
|
15580
15818
|
this.safePatternFieldsCache = null;
|
|
15581
15819
|
this.isTaintedExpressionCache = null;
|
|
15582
|
-
this.
|
|
15820
|
+
const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
|
|
15583
15821
|
for (const methodName of sanitizerMethods) {
|
|
15584
15822
|
this.methodReturnsSanitized.add(methodName);
|
|
15585
15823
|
}
|
|
@@ -15587,7 +15825,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15587
15825
|
this.source,
|
|
15588
15826
|
(name2) => this.lookupSymbol(name2)
|
|
15589
15827
|
);
|
|
15590
|
-
this.analyzeMethodReturns(
|
|
15828
|
+
this.analyzeMethodReturns(prePassMethods);
|
|
15591
15829
|
this.seedPythonModuleConstants(tree.rootNode);
|
|
15592
15830
|
this.visit(tree.rootNode);
|
|
15593
15831
|
this.refineTaintFromConstants();
|
|
@@ -15657,8 +15895,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15657
15895
|
/**
|
|
15658
15896
|
* Pre-pass: Analyze all methods to detect those that always return constants or sanitized values.
|
|
15659
15897
|
*/
|
|
15660
|
-
analyzeMethodReturns(
|
|
15661
|
-
const methods = this.findAllMethods(root);
|
|
15898
|
+
analyzeMethodReturns(methods) {
|
|
15662
15899
|
for (const method of methods) {
|
|
15663
15900
|
const methodName = this.getMethodName(method);
|
|
15664
15901
|
if (!methodName) continue;
|
|
@@ -15922,11 +16159,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15922
16159
|
* Collect all class field names (instance/static variables declared at class level).
|
|
15923
16160
|
* These are variables declared directly in the class body, not inside methods.
|
|
15924
16161
|
*/
|
|
15925
|
-
|
|
16162
|
+
collectClassFieldsAndMethods(root) {
|
|
16163
|
+
const methods = [];
|
|
15926
16164
|
const stack = [root];
|
|
15927
16165
|
while (stack.length > 0) {
|
|
15928
16166
|
const n = stack.pop();
|
|
15929
16167
|
if (!n) continue;
|
|
16168
|
+
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
16169
|
+
methods.push(n);
|
|
16170
|
+
}
|
|
15930
16171
|
if (n.type === "class_body") {
|
|
15931
16172
|
for (const child of n.children) {
|
|
15932
16173
|
if (child.type === "field_declaration") {
|
|
@@ -15940,14 +16181,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15940
16181
|
}
|
|
15941
16182
|
}
|
|
15942
16183
|
}
|
|
15943
|
-
stack.push(child);
|
|
16184
|
+
if (child) stack.push(child);
|
|
15944
16185
|
}
|
|
15945
16186
|
continue;
|
|
15946
16187
|
}
|
|
15947
16188
|
for (const child of n.children) {
|
|
15948
|
-
stack.push(child);
|
|
16189
|
+
if (child) stack.push(child);
|
|
15949
16190
|
}
|
|
15950
16191
|
}
|
|
16192
|
+
return methods;
|
|
15951
16193
|
}
|
|
15952
16194
|
/**
|
|
15953
16195
|
* Sprint 9 #55 — seed the symbol table with Python module-level constant
|
|
@@ -16105,21 +16347,6 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
16105
16347
|
this.symbols.set(name2, value);
|
|
16106
16348
|
}
|
|
16107
16349
|
}
|
|
16108
|
-
findAllMethods(node) {
|
|
16109
|
-
const methods = [];
|
|
16110
|
-
const stack = [node];
|
|
16111
|
-
while (stack.length > 0) {
|
|
16112
|
-
const n = stack.pop();
|
|
16113
|
-
if (!n) continue;
|
|
16114
|
-
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
16115
|
-
methods.push(n);
|
|
16116
|
-
}
|
|
16117
|
-
for (const child of n.children) {
|
|
16118
|
-
if (child) stack.push(child);
|
|
16119
|
-
}
|
|
16120
|
-
}
|
|
16121
|
-
return methods;
|
|
16122
|
-
}
|
|
16123
16350
|
getMethodName(method) {
|
|
16124
16351
|
const nameNode = method.childForFieldName("name");
|
|
16125
16352
|
if (nameNode) {
|