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
|
@@ -7349,8 +7349,16 @@ function extractGoCallInfo(node) {
|
|
|
7349
7349
|
if (funcNode.type === "selector_expression") {
|
|
7350
7350
|
const operand = funcNode.childForFieldName("operand");
|
|
7351
7351
|
const field = funcNode.childForFieldName("field");
|
|
7352
|
-
receiver = operand ? getNodeText(operand) : null;
|
|
7353
7352
|
methodName = field ? getNodeText(field) : getNodeText(funcNode);
|
|
7353
|
+
if (operand) {
|
|
7354
|
+
const opText = getNodeText(operand);
|
|
7355
|
+
if (operand.type === "identifier") {
|
|
7356
|
+
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
7357
|
+
receiver = resolved !== null ? resolved : opText;
|
|
7358
|
+
} else {
|
|
7359
|
+
receiver = opText;
|
|
7360
|
+
}
|
|
7361
|
+
}
|
|
7354
7362
|
} else if (funcNode.type === "identifier") {
|
|
7355
7363
|
methodName = getNodeText(funcNode);
|
|
7356
7364
|
} else {
|
|
@@ -7428,6 +7436,80 @@ function findGoEnclosingFunction(node) {
|
|
|
7428
7436
|
}
|
|
7429
7437
|
return null;
|
|
7430
7438
|
}
|
|
7439
|
+
function resolveGoLocalReceiverType(operandName, callNode) {
|
|
7440
|
+
let cur = callNode.parent;
|
|
7441
|
+
while (cur) {
|
|
7442
|
+
if (cur.type === "method_declaration") {
|
|
7443
|
+
const receiver = cur.childForFieldName("receiver");
|
|
7444
|
+
if (receiver) {
|
|
7445
|
+
const t = extractGoParamTypeForName(receiver, operandName);
|
|
7446
|
+
if (t !== null) return t;
|
|
7447
|
+
}
|
|
7448
|
+
const params = cur.childForFieldName("parameters");
|
|
7449
|
+
if (params) {
|
|
7450
|
+
const t = extractGoParamTypeForName(params, operandName);
|
|
7451
|
+
if (t !== null) return t;
|
|
7452
|
+
}
|
|
7453
|
+
return null;
|
|
7454
|
+
}
|
|
7455
|
+
if (cur.type === "function_declaration" || cur.type === "func_literal") {
|
|
7456
|
+
const params = cur.childForFieldName("parameters");
|
|
7457
|
+
if (params) {
|
|
7458
|
+
const t = extractGoParamTypeForName(params, operandName);
|
|
7459
|
+
if (t !== null) return t;
|
|
7460
|
+
}
|
|
7461
|
+
return null;
|
|
7462
|
+
}
|
|
7463
|
+
cur = cur.parent;
|
|
7464
|
+
}
|
|
7465
|
+
return null;
|
|
7466
|
+
}
|
|
7467
|
+
function extractGoParamTypeForName(list, operandName) {
|
|
7468
|
+
for (let i2 = 0; i2 < list.namedChildCount; i2++) {
|
|
7469
|
+
const child = list.namedChild(i2);
|
|
7470
|
+
if (!child || child.type !== "parameter_declaration") continue;
|
|
7471
|
+
const typeNode = child.childForFieldName("type");
|
|
7472
|
+
if (!typeNode) continue;
|
|
7473
|
+
let matched = false;
|
|
7474
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
7475
|
+
const c = child.namedChild(j);
|
|
7476
|
+
if (!c) continue;
|
|
7477
|
+
if (c.type === "identifier" && getNodeText(c) === operandName) {
|
|
7478
|
+
matched = true;
|
|
7479
|
+
break;
|
|
7480
|
+
}
|
|
7481
|
+
if (c.type === "identifier_list") {
|
|
7482
|
+
for (let k = 0; k < c.namedChildCount; k++) {
|
|
7483
|
+
const id = c.namedChild(k);
|
|
7484
|
+
if (id && id.type === "identifier" && getNodeText(id) === operandName) {
|
|
7485
|
+
matched = true;
|
|
7486
|
+
break;
|
|
7487
|
+
}
|
|
7488
|
+
}
|
|
7489
|
+
if (matched) break;
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
if (!matched) continue;
|
|
7493
|
+
return extractGoTypeLastSegment(typeNode);
|
|
7494
|
+
}
|
|
7495
|
+
return null;
|
|
7496
|
+
}
|
|
7497
|
+
function extractGoTypeLastSegment(typeNode) {
|
|
7498
|
+
if (typeNode.type === "pointer_type") {
|
|
7499
|
+
const inner = typeNode.namedChild(0);
|
|
7500
|
+
return inner ? extractGoTypeLastSegment(inner) : null;
|
|
7501
|
+
}
|
|
7502
|
+
if (typeNode.type === "qualified_type") {
|
|
7503
|
+
const name2 = typeNode.childForFieldName("name");
|
|
7504
|
+
if (name2) return getNodeText(name2);
|
|
7505
|
+
const last = typeNode.namedChild(typeNode.namedChildCount - 1);
|
|
7506
|
+
return last ? getNodeText(last) : null;
|
|
7507
|
+
}
|
|
7508
|
+
if (typeNode.type === "type_identifier" || typeNode.type === "identifier") {
|
|
7509
|
+
return getNodeText(typeNode);
|
|
7510
|
+
}
|
|
7511
|
+
return null;
|
|
7512
|
+
}
|
|
7431
7513
|
|
|
7432
7514
|
// src/core/extractors/imports.ts
|
|
7433
7515
|
function detectLanguage2(tree) {
|
|
@@ -8176,10 +8258,10 @@ function buildCFG(tree, language, cache) {
|
|
|
8176
8258
|
const allEdges = [];
|
|
8177
8259
|
let blockIdCounter = 0;
|
|
8178
8260
|
if (effectiveLanguage === "bash") {
|
|
8179
|
-
return buildBashCFG(tree, blockIdCounter);
|
|
8261
|
+
return buildBashCFG(tree, blockIdCounter, cache);
|
|
8180
8262
|
}
|
|
8181
8263
|
if (effectiveLanguage === "go") {
|
|
8182
|
-
return buildGoCFG(tree, blockIdCounter);
|
|
8264
|
+
return buildGoCFG(tree, blockIdCounter, cache);
|
|
8183
8265
|
}
|
|
8184
8266
|
if (isJavaScript) {
|
|
8185
8267
|
const functions = [
|
|
@@ -8536,11 +8618,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8536
8618
|
nextId: currentId
|
|
8537
8619
|
};
|
|
8538
8620
|
}
|
|
8539
|
-
function buildBashCFG(tree, startId) {
|
|
8621
|
+
function buildBashCFG(tree, startId, cache) {
|
|
8540
8622
|
const allBlocks = [];
|
|
8541
8623
|
const allEdges = [];
|
|
8542
8624
|
let blockIdCounter = startId;
|
|
8543
|
-
const functions =
|
|
8625
|
+
const functions = getNodesFromCache(tree.rootNode, "function_definition", cache);
|
|
8544
8626
|
for (const func2 of functions) {
|
|
8545
8627
|
const body2 = func2.childForFieldName("body");
|
|
8546
8628
|
if (!body2) continue;
|
|
@@ -8657,12 +8739,12 @@ function isStatement(node, isJavaScript) {
|
|
|
8657
8739
|
]);
|
|
8658
8740
|
return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
|
|
8659
8741
|
}
|
|
8660
|
-
function buildGoCFG(tree, blockIdCounter) {
|
|
8742
|
+
function buildGoCFG(tree, blockIdCounter, cache) {
|
|
8661
8743
|
const allBlocks = [];
|
|
8662
8744
|
const allEdges = [];
|
|
8663
8745
|
const functions = [
|
|
8664
|
-
...
|
|
8665
|
-
...
|
|
8746
|
+
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
8747
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache)
|
|
8666
8748
|
];
|
|
8667
8749
|
for (const func2 of functions) {
|
|
8668
8750
|
const body2 = func2.childForFieldName("body");
|
|
@@ -10576,6 +10658,59 @@ var OPEN_REDIRECT_FRAMEWORK_SINKS = [
|
|
|
10576
10658
|
{ method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
10577
10659
|
{ method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
|
|
10578
10660
|
];
|
|
10661
|
+
var DESERIALIZATION_FRAMEWORK_SINKS = [
|
|
10662
|
+
// --- Python: stdlib + popular third-party --------------------------------
|
|
10663
|
+
// pickle: known-dangerous, any unpickle on untrusted bytes is RCE.
|
|
10664
|
+
{ method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10665
|
+
{ method: "load", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10666
|
+
// cPickle alias (Python 2 name, still around in older codebases).
|
|
10667
|
+
{ method: "loads", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10668
|
+
{ method: "load", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10669
|
+
// marshal: stdlib code-object deserializer. Loading a tainted bytestring
|
|
10670
|
+
// as a code object followed by `exec` is arbitrary-code execution.
|
|
10671
|
+
{ method: "loads", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10672
|
+
{ method: "load", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10673
|
+
// dill: pickle superset — same RCE profile.
|
|
10674
|
+
{ method: "loads", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10675
|
+
{ method: "load", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10676
|
+
// jsonpickle: JSON wrapper around pickle — trusts `py/object` marker.
|
|
10677
|
+
{ method: "decode", class: "jsonpickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
10678
|
+
// --- Go: encoding/gob + yaml.Unmarshal -----------------------------------
|
|
10679
|
+
// gob.NewDecoder(r).Decode(&v): tainted io.Reader → arbitrary Go values.
|
|
10680
|
+
{ method: "Decode", class: "Decoder", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
10681
|
+
// gopkg.in/yaml.v2 + v3 top-level function; interface{} target is unsafe.
|
|
10682
|
+
{ method: "Unmarshal", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
10683
|
+
// --- JS/TS: node-serialize ------------------------------------------------
|
|
10684
|
+
// Known-dangerous — accepts embedded IIFE that runs during deserialize.
|
|
10685
|
+
{ method: "unserialize", class: "nodeSerialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
|
|
10686
|
+
];
|
|
10687
|
+
var NOSQL_FRAMEWORK_SINKS = [
|
|
10688
|
+
// --- Python: pymongo Collection ------------------------------------------
|
|
10689
|
+
// Every filter-taking Collection method; filter arg[0] is the query dict.
|
|
10690
|
+
{ method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10691
|
+
{ method: "find_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10692
|
+
{ method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10693
|
+
{ method: "update_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
|
|
10694
|
+
{ method: "update_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
|
|
10695
|
+
{ method: "delete_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10696
|
+
{ method: "delete_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10697
|
+
{ method: "count_documents", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
10698
|
+
// --- Java: Spring Data MongoTemplate + native MongoCollection ------------
|
|
10699
|
+
{ method: "find", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10700
|
+
{ method: "findOne", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10701
|
+
{ method: "findAll", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10702
|
+
{ method: "find", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10703
|
+
{ method: "aggregate", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
10704
|
+
// --- Go: go.mongodb.org/mongo-driver Collection --------------------------
|
|
10705
|
+
// These fire once Go local-receiver type resolution lands (see
|
|
10706
|
+
// taint-matcher.ts + #240 ship 2 Go receiver work). Same gate as the
|
|
10707
|
+
// gin/fiber Ctx sinks in ship 1.
|
|
10708
|
+
{ method: "Find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
|
|
10709
|
+
{ method: "FindOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
|
|
10710
|
+
{ method: "UpdateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
|
|
10711
|
+
{ method: "UpdateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
|
|
10712
|
+
{ method: "DeleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] }
|
|
10713
|
+
];
|
|
10579
10714
|
var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
10580
10715
|
// --- Python: Django cache write -----------------------------------------
|
|
10581
10716
|
{ method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
|
|
@@ -12259,7 +12394,12 @@ var DEFAULT_SINKS = [
|
|
|
12259
12394
|
// keep the DEFAULT_SINKS literal within TypeScript's union-type
|
|
12260
12395
|
// inference complexity limit (TS2590). See ~lines 661-752.
|
|
12261
12396
|
...OPEN_REDIRECT_FRAMEWORK_SINKS,
|
|
12262
|
-
...TRUST_BOUNDARY_FRAMEWORK_SINKS
|
|
12397
|
+
...TRUST_BOUNDARY_FRAMEWORK_SINKS,
|
|
12398
|
+
// cognium-dev #240 ship 2 — extended framework sinks for
|
|
12399
|
+
// deserialization (CWE-502) and nosql_injection (CWE-943). Same
|
|
12400
|
+
// pattern: constants defined near the ship-1 blocks and spread here.
|
|
12401
|
+
...DESERIALIZATION_FRAMEWORK_SINKS,
|
|
12402
|
+
...NOSQL_FRAMEWORK_SINKS
|
|
12263
12403
|
];
|
|
12264
12404
|
var DEFAULT_SANITIZERS = [
|
|
12265
12405
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -12602,6 +12742,64 @@ function getDefaultConfig() {
|
|
|
12602
12742
|
};
|
|
12603
12743
|
}
|
|
12604
12744
|
|
|
12745
|
+
// src/analysis/arg-type-resolver.ts
|
|
12746
|
+
function findEnclosingType(callInMethod, types) {
|
|
12747
|
+
if (!callInMethod) return null;
|
|
12748
|
+
for (const t of types) {
|
|
12749
|
+
for (const m of t.methods) {
|
|
12750
|
+
if (m.name === callInMethod) return t;
|
|
12751
|
+
}
|
|
12752
|
+
}
|
|
12753
|
+
return null;
|
|
12754
|
+
}
|
|
12755
|
+
function resolveIdentifierType(name2, callInMethod, types) {
|
|
12756
|
+
if (!name2) return null;
|
|
12757
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
12758
|
+
if (enclosing) {
|
|
12759
|
+
for (const m of enclosing.methods) {
|
|
12760
|
+
if (m.name !== callInMethod) continue;
|
|
12761
|
+
for (const p of m.parameters) {
|
|
12762
|
+
if (p.name === name2 && p.type) return p.type;
|
|
12763
|
+
}
|
|
12764
|
+
}
|
|
12765
|
+
for (const f of enclosing.fields) {
|
|
12766
|
+
if (f.name === name2 && f.type) return f.type;
|
|
12767
|
+
}
|
|
12768
|
+
return null;
|
|
12769
|
+
}
|
|
12770
|
+
for (const t of types) {
|
|
12771
|
+
for (const m of t.methods) {
|
|
12772
|
+
for (const p of m.parameters) {
|
|
12773
|
+
if (p.name === name2 && p.type) return p.type;
|
|
12774
|
+
}
|
|
12775
|
+
}
|
|
12776
|
+
for (const f of t.fields) {
|
|
12777
|
+
if (f.name === name2 && f.type) return f.type;
|
|
12778
|
+
}
|
|
12779
|
+
}
|
|
12780
|
+
return null;
|
|
12781
|
+
}
|
|
12782
|
+
function resolveCallReturnType(calleeName, callInMethod, types) {
|
|
12783
|
+
if (!calleeName) return null;
|
|
12784
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
12785
|
+
if (enclosing) {
|
|
12786
|
+
for (const m of enclosing.methods) {
|
|
12787
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
12788
|
+
}
|
|
12789
|
+
}
|
|
12790
|
+
for (const t of types) {
|
|
12791
|
+
for (const m of t.methods) {
|
|
12792
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
12793
|
+
}
|
|
12794
|
+
}
|
|
12795
|
+
return null;
|
|
12796
|
+
}
|
|
12797
|
+
function isBoundedClassType(rawType) {
|
|
12798
|
+
if (!rawType) return false;
|
|
12799
|
+
const stripped = rawType.trim().replace(/^(?:java\.lang\.)+/, "");
|
|
12800
|
+
return /^Class(?:\s*<[\s\S]*>)?$/.test(stripped);
|
|
12801
|
+
}
|
|
12802
|
+
|
|
12605
12803
|
// src/analysis/taint-matcher.ts
|
|
12606
12804
|
var PYTHON_TAINTED_PATTERNS = [
|
|
12607
12805
|
{ pattern: /\brequest\.args\b/, sourceType: "http_param" },
|
|
@@ -12627,7 +12825,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
12627
12825
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
12628
12826
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
12629
12827
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
12630
|
-
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
|
|
12828
|
+
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
12631
12829
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
12632
12830
|
return { sources, sinks, sanitizers };
|
|
12633
12831
|
}
|
|
@@ -12721,11 +12919,11 @@ function attachSourceLineCode(sources, sinks, code) {
|
|
|
12721
12919
|
}
|
|
12722
12920
|
function findSources(calls, types, patterns, sourceLines, language) {
|
|
12723
12921
|
const sources = [];
|
|
12922
|
+
const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
|
|
12923
|
+
(p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
|
|
12924
|
+
);
|
|
12724
12925
|
for (const call of calls) {
|
|
12725
|
-
for (const pattern of
|
|
12726
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12727
|
-
continue;
|
|
12728
|
-
}
|
|
12926
|
+
for (const pattern of patternsForLanguage) {
|
|
12729
12927
|
if (matchesSourcePattern(call, pattern)) {
|
|
12730
12928
|
sources.push({
|
|
12731
12929
|
type: pattern.type,
|
|
@@ -12741,11 +12939,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12741
12939
|
for (const type of types) {
|
|
12742
12940
|
for (const method of type.methods) {
|
|
12743
12941
|
for (const param of method.parameters) {
|
|
12744
|
-
for (const pattern of
|
|
12942
|
+
for (const pattern of patternsForLanguage) {
|
|
12745
12943
|
if (pattern.annotation && pattern.param_tainted) {
|
|
12746
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12747
|
-
continue;
|
|
12748
|
-
}
|
|
12749
12944
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
12750
12945
|
const paramLine = param.line ?? method.start_line;
|
|
12751
12946
|
sources.push({
|
|
@@ -12764,11 +12959,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12764
12959
|
}
|
|
12765
12960
|
for (const type of types) {
|
|
12766
12961
|
for (const method of type.methods) {
|
|
12767
|
-
for (const pattern of
|
|
12962
|
+
for (const pattern of patternsForLanguage) {
|
|
12768
12963
|
if (!pattern.method_annotation) continue;
|
|
12769
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12770
|
-
continue;
|
|
12771
|
-
}
|
|
12772
12964
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
12773
12965
|
for (const param of method.parameters) {
|
|
12774
12966
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -13205,13 +13397,30 @@ function isSafeRustCommandCall(call, pattern, language) {
|
|
|
13205
13397
|
return false;
|
|
13206
13398
|
}
|
|
13207
13399
|
var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
|
|
13208
|
-
function argIsClassLiteral(call, position) {
|
|
13400
|
+
function argIsClassLiteral(call, position, types) {
|
|
13209
13401
|
const arg = call.arguments.find((a) => a.position === position);
|
|
13210
13402
|
if (!arg) return false;
|
|
13211
13403
|
const expr = (arg.literal ?? arg.expression ?? "").trim();
|
|
13212
13404
|
if (!expr) return false;
|
|
13213
13405
|
if (CLASS_LITERAL_RE.test(expr)) return true;
|
|
13214
|
-
|
|
13406
|
+
if (TYPE_TOKEN_RE.test(expr)) return true;
|
|
13407
|
+
if (!types || types.length === 0) return false;
|
|
13408
|
+
const argExpr = (arg.expression ?? "").trim();
|
|
13409
|
+
const varName = arg.variable ?? "";
|
|
13410
|
+
const isBareIdentifier = varName !== "" && argExpr === varName && !/[(.]/.test(argExpr);
|
|
13411
|
+
if (isBareIdentifier) {
|
|
13412
|
+
const t = resolveIdentifierType(varName, call.in_method, types);
|
|
13413
|
+
if (isBoundedClassType(t)) return true;
|
|
13414
|
+
}
|
|
13415
|
+
const isCallExpr = varName !== "" && new RegExp(`^${escapeRe(varName)}\\s*\\(`).test(argExpr);
|
|
13416
|
+
if (isCallExpr) {
|
|
13417
|
+
const t = resolveCallReturnType(varName, call.in_method, types);
|
|
13418
|
+
if (isBoundedClassType(t)) return true;
|
|
13419
|
+
}
|
|
13420
|
+
return false;
|
|
13421
|
+
}
|
|
13422
|
+
function escapeRe(s) {
|
|
13423
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13215
13424
|
}
|
|
13216
13425
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
13217
13426
|
function argIsStringLiteral(call, position) {
|
|
@@ -13372,10 +13581,13 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
13372
13581
|
}
|
|
13373
13582
|
return false;
|
|
13374
13583
|
}
|
|
13375
|
-
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
13584
|
+
function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
|
|
13585
|
+
const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
|
|
13586
|
+
(p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
|
|
13587
|
+
);
|
|
13376
13588
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
13377
13589
|
for (const call of calls) {
|
|
13378
|
-
for (const pattern of
|
|
13590
|
+
for (const pattern of patternsForLanguage) {
|
|
13379
13591
|
if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
|
|
13380
13592
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
13381
13593
|
continue;
|
|
@@ -13392,7 +13604,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
13392
13604
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
13393
13605
|
continue;
|
|
13394
13606
|
}
|
|
13395
|
-
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
13607
|
+
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
13396
13608
|
continue;
|
|
13397
13609
|
}
|
|
13398
13610
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
@@ -13793,7 +14005,20 @@ function matchesAnnotation(annotations, targetAnnotation) {
|
|
|
13793
14005
|
}
|
|
13794
14006
|
return false;
|
|
13795
14007
|
}
|
|
14008
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE = /* @__PURE__ */ new Map();
|
|
14009
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
|
|
13796
14010
|
function receiverMightBeClass(receiver, className) {
|
|
14011
|
+
const key = receiver + "\0" + className;
|
|
14012
|
+
const cached = RECEIVER_MIGHT_BE_CLASS_CACHE.get(key);
|
|
14013
|
+
if (cached !== void 0) return cached;
|
|
14014
|
+
const result = receiverMightBeClassImpl(receiver, className);
|
|
14015
|
+
if (RECEIVER_MIGHT_BE_CLASS_CACHE.size >= RECEIVER_MIGHT_BE_CLASS_CACHE_CAP) {
|
|
14016
|
+
RECEIVER_MIGHT_BE_CLASS_CACHE.clear();
|
|
14017
|
+
}
|
|
14018
|
+
RECEIVER_MIGHT_BE_CLASS_CACHE.set(key, result);
|
|
14019
|
+
return result;
|
|
14020
|
+
}
|
|
14021
|
+
function receiverMightBeClassImpl(receiver, className) {
|
|
13797
14022
|
if (className.startsWith("*") && className.length > 1) {
|
|
13798
14023
|
const suffix = className.slice(1).toLowerCase();
|
|
13799
14024
|
let simpleReceiver = receiver;
|
|
@@ -14497,8 +14722,15 @@ var CodeGraph = class {
|
|
|
14497
14722
|
};
|
|
14498
14723
|
|
|
14499
14724
|
// src/analysis/dfg-walk.ts
|
|
14725
|
+
var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
|
|
14500
14726
|
function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
14501
14727
|
const maxHops = options.maxHops ?? 32;
|
|
14728
|
+
let perFile = walkBackwardDefsMemo.get(chainsByToDef);
|
|
14729
|
+
if (perFile !== void 0) {
|
|
14730
|
+
const key = `${startDefId}|${maxHops}`;
|
|
14731
|
+
const hit = perFile.get(key);
|
|
14732
|
+
if (hit !== void 0) return hit;
|
|
14733
|
+
}
|
|
14502
14734
|
const visited = /* @__PURE__ */ new Set();
|
|
14503
14735
|
const lines = /* @__PURE__ */ new Set();
|
|
14504
14736
|
let hopCapReached = false;
|
|
@@ -14530,7 +14762,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
14530
14762
|
queue.push(fromId);
|
|
14531
14763
|
}
|
|
14532
14764
|
}
|
|
14533
|
-
|
|
14765
|
+
const result = { visited, lines, hopCapReached };
|
|
14766
|
+
if (perFile === void 0) {
|
|
14767
|
+
perFile = /* @__PURE__ */ new Map();
|
|
14768
|
+
walkBackwardDefsMemo.set(chainsByToDef, perFile);
|
|
14769
|
+
}
|
|
14770
|
+
perFile.set(`${startDefId}|${maxHops}`, result);
|
|
14771
|
+
return result;
|
|
14534
14772
|
}
|
|
14535
14773
|
|
|
14536
14774
|
// src/analysis/sanitizer-index.ts
|
|
@@ -15513,7 +15751,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15513
15751
|
this.constructorParamPositions.clear();
|
|
15514
15752
|
this.safePatternFieldsCache = null;
|
|
15515
15753
|
this.isTaintedExpressionCache = null;
|
|
15516
|
-
this.
|
|
15754
|
+
const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
|
|
15517
15755
|
for (const methodName of sanitizerMethods) {
|
|
15518
15756
|
this.methodReturnsSanitized.add(methodName);
|
|
15519
15757
|
}
|
|
@@ -15521,7 +15759,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15521
15759
|
this.source,
|
|
15522
15760
|
(name2) => this.lookupSymbol(name2)
|
|
15523
15761
|
);
|
|
15524
|
-
this.analyzeMethodReturns(
|
|
15762
|
+
this.analyzeMethodReturns(prePassMethods);
|
|
15525
15763
|
this.seedPythonModuleConstants(tree.rootNode);
|
|
15526
15764
|
this.visit(tree.rootNode);
|
|
15527
15765
|
this.refineTaintFromConstants();
|
|
@@ -15591,8 +15829,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15591
15829
|
/**
|
|
15592
15830
|
* Pre-pass: Analyze all methods to detect those that always return constants or sanitized values.
|
|
15593
15831
|
*/
|
|
15594
|
-
analyzeMethodReturns(
|
|
15595
|
-
const methods = this.findAllMethods(root);
|
|
15832
|
+
analyzeMethodReturns(methods) {
|
|
15596
15833
|
for (const method of methods) {
|
|
15597
15834
|
const methodName = this.getMethodName(method);
|
|
15598
15835
|
if (!methodName) continue;
|
|
@@ -15856,11 +16093,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15856
16093
|
* Collect all class field names (instance/static variables declared at class level).
|
|
15857
16094
|
* These are variables declared directly in the class body, not inside methods.
|
|
15858
16095
|
*/
|
|
15859
|
-
|
|
16096
|
+
collectClassFieldsAndMethods(root) {
|
|
16097
|
+
const methods = [];
|
|
15860
16098
|
const stack = [root];
|
|
15861
16099
|
while (stack.length > 0) {
|
|
15862
16100
|
const n = stack.pop();
|
|
15863
16101
|
if (!n) continue;
|
|
16102
|
+
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
16103
|
+
methods.push(n);
|
|
16104
|
+
}
|
|
15864
16105
|
if (n.type === "class_body") {
|
|
15865
16106
|
for (const child of n.children) {
|
|
15866
16107
|
if (child.type === "field_declaration") {
|
|
@@ -15874,14 +16115,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15874
16115
|
}
|
|
15875
16116
|
}
|
|
15876
16117
|
}
|
|
15877
|
-
stack.push(child);
|
|
16118
|
+
if (child) stack.push(child);
|
|
15878
16119
|
}
|
|
15879
16120
|
continue;
|
|
15880
16121
|
}
|
|
15881
16122
|
for (const child of n.children) {
|
|
15882
|
-
stack.push(child);
|
|
16123
|
+
if (child) stack.push(child);
|
|
15883
16124
|
}
|
|
15884
16125
|
}
|
|
16126
|
+
return methods;
|
|
15885
16127
|
}
|
|
15886
16128
|
/**
|
|
15887
16129
|
* Sprint 9 #55 — seed the symbol table with Python module-level constant
|
|
@@ -16039,21 +16281,6 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
16039
16281
|
this.symbols.set(name2, value);
|
|
16040
16282
|
}
|
|
16041
16283
|
}
|
|
16042
|
-
findAllMethods(node) {
|
|
16043
|
-
const methods = [];
|
|
16044
|
-
const stack = [node];
|
|
16045
|
-
while (stack.length > 0) {
|
|
16046
|
-
const n = stack.pop();
|
|
16047
|
-
if (!n) continue;
|
|
16048
|
-
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
16049
|
-
methods.push(n);
|
|
16050
|
-
}
|
|
16051
|
-
for (const child of n.children) {
|
|
16052
|
-
if (child) stack.push(child);
|
|
16053
|
-
}
|
|
16054
|
-
}
|
|
16055
|
-
return methods;
|
|
16056
|
-
}
|
|
16057
16284
|
getMethodName(method) {
|
|
16058
16285
|
const nameNode = method.childForFieldName("name");
|
|
16059
16286
|
if (nameNode) {
|
|
@@ -2113,8 +2113,27 @@ function extractGoCallInfo(node) {
|
|
|
2113
2113
|
// pkg.Function() or obj.Method()
|
|
2114
2114
|
const operand = funcNode.childForFieldName('operand');
|
|
2115
2115
|
const field = funcNode.childForFieldName('field');
|
|
2116
|
-
receiver = operand ? getNodeText(operand) : null;
|
|
2117
2116
|
methodName = field ? getNodeText(field) : getNodeText(funcNode);
|
|
2117
|
+
// cognium-dev #240 ship 2 — Go local-receiver type resolution.
|
|
2118
|
+
// When the operand is a bare identifier and matches a method
|
|
2119
|
+
// receiver or function parameter in the enclosing declaration,
|
|
2120
|
+
// rewrite receiver to the type's last identifier segment. This
|
|
2121
|
+
// unblocks sinks defined with `class: 'Context'` (gin/echo/fiber),
|
|
2122
|
+
// `class: 'Ctx'` (fiber), and `class: 'Collection'` (mongo-driver)
|
|
2123
|
+
// when the source shape is `c.Redirect(...)` / `c.SetCookie(...)` /
|
|
2124
|
+
// `col.Find(...)`. Package-qualified calls like `fmt.Sprintf(...)`
|
|
2125
|
+
// fall through to the original operand text (fmt is not a
|
|
2126
|
+
// parameter / receiver, resolver returns null).
|
|
2127
|
+
if (operand) {
|
|
2128
|
+
const opText = getNodeText(operand);
|
|
2129
|
+
if (operand.type === 'identifier') {
|
|
2130
|
+
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
2131
|
+
receiver = resolved !== null ? resolved : opText;
|
|
2132
|
+
}
|
|
2133
|
+
else {
|
|
2134
|
+
receiver = opText;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2118
2137
|
}
|
|
2119
2138
|
else if (funcNode.type === 'identifier') {
|
|
2120
2139
|
// Plain function call: funcName()
|
|
@@ -2213,4 +2232,115 @@ function findGoEnclosingFunction(node) {
|
|
|
2213
2232
|
}
|
|
2214
2233
|
return null;
|
|
2215
2234
|
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Resolve a Go local-variable operand (bare identifier receiver in a
|
|
2237
|
+
* `selector_expression`) to its declared type's last identifier segment.
|
|
2238
|
+
*
|
|
2239
|
+
* Handles three enclosing-declaration shapes:
|
|
2240
|
+
* 1. Method receiver: `func (c *gin.Context) H() { c.Redirect(...) }`
|
|
2241
|
+
* 2. Function param: `func H(c *gin.Context, url string) { c.Redirect(...) }`
|
|
2242
|
+
* 3. Multi-name param: `func H(a, b *Foo) { a.Bar() }` (identifier_list)
|
|
2243
|
+
*
|
|
2244
|
+
* Returns `null` when the operand does not match any receiver or
|
|
2245
|
+
* parameter in the nearest enclosing `method_declaration` /
|
|
2246
|
+
* `function_declaration` / `func_literal` — the caller then falls back
|
|
2247
|
+
* to the operand text so package-qualified calls like `fmt.Sprintf(...)`
|
|
2248
|
+
* continue to work.
|
|
2249
|
+
*
|
|
2250
|
+
* cognium-dev #240 ship 2.
|
|
2251
|
+
*/
|
|
2252
|
+
function resolveGoLocalReceiverType(operandName, callNode) {
|
|
2253
|
+
let cur = callNode.parent;
|
|
2254
|
+
while (cur) {
|
|
2255
|
+
if (cur.type === 'method_declaration') {
|
|
2256
|
+
const receiver = cur.childForFieldName('receiver');
|
|
2257
|
+
if (receiver) {
|
|
2258
|
+
const t = extractGoParamTypeForName(receiver, operandName);
|
|
2259
|
+
if (t !== null)
|
|
2260
|
+
return t;
|
|
2261
|
+
}
|
|
2262
|
+
const params = cur.childForFieldName('parameters');
|
|
2263
|
+
if (params) {
|
|
2264
|
+
const t = extractGoParamTypeForName(params, operandName);
|
|
2265
|
+
if (t !== null)
|
|
2266
|
+
return t;
|
|
2267
|
+
}
|
|
2268
|
+
return null;
|
|
2269
|
+
}
|
|
2270
|
+
if (cur.type === 'function_declaration' || cur.type === 'func_literal') {
|
|
2271
|
+
const params = cur.childForFieldName('parameters');
|
|
2272
|
+
if (params) {
|
|
2273
|
+
const t = extractGoParamTypeForName(params, operandName);
|
|
2274
|
+
if (t !== null)
|
|
2275
|
+
return t;
|
|
2276
|
+
}
|
|
2277
|
+
return null;
|
|
2278
|
+
}
|
|
2279
|
+
cur = cur.parent;
|
|
2280
|
+
}
|
|
2281
|
+
return null;
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Scan a Go `parameter_list` for a `parameter_declaration` naming
|
|
2285
|
+
* `operandName` (directly or inside an `identifier_list`) and return
|
|
2286
|
+
* its type's last identifier segment. Returns `null` on miss.
|
|
2287
|
+
*/
|
|
2288
|
+
function extractGoParamTypeForName(list, operandName) {
|
|
2289
|
+
for (let i = 0; i < list.namedChildCount; i++) {
|
|
2290
|
+
const child = list.namedChild(i);
|
|
2291
|
+
if (!child || child.type !== 'parameter_declaration')
|
|
2292
|
+
continue;
|
|
2293
|
+
const typeNode = child.childForFieldName('type');
|
|
2294
|
+
if (!typeNode)
|
|
2295
|
+
continue;
|
|
2296
|
+
let matched = false;
|
|
2297
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
2298
|
+
const c = child.namedChild(j);
|
|
2299
|
+
if (!c)
|
|
2300
|
+
continue;
|
|
2301
|
+
if (c.type === 'identifier' && getNodeText(c) === operandName) {
|
|
2302
|
+
matched = true;
|
|
2303
|
+
break;
|
|
2304
|
+
}
|
|
2305
|
+
if (c.type === 'identifier_list') {
|
|
2306
|
+
for (let k = 0; k < c.namedChildCount; k++) {
|
|
2307
|
+
const id = c.namedChild(k);
|
|
2308
|
+
if (id && id.type === 'identifier' && getNodeText(id) === operandName) {
|
|
2309
|
+
matched = true;
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
if (matched)
|
|
2314
|
+
break;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
if (!matched)
|
|
2318
|
+
continue;
|
|
2319
|
+
return extractGoTypeLastSegment(typeNode);
|
|
2320
|
+
}
|
|
2321
|
+
return null;
|
|
2322
|
+
}
|
|
2323
|
+
/**
|
|
2324
|
+
* Return the last identifier segment of a Go type expression:
|
|
2325
|
+
* `*gin.Context` → `"Context"`; `gin.Context` → `"Context"`;
|
|
2326
|
+
* `*Context` → `"Context"`; `Context` → `"Context"`;
|
|
2327
|
+
* `[]byte` → `null` (structured types not yet handled).
|
|
2328
|
+
*/
|
|
2329
|
+
function extractGoTypeLastSegment(typeNode) {
|
|
2330
|
+
if (typeNode.type === 'pointer_type') {
|
|
2331
|
+
const inner = typeNode.namedChild(0);
|
|
2332
|
+
return inner ? extractGoTypeLastSegment(inner) : null;
|
|
2333
|
+
}
|
|
2334
|
+
if (typeNode.type === 'qualified_type') {
|
|
2335
|
+
const name = typeNode.childForFieldName('name');
|
|
2336
|
+
if (name)
|
|
2337
|
+
return getNodeText(name);
|
|
2338
|
+
const last = typeNode.namedChild(typeNode.namedChildCount - 1);
|
|
2339
|
+
return last ? getNodeText(last) : null;
|
|
2340
|
+
}
|
|
2341
|
+
if (typeNode.type === 'type_identifier' || typeNode.type === 'identifier') {
|
|
2342
|
+
return getNodeText(typeNode);
|
|
2343
|
+
}
|
|
2344
|
+
return null;
|
|
2345
|
+
}
|
|
2216
2346
|
//# sourceMappingURL=calls.js.map
|