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
|
@@ -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
|
|
@@ -12779,11 +12919,11 @@ function attachSourceLineCode(sources, sinks, code) {
|
|
|
12779
12919
|
}
|
|
12780
12920
|
function findSources(calls, types, patterns, sourceLines, language) {
|
|
12781
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
|
+
);
|
|
12782
12925
|
for (const call of calls) {
|
|
12783
|
-
for (const pattern of
|
|
12784
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12785
|
-
continue;
|
|
12786
|
-
}
|
|
12926
|
+
for (const pattern of patternsForLanguage) {
|
|
12787
12927
|
if (matchesSourcePattern(call, pattern)) {
|
|
12788
12928
|
sources.push({
|
|
12789
12929
|
type: pattern.type,
|
|
@@ -12799,11 +12939,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12799
12939
|
for (const type of types) {
|
|
12800
12940
|
for (const method of type.methods) {
|
|
12801
12941
|
for (const param of method.parameters) {
|
|
12802
|
-
for (const pattern of
|
|
12942
|
+
for (const pattern of patternsForLanguage) {
|
|
12803
12943
|
if (pattern.annotation && pattern.param_tainted) {
|
|
12804
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12805
|
-
continue;
|
|
12806
|
-
}
|
|
12807
12944
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
12808
12945
|
const paramLine = param.line ?? method.start_line;
|
|
12809
12946
|
sources.push({
|
|
@@ -12822,11 +12959,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
12822
12959
|
}
|
|
12823
12960
|
for (const type of types) {
|
|
12824
12961
|
for (const method of type.methods) {
|
|
12825
|
-
for (const pattern of
|
|
12962
|
+
for (const pattern of patternsForLanguage) {
|
|
12826
12963
|
if (!pattern.method_annotation) continue;
|
|
12827
|
-
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
12828
|
-
continue;
|
|
12829
|
-
}
|
|
12830
12964
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
12831
12965
|
for (const param of method.parameters) {
|
|
12832
12966
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -13448,9 +13582,12 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
13448
13582
|
return false;
|
|
13449
13583
|
}
|
|
13450
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
|
+
);
|
|
13451
13588
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
13452
13589
|
for (const call of calls) {
|
|
13453
|
-
for (const pattern of
|
|
13590
|
+
for (const pattern of patternsForLanguage) {
|
|
13454
13591
|
if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
|
|
13455
13592
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
13456
13593
|
continue;
|
|
@@ -13868,7 +14005,20 @@ function matchesAnnotation(annotations, targetAnnotation) {
|
|
|
13868
14005
|
}
|
|
13869
14006
|
return false;
|
|
13870
14007
|
}
|
|
14008
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE = /* @__PURE__ */ new Map();
|
|
14009
|
+
var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
|
|
13871
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) {
|
|
13872
14022
|
if (className.startsWith("*") && className.length > 1) {
|
|
13873
14023
|
const suffix = className.slice(1).toLowerCase();
|
|
13874
14024
|
let simpleReceiver = receiver;
|
|
@@ -14572,8 +14722,15 @@ var CodeGraph = class {
|
|
|
14572
14722
|
};
|
|
14573
14723
|
|
|
14574
14724
|
// src/analysis/dfg-walk.ts
|
|
14725
|
+
var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
|
|
14575
14726
|
function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
14576
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
|
+
}
|
|
14577
14734
|
const visited = /* @__PURE__ */ new Set();
|
|
14578
14735
|
const lines = /* @__PURE__ */ new Set();
|
|
14579
14736
|
let hopCapReached = false;
|
|
@@ -14605,7 +14762,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
14605
14762
|
queue.push(fromId);
|
|
14606
14763
|
}
|
|
14607
14764
|
}
|
|
14608
|
-
|
|
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;
|
|
14609
14772
|
}
|
|
14610
14773
|
|
|
14611
14774
|
// src/analysis/sanitizer-index.ts
|
|
@@ -15588,7 +15751,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15588
15751
|
this.constructorParamPositions.clear();
|
|
15589
15752
|
this.safePatternFieldsCache = null;
|
|
15590
15753
|
this.isTaintedExpressionCache = null;
|
|
15591
|
-
this.
|
|
15754
|
+
const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
|
|
15592
15755
|
for (const methodName of sanitizerMethods) {
|
|
15593
15756
|
this.methodReturnsSanitized.add(methodName);
|
|
15594
15757
|
}
|
|
@@ -15596,7 +15759,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15596
15759
|
this.source,
|
|
15597
15760
|
(name2) => this.lookupSymbol(name2)
|
|
15598
15761
|
);
|
|
15599
|
-
this.analyzeMethodReturns(
|
|
15762
|
+
this.analyzeMethodReturns(prePassMethods);
|
|
15600
15763
|
this.seedPythonModuleConstants(tree.rootNode);
|
|
15601
15764
|
this.visit(tree.rootNode);
|
|
15602
15765
|
this.refineTaintFromConstants();
|
|
@@ -15666,8 +15829,7 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15666
15829
|
/**
|
|
15667
15830
|
* Pre-pass: Analyze all methods to detect those that always return constants or sanitized values.
|
|
15668
15831
|
*/
|
|
15669
|
-
analyzeMethodReturns(
|
|
15670
|
-
const methods = this.findAllMethods(root);
|
|
15832
|
+
analyzeMethodReturns(methods) {
|
|
15671
15833
|
for (const method of methods) {
|
|
15672
15834
|
const methodName = this.getMethodName(method);
|
|
15673
15835
|
if (!methodName) continue;
|
|
@@ -15931,11 +16093,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15931
16093
|
* Collect all class field names (instance/static variables declared at class level).
|
|
15932
16094
|
* These are variables declared directly in the class body, not inside methods.
|
|
15933
16095
|
*/
|
|
15934
|
-
|
|
16096
|
+
collectClassFieldsAndMethods(root) {
|
|
16097
|
+
const methods = [];
|
|
15935
16098
|
const stack = [root];
|
|
15936
16099
|
while (stack.length > 0) {
|
|
15937
16100
|
const n = stack.pop();
|
|
15938
16101
|
if (!n) continue;
|
|
16102
|
+
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
16103
|
+
methods.push(n);
|
|
16104
|
+
}
|
|
15939
16105
|
if (n.type === "class_body") {
|
|
15940
16106
|
for (const child of n.children) {
|
|
15941
16107
|
if (child.type === "field_declaration") {
|
|
@@ -15949,14 +16115,15 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
15949
16115
|
}
|
|
15950
16116
|
}
|
|
15951
16117
|
}
|
|
15952
|
-
stack.push(child);
|
|
16118
|
+
if (child) stack.push(child);
|
|
15953
16119
|
}
|
|
15954
16120
|
continue;
|
|
15955
16121
|
}
|
|
15956
16122
|
for (const child of n.children) {
|
|
15957
|
-
stack.push(child);
|
|
16123
|
+
if (child) stack.push(child);
|
|
15958
16124
|
}
|
|
15959
16125
|
}
|
|
16126
|
+
return methods;
|
|
15960
16127
|
}
|
|
15961
16128
|
/**
|
|
15962
16129
|
* Sprint 9 #55 — seed the symbol table with Python module-level constant
|
|
@@ -16114,21 +16281,6 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
16114
16281
|
this.symbols.set(name2, value);
|
|
16115
16282
|
}
|
|
16116
16283
|
}
|
|
16117
|
-
findAllMethods(node) {
|
|
16118
|
-
const methods = [];
|
|
16119
|
-
const stack = [node];
|
|
16120
|
-
while (stack.length > 0) {
|
|
16121
|
-
const n = stack.pop();
|
|
16122
|
-
if (!n) continue;
|
|
16123
|
-
if (n.type === "method_declaration" || n.type === "function_declaration") {
|
|
16124
|
-
methods.push(n);
|
|
16125
|
-
}
|
|
16126
|
-
for (const child of n.children) {
|
|
16127
|
-
if (child) stack.push(child);
|
|
16128
|
-
}
|
|
16129
|
-
}
|
|
16130
|
-
return methods;
|
|
16131
|
-
}
|
|
16132
16284
|
getMethodName(method) {
|
|
16133
16285
|
const nameNode = method.childForFieldName("name");
|
|
16134
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
|