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
|
@@ -7369,8 +7369,16 @@ function extractGoCallInfo(node) {
|
|
|
7369
7369
|
if (funcNode.type === "selector_expression") {
|
|
7370
7370
|
const operand = funcNode.childForFieldName("operand");
|
|
7371
7371
|
const field = funcNode.childForFieldName("field");
|
|
7372
|
-
receiver = operand ? getNodeText(operand) : null;
|
|
7373
7372
|
methodName = field ? getNodeText(field) : getNodeText(funcNode);
|
|
7373
|
+
if (operand) {
|
|
7374
|
+
const opText = getNodeText(operand);
|
|
7375
|
+
if (operand.type === "identifier") {
|
|
7376
|
+
const resolved = resolveGoLocalReceiverType(opText, node);
|
|
7377
|
+
receiver = resolved !== null ? resolved : opText;
|
|
7378
|
+
} else {
|
|
7379
|
+
receiver = opText;
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7374
7382
|
} else if (funcNode.type === "identifier") {
|
|
7375
7383
|
methodName = getNodeText(funcNode);
|
|
7376
7384
|
} else {
|
|
@@ -7448,6 +7456,80 @@ function findGoEnclosingFunction(node) {
|
|
|
7448
7456
|
}
|
|
7449
7457
|
return null;
|
|
7450
7458
|
}
|
|
7459
|
+
function resolveGoLocalReceiverType(operandName, callNode) {
|
|
7460
|
+
let cur = callNode.parent;
|
|
7461
|
+
while (cur) {
|
|
7462
|
+
if (cur.type === "method_declaration") {
|
|
7463
|
+
const receiver = cur.childForFieldName("receiver");
|
|
7464
|
+
if (receiver) {
|
|
7465
|
+
const t = extractGoParamTypeForName(receiver, operandName);
|
|
7466
|
+
if (t !== null) return t;
|
|
7467
|
+
}
|
|
7468
|
+
const params = cur.childForFieldName("parameters");
|
|
7469
|
+
if (params) {
|
|
7470
|
+
const t = extractGoParamTypeForName(params, operandName);
|
|
7471
|
+
if (t !== null) return t;
|
|
7472
|
+
}
|
|
7473
|
+
return null;
|
|
7474
|
+
}
|
|
7475
|
+
if (cur.type === "function_declaration" || cur.type === "func_literal") {
|
|
7476
|
+
const params = cur.childForFieldName("parameters");
|
|
7477
|
+
if (params) {
|
|
7478
|
+
const t = extractGoParamTypeForName(params, operandName);
|
|
7479
|
+
if (t !== null) return t;
|
|
7480
|
+
}
|
|
7481
|
+
return null;
|
|
7482
|
+
}
|
|
7483
|
+
cur = cur.parent;
|
|
7484
|
+
}
|
|
7485
|
+
return null;
|
|
7486
|
+
}
|
|
7487
|
+
function extractGoParamTypeForName(list, operandName) {
|
|
7488
|
+
for (let i2 = 0; i2 < list.namedChildCount; i2++) {
|
|
7489
|
+
const child = list.namedChild(i2);
|
|
7490
|
+
if (!child || child.type !== "parameter_declaration") continue;
|
|
7491
|
+
const typeNode = child.childForFieldName("type");
|
|
7492
|
+
if (!typeNode) continue;
|
|
7493
|
+
let matched = false;
|
|
7494
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
7495
|
+
const c = child.namedChild(j);
|
|
7496
|
+
if (!c) continue;
|
|
7497
|
+
if (c.type === "identifier" && getNodeText(c) === operandName) {
|
|
7498
|
+
matched = true;
|
|
7499
|
+
break;
|
|
7500
|
+
}
|
|
7501
|
+
if (c.type === "identifier_list") {
|
|
7502
|
+
for (let k = 0; k < c.namedChildCount; k++) {
|
|
7503
|
+
const id = c.namedChild(k);
|
|
7504
|
+
if (id && id.type === "identifier" && getNodeText(id) === operandName) {
|
|
7505
|
+
matched = true;
|
|
7506
|
+
break;
|
|
7507
|
+
}
|
|
7508
|
+
}
|
|
7509
|
+
if (matched) break;
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
if (!matched) continue;
|
|
7513
|
+
return extractGoTypeLastSegment(typeNode);
|
|
7514
|
+
}
|
|
7515
|
+
return null;
|
|
7516
|
+
}
|
|
7517
|
+
function extractGoTypeLastSegment(typeNode) {
|
|
7518
|
+
if (typeNode.type === "pointer_type") {
|
|
7519
|
+
const inner = typeNode.namedChild(0);
|
|
7520
|
+
return inner ? extractGoTypeLastSegment(inner) : null;
|
|
7521
|
+
}
|
|
7522
|
+
if (typeNode.type === "qualified_type") {
|
|
7523
|
+
const name2 = typeNode.childForFieldName("name");
|
|
7524
|
+
if (name2) return getNodeText(name2);
|
|
7525
|
+
const last = typeNode.namedChild(typeNode.namedChildCount - 1);
|
|
7526
|
+
return last ? getNodeText(last) : null;
|
|
7527
|
+
}
|
|
7528
|
+
if (typeNode.type === "type_identifier" || typeNode.type === "identifier") {
|
|
7529
|
+
return getNodeText(typeNode);
|
|
7530
|
+
}
|
|
7531
|
+
return null;
|
|
7532
|
+
}
|
|
7451
7533
|
|
|
7452
7534
|
// src/core/extractors/imports.ts
|
|
7453
7535
|
function detectLanguage2(tree) {
|
|
@@ -8196,10 +8278,10 @@ function buildCFG(tree, language, cache) {
|
|
|
8196
8278
|
const allEdges = [];
|
|
8197
8279
|
let blockIdCounter = 0;
|
|
8198
8280
|
if (effectiveLanguage === "bash") {
|
|
8199
|
-
return buildBashCFG(tree, blockIdCounter);
|
|
8281
|
+
return buildBashCFG(tree, blockIdCounter, cache);
|
|
8200
8282
|
}
|
|
8201
8283
|
if (effectiveLanguage === "go") {
|
|
8202
|
-
return buildGoCFG(tree, blockIdCounter);
|
|
8284
|
+
return buildGoCFG(tree, blockIdCounter, cache);
|
|
8203
8285
|
}
|
|
8204
8286
|
if (isJavaScript) {
|
|
8205
8287
|
const functions = [
|
|
@@ -8556,11 +8638,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8556
8638
|
nextId: currentId
|
|
8557
8639
|
};
|
|
8558
8640
|
}
|
|
8559
|
-
function buildBashCFG(tree, startId) {
|
|
8641
|
+
function buildBashCFG(tree, startId, cache) {
|
|
8560
8642
|
const allBlocks = [];
|
|
8561
8643
|
const allEdges = [];
|
|
8562
8644
|
let blockIdCounter = startId;
|
|
8563
|
-
const functions =
|
|
8645
|
+
const functions = getNodesFromCache(tree.rootNode, "function_definition", cache);
|
|
8564
8646
|
for (const func2 of functions) {
|
|
8565
8647
|
const body2 = func2.childForFieldName("body");
|
|
8566
8648
|
if (!body2) continue;
|
|
@@ -8677,12 +8759,12 @@ function isStatement(node, isJavaScript) {
|
|
|
8677
8759
|
]);
|
|
8678
8760
|
return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
|
|
8679
8761
|
}
|
|
8680
|
-
function buildGoCFG(tree, blockIdCounter) {
|
|
8762
|
+
function buildGoCFG(tree, blockIdCounter, cache) {
|
|
8681
8763
|
const allBlocks = [];
|
|
8682
8764
|
const allEdges = [];
|
|
8683
8765
|
const functions = [
|
|
8684
|
-
...
|
|
8685
|
-
...
|
|
8766
|
+
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
8767
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache)
|
|
8686
8768
|
];
|
|
8687
8769
|
for (const func2 of functions) {
|
|
8688
8770
|
const body2 = func2.childForFieldName("body");
|
|
@@ -11247,6 +11329,59 @@ var OPEN_REDIRECT_FRAMEWORK_SINKS = [
|
|
|
11247
11329
|
{ method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
|
|
11248
11330
|
{ method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
|
|
11249
11331
|
];
|
|
11332
|
+
var DESERIALIZATION_FRAMEWORK_SINKS = [
|
|
11333
|
+
// --- Python: stdlib + popular third-party --------------------------------
|
|
11334
|
+
// pickle: known-dangerous, any unpickle on untrusted bytes is RCE.
|
|
11335
|
+
{ method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11336
|
+
{ method: "load", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11337
|
+
// cPickle alias (Python 2 name, still around in older codebases).
|
|
11338
|
+
{ method: "loads", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11339
|
+
{ method: "load", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11340
|
+
// marshal: stdlib code-object deserializer. Loading a tainted bytestring
|
|
11341
|
+
// as a code object followed by `exec` is arbitrary-code execution.
|
|
11342
|
+
{ method: "loads", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11343
|
+
{ method: "load", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11344
|
+
// dill: pickle superset — same RCE profile.
|
|
11345
|
+
{ method: "loads", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11346
|
+
{ method: "load", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11347
|
+
// jsonpickle: JSON wrapper around pickle — trusts `py/object` marker.
|
|
11348
|
+
{ method: "decode", class: "jsonpickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
|
|
11349
|
+
// --- Go: encoding/gob + yaml.Unmarshal -----------------------------------
|
|
11350
|
+
// gob.NewDecoder(r).Decode(&v): tainted io.Reader → arbitrary Go values.
|
|
11351
|
+
{ method: "Decode", class: "Decoder", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
11352
|
+
// gopkg.in/yaml.v2 + v3 top-level function; interface{} target is unsafe.
|
|
11353
|
+
{ method: "Unmarshal", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
|
|
11354
|
+
// --- JS/TS: node-serialize ------------------------------------------------
|
|
11355
|
+
// Known-dangerous — accepts embedded IIFE that runs during deserialize.
|
|
11356
|
+
{ method: "unserialize", class: "nodeSerialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
|
|
11357
|
+
];
|
|
11358
|
+
var NOSQL_FRAMEWORK_SINKS = [
|
|
11359
|
+
// --- Python: pymongo Collection ------------------------------------------
|
|
11360
|
+
// Every filter-taking Collection method; filter arg[0] is the query dict.
|
|
11361
|
+
{ method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11362
|
+
{ method: "find_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11363
|
+
{ method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11364
|
+
{ method: "update_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
|
|
11365
|
+
{ method: "update_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
|
|
11366
|
+
{ method: "delete_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11367
|
+
{ method: "delete_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11368
|
+
{ method: "count_documents", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
|
|
11369
|
+
// --- Java: Spring Data MongoTemplate + native MongoCollection ------------
|
|
11370
|
+
{ method: "find", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
11371
|
+
{ method: "findOne", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
11372
|
+
{ method: "findAll", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
11373
|
+
{ method: "find", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
11374
|
+
{ method: "aggregate", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
|
|
11375
|
+
// --- Go: go.mongodb.org/mongo-driver Collection --------------------------
|
|
11376
|
+
// These fire once Go local-receiver type resolution lands (see
|
|
11377
|
+
// taint-matcher.ts + #240 ship 2 Go receiver work). Same gate as the
|
|
11378
|
+
// gin/fiber Ctx sinks in ship 1.
|
|
11379
|
+
{ method: "Find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
|
|
11380
|
+
{ method: "FindOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
|
|
11381
|
+
{ method: "UpdateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
|
|
11382
|
+
{ method: "UpdateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
|
|
11383
|
+
{ method: "DeleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] }
|
|
11384
|
+
];
|
|
11250
11385
|
var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
|
|
11251
11386
|
// --- Python: Django cache write -----------------------------------------
|
|
11252
11387
|
{ method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
|
|
@@ -12930,7 +13065,12 @@ var DEFAULT_SINKS = [
|
|
|
12930
13065
|
// keep the DEFAULT_SINKS literal within TypeScript's union-type
|
|
12931
13066
|
// inference complexity limit (TS2590). See ~lines 661-752.
|
|
12932
13067
|
...OPEN_REDIRECT_FRAMEWORK_SINKS,
|
|
12933
|
-
...TRUST_BOUNDARY_FRAMEWORK_SINKS
|
|
13068
|
+
...TRUST_BOUNDARY_FRAMEWORK_SINKS,
|
|
13069
|
+
// cognium-dev #240 ship 2 — extended framework sinks for
|
|
13070
|
+
// deserialization (CWE-502) and nosql_injection (CWE-943). Same
|
|
13071
|
+
// pattern: constants defined near the ship-1 blocks and spread here.
|
|
13072
|
+
...DESERIALIZATION_FRAMEWORK_SINKS,
|
|
13073
|
+
...NOSQL_FRAMEWORK_SINKS
|
|
12934
13074
|
];
|
|
12935
13075
|
var DEFAULT_SANITIZERS = [
|
|
12936
13076
|
// SQL Injection - proper parameter binding sanitizes input
|
|
@@ -13360,6 +13500,72 @@ var DEFAULT_HEADER_RULES = [
|
|
|
13360
13500
|
}
|
|
13361
13501
|
];
|
|
13362
13502
|
|
|
13503
|
+
// src/analysis/arg-type-resolver.ts
|
|
13504
|
+
function findEnclosingType(callInMethod, types) {
|
|
13505
|
+
if (!callInMethod) return null;
|
|
13506
|
+
for (const t of types) {
|
|
13507
|
+
for (const m of t.methods) {
|
|
13508
|
+
if (m.name === callInMethod) return t;
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
return null;
|
|
13512
|
+
}
|
|
13513
|
+
function resolveIdentifierType(name2, callInMethod, types) {
|
|
13514
|
+
if (!name2) return null;
|
|
13515
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
13516
|
+
if (enclosing) {
|
|
13517
|
+
for (const m of enclosing.methods) {
|
|
13518
|
+
if (m.name !== callInMethod) continue;
|
|
13519
|
+
for (const p of m.parameters) {
|
|
13520
|
+
if (p.name === name2 && p.type) return p.type;
|
|
13521
|
+
}
|
|
13522
|
+
}
|
|
13523
|
+
for (const f of enclosing.fields) {
|
|
13524
|
+
if (f.name === name2 && f.type) return f.type;
|
|
13525
|
+
}
|
|
13526
|
+
return null;
|
|
13527
|
+
}
|
|
13528
|
+
for (const t of types) {
|
|
13529
|
+
for (const m of t.methods) {
|
|
13530
|
+
for (const p of m.parameters) {
|
|
13531
|
+
if (p.name === name2 && p.type) return p.type;
|
|
13532
|
+
}
|
|
13533
|
+
}
|
|
13534
|
+
for (const f of t.fields) {
|
|
13535
|
+
if (f.name === name2 && f.type) return f.type;
|
|
13536
|
+
}
|
|
13537
|
+
}
|
|
13538
|
+
return null;
|
|
13539
|
+
}
|
|
13540
|
+
function resolveCallReturnType(calleeName, callInMethod, types) {
|
|
13541
|
+
if (!calleeName) return null;
|
|
13542
|
+
const enclosing = findEnclosingType(callInMethod, types);
|
|
13543
|
+
if (enclosing) {
|
|
13544
|
+
for (const m of enclosing.methods) {
|
|
13545
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
13546
|
+
}
|
|
13547
|
+
}
|
|
13548
|
+
for (const t of types) {
|
|
13549
|
+
for (const m of t.methods) {
|
|
13550
|
+
if (m.name === calleeName && m.return_type) return m.return_type;
|
|
13551
|
+
}
|
|
13552
|
+
}
|
|
13553
|
+
return null;
|
|
13554
|
+
}
|
|
13555
|
+
function isBoundedClassType(rawType) {
|
|
13556
|
+
if (!rawType) return false;
|
|
13557
|
+
const stripped = rawType.trim().replace(/^(?:java\.lang\.)+/, "");
|
|
13558
|
+
return /^Class(?:\s*<[\s\S]*>)?$/.test(stripped);
|
|
13559
|
+
}
|
|
13560
|
+
function isArgvContainerType(rawType) {
|
|
13561
|
+
if (!rawType) return false;
|
|
13562
|
+
const s = rawType.trim().replace(/^(?:java\.util\.|java\.lang\.)+/, "");
|
|
13563
|
+
if (/^String\s*(?:\[\s*\]|\.\.\.)$/.test(s)) return true;
|
|
13564
|
+
return /^(?:List|ArrayList|LinkedList|Collection|Iterable|Deque|Queue)\s*<\s*(?:String|CharSequence|java\.lang\.String)\b/.test(
|
|
13565
|
+
s
|
|
13566
|
+
);
|
|
13567
|
+
}
|
|
13568
|
+
|
|
13363
13569
|
// src/analysis/taint-matcher.ts
|
|
13364
13570
|
var PYTHON_TAINTED_PATTERNS = [
|
|
13365
13571
|
{ pattern: /\brequest\.args\b/, sourceType: "http_param" },
|
|
@@ -13385,7 +13591,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
|
|
|
13385
13591
|
const sources = findSources(calls, types, config.sources, sourceLines, language);
|
|
13386
13592
|
let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
|
|
13387
13593
|
sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
|
|
13388
|
-
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
|
|
13594
|
+
const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
|
|
13389
13595
|
const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
|
|
13390
13596
|
return { sources, sinks, sanitizers };
|
|
13391
13597
|
}
|
|
@@ -13479,11 +13685,11 @@ function attachSourceLineCode(sources, sinks, code) {
|
|
|
13479
13685
|
}
|
|
13480
13686
|
function findSources(calls, types, patterns, sourceLines, language) {
|
|
13481
13687
|
const sources = [];
|
|
13688
|
+
const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
|
|
13689
|
+
(p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
|
|
13690
|
+
);
|
|
13482
13691
|
for (const call of calls) {
|
|
13483
|
-
for (const pattern of
|
|
13484
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13485
|
-
continue;
|
|
13486
|
-
}
|
|
13692
|
+
for (const pattern of patternsForLanguage) {
|
|
13487
13693
|
if (matchesSourcePattern(call, pattern)) {
|
|
13488
13694
|
sources.push({
|
|
13489
13695
|
type: pattern.type,
|
|
@@ -13499,11 +13705,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13499
13705
|
for (const type of types) {
|
|
13500
13706
|
for (const method of type.methods) {
|
|
13501
13707
|
for (const param of method.parameters) {
|
|
13502
|
-
for (const pattern of
|
|
13708
|
+
for (const pattern of patternsForLanguage) {
|
|
13503
13709
|
if (pattern.annotation && pattern.param_tainted) {
|
|
13504
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13505
|
-
continue;
|
|
13506
|
-
}
|
|
13507
13710
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
13508
13711
|
const paramLine = param.line ?? method.start_line;
|
|
13509
13712
|
sources.push({
|
|
@@ -13522,11 +13725,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13522
13725
|
}
|
|
13523
13726
|
for (const type of types) {
|
|
13524
13727
|
for (const method of type.methods) {
|
|
13525
|
-
for (const pattern of
|
|
13728
|
+
for (const pattern of patternsForLanguage) {
|
|
13526
13729
|
if (!pattern.method_annotation) continue;
|
|
13527
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13528
|
-
continue;
|
|
13529
|
-
}
|
|
13530
13730
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
13531
13731
|
for (const param of method.parameters) {
|
|
13532
13732
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -13963,13 +14163,30 @@ function isSafeRustCommandCall(call, pattern, language) {
|
|
|
13963
14163
|
return false;
|
|
13964
14164
|
}
|
|
13965
14165
|
var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
|
|
13966
|
-
function argIsClassLiteral(call, position) {
|
|
14166
|
+
function argIsClassLiteral(call, position, types) {
|
|
13967
14167
|
const arg = call.arguments.find((a) => a.position === position);
|
|
13968
14168
|
if (!arg) return false;
|
|
13969
14169
|
const expr = (arg.literal ?? arg.expression ?? "").trim();
|
|
13970
14170
|
if (!expr) return false;
|
|
13971
14171
|
if (CLASS_LITERAL_RE.test(expr)) return true;
|
|
13972
|
-
|
|
14172
|
+
if (TYPE_TOKEN_RE.test(expr)) return true;
|
|
14173
|
+
if (!types || types.length === 0) return false;
|
|
14174
|
+
const argExpr = (arg.expression ?? "").trim();
|
|
14175
|
+
const varName = arg.variable ?? "";
|
|
14176
|
+
const isBareIdentifier = varName !== "" && argExpr === varName && !/[(.]/.test(argExpr);
|
|
14177
|
+
if (isBareIdentifier) {
|
|
14178
|
+
const t = resolveIdentifierType(varName, call.in_method, types);
|
|
14179
|
+
if (isBoundedClassType(t)) return true;
|
|
14180
|
+
}
|
|
14181
|
+
const isCallExpr = varName !== "" && new RegExp(`^${escapeRe(varName)}\\s*\\(`).test(argExpr);
|
|
14182
|
+
if (isCallExpr) {
|
|
14183
|
+
const t = resolveCallReturnType(varName, call.in_method, types);
|
|
14184
|
+
if (isBoundedClassType(t)) return true;
|
|
14185
|
+
}
|
|
14186
|
+
return false;
|
|
14187
|
+
}
|
|
14188
|
+
function escapeRe(s) {
|
|
14189
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13973
14190
|
}
|
|
13974
14191
|
var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
|
|
13975
14192
|
function argIsStringLiteral(call, position) {
|
|
@@ -14130,10 +14347,13 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
14130
14347
|
}
|
|
14131
14348
|
return false;
|
|
14132
14349
|
}
|
|
14133
|
-
function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
14350
|
+
function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
|
|
14351
|
+
const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
|
|
14352
|
+
(p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
|
|
14353
|
+
);
|
|
14134
14354
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
14135
14355
|
for (const call of calls) {
|
|
14136
|
-
for (const pattern of
|
|
14356
|
+
for (const pattern of patternsForLanguage) {
|
|
14137
14357
|
if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
|
|
14138
14358
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
14139
14359
|
continue;
|
|
@@ -14150,7 +14370,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
|
|
|
14150
14370
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
14151
14371
|
continue;
|
|
14152
14372
|
}
|
|
14153
|
-
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
|
|
14373
|
+
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|
|
14154
14374
|
continue;
|
|
14155
14375
|
}
|
|
14156
14376
|
if (pattern.safe_if_string_literal_at !== void 0 && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
|
|
@@ -14551,7 +14771,20 @@ function matchesAnnotation(annotations, targetAnnotation) {
|
|
|
14551
14771
|
}
|
|
14552
14772
|
return false;
|
|
14553
14773
|
}
|
|
14774
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE = /* @__PURE__ */ new Map();
|
|
14775
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
|
|
14554
14776
|
function receiverMightBeClass(receiver, className) {
|
|
14777
|
+
const key = receiver + "\0" + className;
|
|
14778
|
+
const cached = RECEIVER_MIGHT_BE_CLASS_CACHE.get(key);
|
|
14779
|
+
if (cached !== void 0) return cached;
|
|
14780
|
+
const result = receiverMightBeClassImpl(receiver, className);
|
|
14781
|
+
if (RECEIVER_MIGHT_BE_CLASS_CACHE.size >= RECEIVER_MIGHT_BE_CLASS_CACHE_CAP) {
|
|
14782
|
+
RECEIVER_MIGHT_BE_CLASS_CACHE.clear();
|
|
14783
|
+
}
|
|
14784
|
+
RECEIVER_MIGHT_BE_CLASS_CACHE.set(key, result);
|
|
14785
|
+
return result;
|
|
14786
|
+
}
|
|
14787
|
+
function receiverMightBeClassImpl(receiver, className) {
|
|
14555
14788
|
if (className.startsWith("*") && className.length > 1) {
|
|
14556
14789
|
const suffix = className.slice(1).toLowerCase();
|
|
14557
14790
|
let simpleReceiver = receiver;
|
|
@@ -16570,8 +16803,15 @@ var AnalysisPipeline = class {
|
|
|
16570
16803
|
};
|
|
16571
16804
|
|
|
16572
16805
|
// src/analysis/dfg-walk.ts
|
|
16806
|
+
var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
|
|
16573
16807
|
function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
16574
16808
|
const maxHops = options.maxHops ?? 32;
|
|
16809
|
+
let perFile = walkBackwardDefsMemo.get(chainsByToDef);
|
|
16810
|
+
if (perFile !== void 0) {
|
|
16811
|
+
const key = `${startDefId}|${maxHops}`;
|
|
16812
|
+
const hit = perFile.get(key);
|
|
16813
|
+
if (hit !== void 0) return hit;
|
|
16814
|
+
}
|
|
16575
16815
|
const visited = /* @__PURE__ */ new Set();
|
|
16576
16816
|
const lines = /* @__PURE__ */ new Set();
|
|
16577
16817
|
let hopCapReached = false;
|
|
@@ -16603,7 +16843,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
16603
16843
|
queue.push(fromId);
|
|
16604
16844
|
}
|
|
16605
16845
|
}
|
|
16606
|
-
|
|
16846
|
+
const result = { visited, lines, hopCapReached };
|
|
16847
|
+
if (perFile === void 0) {
|
|
16848
|
+
perFile = /* @__PURE__ */ new Map();
|
|
16849
|
+
walkBackwardDefsMemo.set(chainsByToDef, perFile);
|
|
16850
|
+
}
|
|
16851
|
+
perFile.set(`${startDefId}|${maxHops}`, result);
|
|
16852
|
+
return result;
|
|
16607
16853
|
}
|
|
16608
16854
|
|
|
16609
16855
|
// src/analysis/sanitizer-index.ts
|
|
@@ -18113,7 +18359,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18113
18359
|
this.constructorParamPositions.clear();
|
|
18114
18360
|
this.safePatternFieldsCache = null;
|
|
18115
18361
|
this.isTaintedExpressionCache = null;
|
|
18116
|
-
this.
|
|
18362
|
+
const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
|
|
18117
18363
|
for (const methodName of sanitizerMethods) {
|
|
18118
18364
|
this.methodReturnsSanitized.add(methodName);
|
|
18119
18365
|
}
|
|
@@ -18121,7 +18367,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18121
18367
|
this.source,
|
|
18122
18368
|
(name2) => this.lookupSymbol(name2)
|
|
18123
18369
|
);
|
|
18124
|
-
this.analyzeMethodReturns(
|
|
18370
|
+
this.analyzeMethodReturns(prePassMethods);
|
|
18125
18371
|
this.seedPythonModuleConstants(tree.rootNode);
|
|
18126
18372
|
this.visit(tree.rootNode);
|
|
18127
18373
|
this.refineTaintFromConstants();
|
|
@@ -18191,8 +18437,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18191
18437
|
/**
|
|
18192
18438
|
* Pre-pass: Analyze all methods to detect those that always return constants or sanitized values.
|
|
18193
18439
|
*/
|
|
18194
|
-
analyzeMethodReturns(
|
|
18195
|
-
const methods = this.findAllMethods(root);
|
|
18440
|
+
analyzeMethodReturns(methods) {
|
|
18196
18441
|
for (const method of methods) {
|
|
18197
18442
|
const methodName = this.getMethodName(method);
|
|
18198
18443
|
if (!methodName) continue;
|
|
@@ -18456,11 +18701,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18456
18701
|
* Collect all class field names (instance/static variables declared at class level).
|
|
18457
18702
|
* These are variables declared directly in the class body, not inside methods.
|
|
18458
18703
|
*/
|
|
18459
|
-
|
|
18704
|
+
collectClassFieldsAndMethods(root) {
|
|
18705
|
+
const methods = [];
|
|
18460
18706
|
const stack = [root];
|
|
18461
18707
|
while (stack.length > 0) {
|
|
18462
18708
|
const n = stack.pop();
|
|
18463
18709
|
if (!n) continue;
|
|
18710
|
+
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
18711
|
+
methods.push(n);
|
|
18712
|
+
}
|
|
18464
18713
|
if (n.type === "class_body") {
|
|
18465
18714
|
for (const child of n.children) {
|
|
18466
18715
|
if (child.type === "field_declaration") {
|
|
@@ -18474,14 +18723,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18474
18723
|
}
|
|
18475
18724
|
}
|
|
18476
18725
|
}
|
|
18477
|
-
stack.push(child);
|
|
18726
|
+
if (child) stack.push(child);
|
|
18478
18727
|
}
|
|
18479
18728
|
continue;
|
|
18480
18729
|
}
|
|
18481
18730
|
for (const child of n.children) {
|
|
18482
|
-
stack.push(child);
|
|
18731
|
+
if (child) stack.push(child);
|
|
18483
18732
|
}
|
|
18484
18733
|
}
|
|
18734
|
+
return methods;
|
|
18485
18735
|
}
|
|
18486
18736
|
/**
|
|
18487
18737
|
* Sprint 9 #55 — seed the symbol table with Python module-level constant
|
|
@@ -18639,21 +18889,6 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18639
18889
|
this.symbols.set(name2, value);
|
|
18640
18890
|
}
|
|
18641
18891
|
}
|
|
18642
|
-
findAllMethods(node) {
|
|
18643
|
-
const methods = [];
|
|
18644
|
-
const stack = [node];
|
|
18645
|
-
while (stack.length > 0) {
|
|
18646
|
-
const n = stack.pop();
|
|
18647
|
-
if (!n) continue;
|
|
18648
|
-
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
18649
|
-
methods.push(n);
|
|
18650
|
-
}
|
|
18651
|
-
for (const child of n.children) {
|
|
18652
|
-
if (child) stack.push(child);
|
|
18653
|
-
}
|
|
18654
|
-
}
|
|
18655
|
-
return methods;
|
|
18656
|
-
}
|
|
18657
18892
|
getMethodName(method) {
|
|
18658
18893
|
const nameNode = method.childForFieldName("name");
|
|
18659
18894
|
if (nameNode) {
|
|
@@ -31145,6 +31380,9 @@ var MyBatisAnnotationSqlSinkPass = class {
|
|
|
31145
31380
|
};
|
|
31146
31381
|
|
|
31147
31382
|
// src/analysis/passes/sink-filter-pass.ts
|
|
31383
|
+
function escapeReSf(s) {
|
|
31384
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31385
|
+
}
|
|
31148
31386
|
var JS_XSS_SANITIZERS = [
|
|
31149
31387
|
/\bDOMPurify\.sanitize\s*\(/,
|
|
31150
31388
|
/\bsanitizeHtml\s*\(/,
|
|
@@ -31201,6 +31439,12 @@ var DATA_PARSER_TYPES = /* @__PURE__ */ new Set([
|
|
|
31201
31439
|
"OptionParser",
|
|
31202
31440
|
"CmdLineParser"
|
|
31203
31441
|
]);
|
|
31442
|
+
var JAVA_EVAL_PARSER_DENYLIST = /* @__PURE__ */ new Set([
|
|
31443
|
+
"GroovyShell",
|
|
31444
|
+
"GroovyClassLoader",
|
|
31445
|
+
"ScriptEngine",
|
|
31446
|
+
"CronParser"
|
|
31447
|
+
]);
|
|
31204
31448
|
var COMPILED_TEMPLATE_TYPES = /* @__PURE__ */ new Set([
|
|
31205
31449
|
"Template",
|
|
31206
31450
|
// Freemarker, Velocity
|
|
@@ -31643,7 +31887,12 @@ var SinkFilterPass = class {
|
|
|
31643
31887
|
const method = sink.method ?? receiverMatch?.[2];
|
|
31644
31888
|
if (method === "parse" && receiver) {
|
|
31645
31889
|
const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
|
|
31646
|
-
if (recvType
|
|
31890
|
+
if (recvType) {
|
|
31891
|
+
if (DATA_PARSER_TYPES.has(recvType)) return false;
|
|
31892
|
+
if (recvType.endsWith("Parser") && !JAVA_EVAL_PARSER_DENYLIST.has(recvType)) {
|
|
31893
|
+
return false;
|
|
31894
|
+
}
|
|
31895
|
+
}
|
|
31647
31896
|
}
|
|
31648
31897
|
if ((method === "render" || method === "process" || method === "merge" || method === "renderTo") && receiver) {
|
|
31649
31898
|
const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
|
|
@@ -31740,12 +31989,30 @@ var SinkFilterPass = class {
|
|
|
31740
31989
|
}
|
|
31741
31990
|
if (language === "java") {
|
|
31742
31991
|
const sourceLines = ctx.code.split("\n");
|
|
31992
|
+
const irCalls = ctx.graph.ir.calls;
|
|
31993
|
+
const irTypes = ctx.graph.ir.types;
|
|
31743
31994
|
filtered = filtered.filter((sink) => {
|
|
31744
31995
|
if (sink.type !== "command_injection") return true;
|
|
31745
31996
|
if (sink.method !== "ProcessBuilder" && sink.method !== "start") return true;
|
|
31746
31997
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
31747
31998
|
if (!/\bnew\s+ProcessBuilder\s*\(/.test(sinkLineText)) return true;
|
|
31748
31999
|
if (PROCESS_BUILDER_ARGV_FORM_RE.test(sinkLineText)) return false;
|
|
32000
|
+
const ctorCall = irCalls.find(
|
|
32001
|
+
(c) => c.method_name === "ProcessBuilder" && c.location.line === sink.line && (c.receiver === null || c.receiver === void 0)
|
|
32002
|
+
);
|
|
32003
|
+
if (!ctorCall || ctorCall.arguments.length === 0) return true;
|
|
32004
|
+
const arg0 = ctorCall.arguments[0];
|
|
32005
|
+
if (!arg0) return true;
|
|
32006
|
+
const argExpr = (arg0.expression ?? "").trim();
|
|
32007
|
+
const varName = arg0.variable ?? "";
|
|
32008
|
+
if (varName !== "" && argExpr === varName && !/[(.]/.test(argExpr)) {
|
|
32009
|
+
const t = resolveIdentifierType(varName, ctorCall.in_method, irTypes);
|
|
32010
|
+
if (isArgvContainerType(t)) return false;
|
|
32011
|
+
}
|
|
32012
|
+
if (varName !== "" && new RegExp(`^${escapeReSf(varName)}\\s*\\(`).test(argExpr)) {
|
|
32013
|
+
const t = resolveCallReturnType(varName, ctorCall.in_method, irTypes);
|
|
32014
|
+
if (isArgvContainerType(t)) return false;
|
|
32015
|
+
}
|
|
31749
32016
|
return true;
|
|
31750
32017
|
});
|
|
31751
32018
|
}
|