circle-ir 3.176.0 → 3.178.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +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/dependency-versions.d.ts +102 -0
- package/dist/analysis/dependency-versions.d.ts.map +1 -0
- package/dist/analysis/dependency-versions.js +153 -0
- package/dist/analysis/dependency-versions.js.map +1 -0
- 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/note-coalescer.d.ts +46 -0
- package/dist/analysis/note-coalescer.d.ts.map +1 -0
- package/dist/analysis/note-coalescer.js +106 -0
- package/dist/analysis/note-coalescer.js.map +1 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts +58 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts.map +1 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.js +122 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.js.map +1 -0
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +33 -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 +55 -33
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +34 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +21 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +351 -47
- package/dist/core/circle-ir-core.cjs +197 -45
- package/dist/core/circle-ir-core.js +197 -45
- 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/dist/types/index.d.ts +24 -0
- package/dist/types/index.d.ts.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
|
|
@@ -13545,11 +13685,11 @@ function attachSourceLineCode(sources, sinks, code) {
|
|
|
13545
13685
|
}
|
|
13546
13686
|
function findSources(calls, types, patterns, sourceLines, language) {
|
|
13547
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
|
+
);
|
|
13548
13691
|
for (const call of calls) {
|
|
13549
|
-
for (const pattern of
|
|
13550
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13551
|
-
continue;
|
|
13552
|
-
}
|
|
13692
|
+
for (const pattern of patternsForLanguage) {
|
|
13553
13693
|
if (matchesSourcePattern(call, pattern)) {
|
|
13554
13694
|
sources.push({
|
|
13555
13695
|
type: pattern.type,
|
|
@@ -13565,11 +13705,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13565
13705
|
for (const type of types) {
|
|
13566
13706
|
for (const method of type.methods) {
|
|
13567
13707
|
for (const param of method.parameters) {
|
|
13568
|
-
for (const pattern of
|
|
13708
|
+
for (const pattern of patternsForLanguage) {
|
|
13569
13709
|
if (pattern.annotation && pattern.param_tainted) {
|
|
13570
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13571
|
-
continue;
|
|
13572
|
-
}
|
|
13573
13710
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
13574
13711
|
const paramLine = param.line ?? method.start_line;
|
|
13575
13712
|
sources.push({
|
|
@@ -13588,11 +13725,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13588
13725
|
}
|
|
13589
13726
|
for (const type of types) {
|
|
13590
13727
|
for (const method of type.methods) {
|
|
13591
|
-
for (const pattern of
|
|
13728
|
+
for (const pattern of patternsForLanguage) {
|
|
13592
13729
|
if (!pattern.method_annotation) continue;
|
|
13593
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13594
|
-
continue;
|
|
13595
|
-
}
|
|
13596
13730
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
13597
13731
|
for (const param of method.parameters) {
|
|
13598
13732
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -14214,9 +14348,12 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
14214
14348
|
return false;
|
|
14215
14349
|
}
|
|
14216
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
|
+
);
|
|
14217
14354
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
14218
14355
|
for (const call of calls) {
|
|
14219
|
-
for (const pattern of
|
|
14356
|
+
for (const pattern of patternsForLanguage) {
|
|
14220
14357
|
if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
|
|
14221
14358
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
14222
14359
|
continue;
|
|
@@ -14634,7 +14771,20 @@ function matchesAnnotation(annotations, targetAnnotation) {
|
|
|
14634
14771
|
}
|
|
14635
14772
|
return false;
|
|
14636
14773
|
}
|
|
14774
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE = /* @__PURE__ */ new Map();
|
|
14775
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
|
|
14637
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) {
|
|
14638
14788
|
if (className.startsWith("*") && className.length > 1) {
|
|
14639
14789
|
const suffix = className.slice(1).toLowerCase();
|
|
14640
14790
|
let simpleReceiver = receiver;
|
|
@@ -16653,8 +16803,15 @@ var AnalysisPipeline = class {
|
|
|
16653
16803
|
};
|
|
16654
16804
|
|
|
16655
16805
|
// src/analysis/dfg-walk.ts
|
|
16806
|
+
var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
|
|
16656
16807
|
function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
16657
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
|
+
}
|
|
16658
16815
|
const visited = /* @__PURE__ */ new Set();
|
|
16659
16816
|
const lines = /* @__PURE__ */ new Set();
|
|
16660
16817
|
let hopCapReached = false;
|
|
@@ -16686,7 +16843,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
16686
16843
|
queue.push(fromId);
|
|
16687
16844
|
}
|
|
16688
16845
|
}
|
|
16689
|
-
|
|
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;
|
|
16690
16853
|
}
|
|
16691
16854
|
|
|
16692
16855
|
// src/analysis/sanitizer-index.ts
|
|
@@ -18196,7 +18359,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18196
18359
|
this.constructorParamPositions.clear();
|
|
18197
18360
|
this.safePatternFieldsCache = null;
|
|
18198
18361
|
this.isTaintedExpressionCache = null;
|
|
18199
|
-
this.
|
|
18362
|
+
const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
|
|
18200
18363
|
for (const methodName of sanitizerMethods) {
|
|
18201
18364
|
this.methodReturnsSanitized.add(methodName);
|
|
18202
18365
|
}
|
|
@@ -18204,7 +18367,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18204
18367
|
this.source,
|
|
18205
18368
|
(name2) => this.lookupSymbol(name2)
|
|
18206
18369
|
);
|
|
18207
|
-
this.analyzeMethodReturns(
|
|
18370
|
+
this.analyzeMethodReturns(prePassMethods);
|
|
18208
18371
|
this.seedPythonModuleConstants(tree.rootNode);
|
|
18209
18372
|
this.visit(tree.rootNode);
|
|
18210
18373
|
this.refineTaintFromConstants();
|
|
@@ -18274,8 +18437,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18274
18437
|
/**
|
|
18275
18438
|
* Pre-pass: Analyze all methods to detect those that always return constants or sanitized values.
|
|
18276
18439
|
*/
|
|
18277
|
-
analyzeMethodReturns(
|
|
18278
|
-
const methods = this.findAllMethods(root);
|
|
18440
|
+
analyzeMethodReturns(methods) {
|
|
18279
18441
|
for (const method of methods) {
|
|
18280
18442
|
const methodName = this.getMethodName(method);
|
|
18281
18443
|
if (!methodName) continue;
|
|
@@ -18539,11 +18701,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18539
18701
|
* Collect all class field names (instance/static variables declared at class level).
|
|
18540
18702
|
* These are variables declared directly in the class body, not inside methods.
|
|
18541
18703
|
*/
|
|
18542
|
-
|
|
18704
|
+
collectClassFieldsAndMethods(root) {
|
|
18705
|
+
const methods = [];
|
|
18543
18706
|
const stack = [root];
|
|
18544
18707
|
while (stack.length > 0) {
|
|
18545
18708
|
const n = stack.pop();
|
|
18546
18709
|
if (!n) continue;
|
|
18710
|
+
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
18711
|
+
methods.push(n);
|
|
18712
|
+
}
|
|
18547
18713
|
if (n.type === "class_body") {
|
|
18548
18714
|
for (const child of n.children) {
|
|
18549
18715
|
if (child.type === "field_declaration") {
|
|
@@ -18557,14 +18723,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18557
18723
|
}
|
|
18558
18724
|
}
|
|
18559
18725
|
}
|
|
18560
|
-
stack.push(child);
|
|
18726
|
+
if (child) stack.push(child);
|
|
18561
18727
|
}
|
|
18562
18728
|
continue;
|
|
18563
18729
|
}
|
|
18564
18730
|
for (const child of n.children) {
|
|
18565
|
-
stack.push(child);
|
|
18731
|
+
if (child) stack.push(child);
|
|
18566
18732
|
}
|
|
18567
18733
|
}
|
|
18734
|
+
return methods;
|
|
18568
18735
|
}
|
|
18569
18736
|
/**
|
|
18570
18737
|
* Sprint 9 #55 — seed the symbol table with Python module-level constant
|
|
@@ -18722,21 +18889,6 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
18722
18889
|
this.symbols.set(name2, value);
|
|
18723
18890
|
}
|
|
18724
18891
|
}
|
|
18725
|
-
findAllMethods(node) {
|
|
18726
|
-
const methods = [];
|
|
18727
|
-
const stack = [node];
|
|
18728
|
-
while (stack.length > 0) {
|
|
18729
|
-
const n = stack.pop();
|
|
18730
|
-
if (!n) continue;
|
|
18731
|
-
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
18732
|
-
methods.push(n);
|
|
18733
|
-
}
|
|
18734
|
-
for (const child of n.children) {
|
|
18735
|
-
if (child) stack.push(child);
|
|
18736
|
-
}
|
|
18737
|
-
}
|
|
18738
|
-
return methods;
|
|
18739
|
-
}
|
|
18740
18892
|
getMethodName(method) {
|
|
18741
18893
|
const nameNode = method.childForFieldName("name");
|
|
18742
18894
|
if (nameNode) {
|
|
@@ -20353,6 +20505,49 @@ function applyLibraryApiSurfaceDowngrade(findings) {
|
|
|
20353
20505
|
});
|
|
20354
20506
|
}
|
|
20355
20507
|
|
|
20508
|
+
// src/analysis/note-coalescer.ts
|
|
20509
|
+
function coalesceNoteLevelFindings(findings) {
|
|
20510
|
+
if (findings.length < 2) return [...findings];
|
|
20511
|
+
const groups = /* @__PURE__ */ new Map();
|
|
20512
|
+
const order = [];
|
|
20513
|
+
for (const f of findings) {
|
|
20514
|
+
const key = `${f.file}\0${f.line}`;
|
|
20515
|
+
const bucket = groups.get(key);
|
|
20516
|
+
if (bucket) {
|
|
20517
|
+
bucket.push(f);
|
|
20518
|
+
} else {
|
|
20519
|
+
groups.set(key, [f]);
|
|
20520
|
+
order.push(key);
|
|
20521
|
+
}
|
|
20522
|
+
}
|
|
20523
|
+
const out2 = [];
|
|
20524
|
+
for (const key of order) {
|
|
20525
|
+
const bucket = groups.get(key);
|
|
20526
|
+
if (bucket.length === 1) {
|
|
20527
|
+
out2.push(bucket[0]);
|
|
20528
|
+
continue;
|
|
20529
|
+
}
|
|
20530
|
+
const allNote = bucket.every((f) => f.level === "note");
|
|
20531
|
+
if (!allNote) {
|
|
20532
|
+
for (const f of bucket) out2.push(f);
|
|
20533
|
+
continue;
|
|
20534
|
+
}
|
|
20535
|
+
const uniqueRuleIds = new Set(bucket.map((f) => f.rule_id));
|
|
20536
|
+
if (uniqueRuleIds.size < 2) {
|
|
20537
|
+
for (const f of bucket) out2.push(f);
|
|
20538
|
+
continue;
|
|
20539
|
+
}
|
|
20540
|
+
const sorted = [...bucket].sort((a, b) => a.rule_id.localeCompare(b.rule_id));
|
|
20541
|
+
const primary = sorted[0];
|
|
20542
|
+
const additional = sorted.slice(1).map((f) => f.rule_id).concat(...sorted.map((f) => f.labels ?? []));
|
|
20543
|
+
const uniqueLabels = Array.from(new Set(additional)).filter(
|
|
20544
|
+
(l) => l !== primary.rule_id
|
|
20545
|
+
);
|
|
20546
|
+
out2.push({ ...primary, labels: uniqueLabels });
|
|
20547
|
+
}
|
|
20548
|
+
return out2;
|
|
20549
|
+
}
|
|
20550
|
+
|
|
20356
20551
|
// src/analysis/entry-point-detection.ts
|
|
20357
20552
|
var TIER_1_METHOD_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
20358
20553
|
// Spring MVC
|
|
@@ -31287,6 +31482,12 @@ var DATA_PARSER_TYPES = /* @__PURE__ */ new Set([
|
|
|
31287
31482
|
"OptionParser",
|
|
31288
31483
|
"CmdLineParser"
|
|
31289
31484
|
]);
|
|
31485
|
+
var JAVA_EVAL_PARSER_DENYLIST = /* @__PURE__ */ new Set([
|
|
31486
|
+
"GroovyShell",
|
|
31487
|
+
"GroovyClassLoader",
|
|
31488
|
+
"ScriptEngine",
|
|
31489
|
+
"CronParser"
|
|
31490
|
+
]);
|
|
31290
31491
|
var COMPILED_TEMPLATE_TYPES = /* @__PURE__ */ new Set([
|
|
31291
31492
|
"Template",
|
|
31292
31493
|
// Freemarker, Velocity
|
|
@@ -31729,7 +31930,12 @@ var SinkFilterPass = class {
|
|
|
31729
31930
|
const method = sink.method ?? receiverMatch?.[2];
|
|
31730
31931
|
if (method === "parse" && receiver) {
|
|
31731
31932
|
const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
|
|
31732
|
-
if (recvType
|
|
31933
|
+
if (recvType) {
|
|
31934
|
+
if (DATA_PARSER_TYPES.has(recvType)) return false;
|
|
31935
|
+
if (recvType.endsWith("Parser") && !JAVA_EVAL_PARSER_DENYLIST.has(recvType)) {
|
|
31936
|
+
return false;
|
|
31937
|
+
}
|
|
31938
|
+
}
|
|
31733
31939
|
}
|
|
31734
31940
|
if ((method === "render" || method === "process" || method === "merge" || method === "renderTo") && receiver) {
|
|
31735
31941
|
const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
|
|
@@ -32634,6 +32840,101 @@ var SinkSemanticsPass = class {
|
|
|
32634
32840
|
}
|
|
32635
32841
|
};
|
|
32636
32842
|
|
|
32843
|
+
// src/analysis/dependency-versions.ts
|
|
32844
|
+
function resolveFastjsonFromPom(pomXml) {
|
|
32845
|
+
if (!pomXml) return null;
|
|
32846
|
+
const propMatch = pomXml.match(/<fastjson\.version>\s*([^<\s]+)\s*<\/fastjson\.version>/);
|
|
32847
|
+
if (propMatch) {
|
|
32848
|
+
const version = propMatch[1];
|
|
32849
|
+
return { version, noneAutotype: /_noneautotype/i.test(version) };
|
|
32850
|
+
}
|
|
32851
|
+
const depRe = /<dependency>[\s\S]*?<\/dependency>/g;
|
|
32852
|
+
let m;
|
|
32853
|
+
while ((m = depRe.exec(pomXml)) !== null) {
|
|
32854
|
+
const block = m[0];
|
|
32855
|
+
const gid = block.match(/<groupId>\s*([^<\s]+)\s*<\/groupId>/)?.[1];
|
|
32856
|
+
const aid = block.match(/<artifactId>\s*([^<\s]+)\s*<\/artifactId>/)?.[1];
|
|
32857
|
+
if (gid !== "com.alibaba") continue;
|
|
32858
|
+
if (aid !== "fastjson" && aid !== "fastjson2") continue;
|
|
32859
|
+
const ver = block.match(/<version>\s*([^<\s]+)\s*<\/version>/)?.[1];
|
|
32860
|
+
if (!ver) continue;
|
|
32861
|
+
if (/^\$\{/.test(ver)) return null;
|
|
32862
|
+
return { version: ver, noneAutotype: /_noneautotype/i.test(ver) };
|
|
32863
|
+
}
|
|
32864
|
+
return null;
|
|
32865
|
+
}
|
|
32866
|
+
function fileReenablesFastjsonAutotype(source) {
|
|
32867
|
+
if (!source) return false;
|
|
32868
|
+
return /\bsetAutoTypeSupport\s*\(\s*true\b/.test(source);
|
|
32869
|
+
}
|
|
32870
|
+
function fileEnablesJacksonPolymorphism(source) {
|
|
32871
|
+
if (!source) return false;
|
|
32872
|
+
if (/\benableDefaultTyping\s*\(/.test(source)) return true;
|
|
32873
|
+
if (/\bactivateDefaultTyping\s*\(/.test(source)) return true;
|
|
32874
|
+
if (/@JsonTypeInfo\b/.test(source)) return true;
|
|
32875
|
+
return false;
|
|
32876
|
+
}
|
|
32877
|
+
function fileConfiguresSnakeYamlSafely(source) {
|
|
32878
|
+
if (!source) return false;
|
|
32879
|
+
if (/\bnew\s+SafeConstructor\s*\(/.test(source)) return true;
|
|
32880
|
+
if (/\bSafeConstructor\s+\w+\s*=/.test(source)) return true;
|
|
32881
|
+
return false;
|
|
32882
|
+
}
|
|
32883
|
+
|
|
32884
|
+
// src/analysis/passes/deserialization-safety-gate-pass.ts
|
|
32885
|
+
var FASTJSON_METHODS = /* @__PURE__ */ new Set(["parseObject", "parse"]);
|
|
32886
|
+
var FASTJSON_CLASSES = /* @__PURE__ */ new Set(["JSON", "JSONObject"]);
|
|
32887
|
+
var JACKSON_METHODS = /* @__PURE__ */ new Set(["readValue", "convertValue", "treeToValue"]);
|
|
32888
|
+
var JACKSON_CLASSES = /* @__PURE__ */ new Set(["ObjectMapper", "ObjectReader"]);
|
|
32889
|
+
var SNAKEYAML_METHODS = /* @__PURE__ */ new Set(["load", "loadAs", "loadAll"]);
|
|
32890
|
+
var SNAKEYAML_CLASSES = /* @__PURE__ */ new Set(["Yaml"]);
|
|
32891
|
+
var DeserializationSafetyGatePass = class {
|
|
32892
|
+
constructor(dependencyContext) {
|
|
32893
|
+
this.dependencyContext = dependencyContext;
|
|
32894
|
+
}
|
|
32895
|
+
dependencyContext;
|
|
32896
|
+
name = "deserialization-safety-gate";
|
|
32897
|
+
category = "security";
|
|
32898
|
+
run(ctx) {
|
|
32899
|
+
const { graph, language, code } = ctx;
|
|
32900
|
+
if (language !== "java") {
|
|
32901
|
+
return { droppedFastjson: 0, droppedJackson: 0, droppedSnakeYaml: 0 };
|
|
32902
|
+
}
|
|
32903
|
+
const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
|
|
32904
|
+
const pomXml = this.dependencyContext?.java?.pomXml;
|
|
32905
|
+
const fastjson = pomXml ? resolveFastjsonFromPom(pomXml) : null;
|
|
32906
|
+
const fastjsonHardened = fastjson?.noneAutotype === true && !fileReenablesFastjsonAutotype(code);
|
|
32907
|
+
const jacksonSafe = !fileEnablesJacksonPolymorphism(code);
|
|
32908
|
+
const snakeYamlSafe = fileConfiguresSnakeYamlSafely(code);
|
|
32909
|
+
let droppedFastjson = 0;
|
|
32910
|
+
let droppedJackson = 0;
|
|
32911
|
+
let droppedSnakeYaml = 0;
|
|
32912
|
+
const kept = sinks.filter((sink) => {
|
|
32913
|
+
if (sink.type !== "deserialization") return true;
|
|
32914
|
+
if (!sink.method) return true;
|
|
32915
|
+
if (fastjsonHardened && FASTJSON_METHODS.has(sink.method) && (sink.class === void 0 || FASTJSON_CLASSES.has(sink.class))) {
|
|
32916
|
+
droppedFastjson++;
|
|
32917
|
+
return false;
|
|
32918
|
+
}
|
|
32919
|
+
if (jacksonSafe && JACKSON_METHODS.has(sink.method) && sink.class !== void 0 && JACKSON_CLASSES.has(sink.class)) {
|
|
32920
|
+
droppedJackson++;
|
|
32921
|
+
return false;
|
|
32922
|
+
}
|
|
32923
|
+
if (snakeYamlSafe && SNAKEYAML_METHODS.has(sink.method) && sink.class !== void 0 && SNAKEYAML_CLASSES.has(sink.class)) {
|
|
32924
|
+
droppedSnakeYaml++;
|
|
32925
|
+
return false;
|
|
32926
|
+
}
|
|
32927
|
+
return true;
|
|
32928
|
+
});
|
|
32929
|
+
const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml;
|
|
32930
|
+
if (totalDropped > 0) {
|
|
32931
|
+
sinks.length = 0;
|
|
32932
|
+
sinks.push(...kept);
|
|
32933
|
+
}
|
|
32934
|
+
return { droppedFastjson, droppedJackson, droppedSnakeYaml };
|
|
32935
|
+
}
|
|
32936
|
+
};
|
|
32937
|
+
|
|
32637
32938
|
// src/analysis/passes/cli-main-reflection-suppress-pass.ts
|
|
32638
32939
|
var REFLECTION_SINK_METHODS = /* @__PURE__ */ new Set([
|
|
32639
32940
|
"forName",
|
|
@@ -42729,6 +43030,8 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
42729
43030
|
pipeline.add(new MyBatisAnnotationSqlSinkPass());
|
|
42730
43031
|
pipeline.add(new SinkFilterPass());
|
|
42731
43032
|
if (!disabledPasses.has("sink-semantics")) pipeline.add(new SinkSemanticsPass());
|
|
43033
|
+
if (!disabledPasses.has("deserialization-safety-gate"))
|
|
43034
|
+
pipeline.add(new DeserializationSafetyGatePass(options.dependencyContext));
|
|
42732
43035
|
if (!disabledPasses.has("cli-main-reflection-suppress"))
|
|
42733
43036
|
pipeline.add(new CliMainReflectionSuppressPass());
|
|
42734
43037
|
if (!disabledPasses.has("library-profile-sink-gate"))
|
|
@@ -42840,9 +43143,10 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
42840
43143
|
downgradedFindings,
|
|
42841
43144
|
makeProfileResolver(options.projectProfile)
|
|
42842
43145
|
);
|
|
43146
|
+
const coalescedFindings = coalesceNoteLevelFindings(profiledFindings);
|
|
42843
43147
|
const cappedFindings = applyPerFileFindingCap(
|
|
42844
43148
|
filePath,
|
|
42845
|
-
|
|
43149
|
+
coalescedFindings,
|
|
42846
43150
|
options.perFileFindingCap ?? DEFAULT_PER_FILE_FINDING_CAP
|
|
42847
43151
|
);
|
|
42848
43152
|
return {
|