circle-ir 4.7.2 → 4.9.7
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 +34 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +1 -0
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +37 -3
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/passes/insecure-deserialization-config-pass.d.ts +2 -0
- package/dist/analysis/passes/insecure-deserialization-config-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-deserialization-config-pass.js +45 -2
- package/dist/analysis/passes/insecure-deserialization-config-pass.js.map +1 -1
- package/dist/analysis/passes/jwt-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/jwt-verify-disabled-pass.js +48 -21
- package/dist/analysis/passes/jwt-verify-disabled-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +131 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.js +34 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-crypto-pass.js +32 -19
- package/dist/analysis/passes/weak-crypto-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +293 -25
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +14 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +660 -109
- package/dist/core/circle-ir-core.cjs +411 -68
- package/dist/core/circle-ir-core.js +411 -68
- package/dist/core/extractors/calls.js +17 -3
- 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 +140 -40
- package/dist/core/extractors/cfg.js.map +1 -1
- package/dist/core/extractors/types.js +77 -2
- package/dist/core/extractors/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -4421,6 +4421,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4421
4421
|
const nameNode = node.childForFieldName("name");
|
|
4422
4422
|
const body2 = node.childForFieldName("body");
|
|
4423
4423
|
const methods = [];
|
|
4424
|
+
const fields = extractCSharpFields(body2);
|
|
4424
4425
|
if (body2) {
|
|
4425
4426
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4426
4427
|
const m = body2.child(i2);
|
|
@@ -4438,7 +4439,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4438
4439
|
parameters.push({
|
|
4439
4440
|
name: getNodeText(pName),
|
|
4440
4441
|
type: pType ? getNodeText(pType) : null,
|
|
4441
|
-
annotations:
|
|
4442
|
+
annotations: extractCSharpParamAnnotations(pnode),
|
|
4442
4443
|
line: pnode.startPosition.row + 1
|
|
4443
4444
|
});
|
|
4444
4445
|
}
|
|
@@ -4464,7 +4465,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4464
4465
|
implements: [],
|
|
4465
4466
|
annotations: [],
|
|
4466
4467
|
methods,
|
|
4467
|
-
fields
|
|
4468
|
+
fields,
|
|
4468
4469
|
start_line: node.startPosition.row + 1,
|
|
4469
4470
|
end_line: node.endPosition.row + 1
|
|
4470
4471
|
});
|
|
@@ -4472,6 +4473,59 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4472
4473
|
}
|
|
4473
4474
|
return types;
|
|
4474
4475
|
}
|
|
4476
|
+
function extractCSharpFields(body2) {
|
|
4477
|
+
const fields = [];
|
|
4478
|
+
if (!body2) return fields;
|
|
4479
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4480
|
+
const c = body2.child(i2);
|
|
4481
|
+
if (!c) continue;
|
|
4482
|
+
if (c.type === "field_declaration") {
|
|
4483
|
+
let varDecl = null;
|
|
4484
|
+
for (let k = 0; k < c.childCount; k++) {
|
|
4485
|
+
const cc = c.child(k);
|
|
4486
|
+
if (cc?.type === "variable_declaration") {
|
|
4487
|
+
varDecl = cc;
|
|
4488
|
+
break;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
if (!varDecl) continue;
|
|
4492
|
+
const typeNode = varDecl.childForFieldName("type");
|
|
4493
|
+
const type = typeNode ? getNodeText(typeNode) : null;
|
|
4494
|
+
const modifiers = extractCSharpModifiers(c);
|
|
4495
|
+
for (let k = 0; k < varDecl.childCount; k++) {
|
|
4496
|
+
const decl = varDecl.child(k);
|
|
4497
|
+
if (decl?.type !== "variable_declarator") continue;
|
|
4498
|
+
const nameNode = decl.childForFieldName("name");
|
|
4499
|
+
fields.push({ name: nameNode ? getNodeText(nameNode) : "unknown", type, modifiers, annotations: [] });
|
|
4500
|
+
}
|
|
4501
|
+
} else if (c.type === "property_declaration") {
|
|
4502
|
+
const nameNode = c.childForFieldName("name");
|
|
4503
|
+
if (!nameNode) continue;
|
|
4504
|
+
const typeNode = c.childForFieldName("type");
|
|
4505
|
+
fields.push({
|
|
4506
|
+
name: getNodeText(nameNode),
|
|
4507
|
+
type: typeNode ? getNodeText(typeNode) : null,
|
|
4508
|
+
modifiers: extractCSharpModifiers(c),
|
|
4509
|
+
annotations: []
|
|
4510
|
+
});
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
return fields;
|
|
4514
|
+
}
|
|
4515
|
+
function extractCSharpParamAnnotations(param) {
|
|
4516
|
+
const out2 = [];
|
|
4517
|
+
for (let i2 = 0; i2 < param.childCount; i2++) {
|
|
4518
|
+
const list = param.child(i2);
|
|
4519
|
+
if (list?.type !== "attribute_list") continue;
|
|
4520
|
+
for (let j = 0; j < list.childCount; j++) {
|
|
4521
|
+
const attr = list.child(j);
|
|
4522
|
+
if (attr?.type !== "attribute") continue;
|
|
4523
|
+
const name2 = attr.childForFieldName("name");
|
|
4524
|
+
if (name2) out2.push(getNodeText(name2));
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
return out2;
|
|
4528
|
+
}
|
|
4475
4529
|
function extractCSharpModifiers(node) {
|
|
4476
4530
|
const mods = [];
|
|
4477
4531
|
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
@@ -6046,13 +6100,21 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6046
6100
|
const left = asn.childForFieldName("left");
|
|
6047
6101
|
if (left?.type !== "member_access_expression") continue;
|
|
6048
6102
|
const nameNode = left.childForFieldName("name");
|
|
6049
|
-
if (!nameNode
|
|
6103
|
+
if (!nameNode) continue;
|
|
6104
|
+
const propName = getNodeText(nameNode);
|
|
6105
|
+
const exprNode = left.childForFieldName("expression");
|
|
6106
|
+
if (propName === "Filter") {
|
|
6107
|
+
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6108
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6109
|
+
if (recvType !== "DirectorySearcher") continue;
|
|
6110
|
+
} else if (propName !== "CommandText") {
|
|
6111
|
+
continue;
|
|
6112
|
+
}
|
|
6050
6113
|
const right = asn.childForFieldName("right");
|
|
6051
6114
|
if (!right) continue;
|
|
6052
6115
|
const rhsText = getNodeText(right);
|
|
6053
|
-
const exprNode = left.childForFieldName("expression");
|
|
6054
6116
|
calls.push({
|
|
6055
|
-
method_name:
|
|
6117
|
+
method_name: propName,
|
|
6056
6118
|
receiver: exprNode ? getNodeText(exprNode) : null,
|
|
6057
6119
|
receiver_type: null,
|
|
6058
6120
|
receiver_type_fqn: null,
|
|
@@ -8618,6 +8680,9 @@ function buildCFG(tree, language, cache) {
|
|
|
8618
8680
|
if (effectiveLanguage === "go") {
|
|
8619
8681
|
return buildGoCFG(tree, blockIdCounter, cache);
|
|
8620
8682
|
}
|
|
8683
|
+
if (effectiveLanguage === "csharp") {
|
|
8684
|
+
return buildCSharpCFG(tree, blockIdCounter, cache);
|
|
8685
|
+
}
|
|
8621
8686
|
if (isJavaScript) {
|
|
8622
8687
|
const functions = [
|
|
8623
8688
|
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
@@ -8630,7 +8695,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8630
8695
|
const body2 = func2.childForFieldName("body");
|
|
8631
8696
|
if (!body2) continue;
|
|
8632
8697
|
if (body2.type === "statement_block") {
|
|
8633
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8698
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "js");
|
|
8634
8699
|
allBlocks.push(...blocks);
|
|
8635
8700
|
allEdges.push(...edges);
|
|
8636
8701
|
blockIdCounter = nextId;
|
|
@@ -8652,7 +8717,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8652
8717
|
for (const method of methods) {
|
|
8653
8718
|
const body2 = method.childForFieldName("body");
|
|
8654
8719
|
if (!body2) continue;
|
|
8655
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8720
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8656
8721
|
allBlocks.push(...blocks);
|
|
8657
8722
|
allEdges.push(...edges);
|
|
8658
8723
|
blockIdCounter = nextId;
|
|
@@ -8660,7 +8725,28 @@ function buildCFG(tree, language, cache) {
|
|
|
8660
8725
|
}
|
|
8661
8726
|
return { blocks: allBlocks, edges: allEdges };
|
|
8662
8727
|
}
|
|
8663
|
-
function
|
|
8728
|
+
function buildCSharpCFG(tree, blockIdCounter, cache) {
|
|
8729
|
+
const allBlocks = [];
|
|
8730
|
+
const allEdges = [];
|
|
8731
|
+
const containers = [
|
|
8732
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache),
|
|
8733
|
+
...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
|
|
8734
|
+
...getNodesFromCache(tree.rootNode, "destructor_declaration", cache),
|
|
8735
|
+
...getNodesFromCache(tree.rootNode, "operator_declaration", cache),
|
|
8736
|
+
...getNodesFromCache(tree.rootNode, "local_function_statement", cache),
|
|
8737
|
+
...getNodesFromCache(tree.rootNode, "accessor_declaration", cache)
|
|
8738
|
+
];
|
|
8739
|
+
for (const container of containers) {
|
|
8740
|
+
const body2 = container.childForFieldName("body");
|
|
8741
|
+
if (!body2 || body2.type !== "block") continue;
|
|
8742
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "csharp");
|
|
8743
|
+
allBlocks.push(...blocks);
|
|
8744
|
+
allEdges.push(...edges);
|
|
8745
|
+
blockIdCounter = nextId;
|
|
8746
|
+
}
|
|
8747
|
+
return { blocks: allBlocks, edges: allEdges };
|
|
8748
|
+
}
|
|
8749
|
+
function buildMethodCFG(body2, startId, dialect) {
|
|
8664
8750
|
const blocks = [];
|
|
8665
8751
|
const edges = [];
|
|
8666
8752
|
let currentId = startId;
|
|
@@ -8671,7 +8757,7 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8671
8757
|
end_line: body2.startPosition.row + 1
|
|
8672
8758
|
};
|
|
8673
8759
|
blocks.push(entryBlock);
|
|
8674
|
-
const result = processStatements(body2, currentId, blocks, edges,
|
|
8760
|
+
const result = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8675
8761
|
currentId = result.nextId;
|
|
8676
8762
|
if (result.entryId !== -1) {
|
|
8677
8763
|
edges.push({
|
|
@@ -8703,15 +8789,15 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8703
8789
|
}
|
|
8704
8790
|
return { blocks, edges, nextId: currentId };
|
|
8705
8791
|
}
|
|
8706
|
-
function processStatements(container, startId, blocks, edges,
|
|
8792
|
+
function processStatements(container, startId, blocks, edges, dialect) {
|
|
8707
8793
|
let currentId = startId;
|
|
8708
8794
|
let firstBlockId = -1;
|
|
8709
8795
|
let lastExitIds = [];
|
|
8710
8796
|
for (let i2 = 0; i2 < container.childCount; i2++) {
|
|
8711
8797
|
const stmt = container.child(i2);
|
|
8712
8798
|
if (!stmt) continue;
|
|
8713
|
-
if (!isStatement(stmt,
|
|
8714
|
-
const result = processStatement(stmt, currentId, blocks, edges,
|
|
8799
|
+
if (!isStatement(stmt, dialect)) continue;
|
|
8800
|
+
const result = processStatement(stmt, currentId, blocks, edges, dialect);
|
|
8715
8801
|
currentId = result.nextId;
|
|
8716
8802
|
if (firstBlockId === -1) {
|
|
8717
8803
|
firstBlockId = result.entryId;
|
|
@@ -8732,31 +8818,49 @@ function processStatements(container, startId, blocks, edges, isJavaScript) {
|
|
|
8732
8818
|
nextId: currentId
|
|
8733
8819
|
};
|
|
8734
8820
|
}
|
|
8735
|
-
function processStatement(stmt, startId, blocks, edges,
|
|
8821
|
+
function processStatement(stmt, startId, blocks, edges, dialect) {
|
|
8736
8822
|
switch (stmt.type) {
|
|
8737
8823
|
case "if_statement":
|
|
8738
|
-
return processIfStatement(stmt, startId, blocks, edges,
|
|
8824
|
+
return processIfStatement(stmt, startId, blocks, edges, dialect);
|
|
8739
8825
|
case "for_statement":
|
|
8740
8826
|
case "enhanced_for_statement":
|
|
8741
8827
|
case "for_in_statement":
|
|
8742
8828
|
case "for_of_statement":
|
|
8743
|
-
|
|
8829
|
+
case "foreach_statement":
|
|
8830
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8744
8831
|
case "while_statement":
|
|
8745
|
-
return processWhileStatement(stmt, startId, blocks, edges,
|
|
8832
|
+
return processWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8746
8833
|
case "do_statement":
|
|
8747
|
-
return processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8834
|
+
return processDoWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8748
8835
|
case "try_statement":
|
|
8749
|
-
return processTryStatement(stmt, startId, blocks, edges,
|
|
8836
|
+
return processTryStatement(stmt, startId, blocks, edges, dialect);
|
|
8750
8837
|
case "switch_expression":
|
|
8751
8838
|
case "switch_statement":
|
|
8752
|
-
return processSwitchStatement(stmt, startId, blocks, edges,
|
|
8839
|
+
return processSwitchStatement(stmt, startId, blocks, edges, dialect);
|
|
8840
|
+
// C# scoped blocks add no branching — flow through the inner block so its
|
|
8841
|
+
// nested statements are still captured.
|
|
8842
|
+
case "using_statement":
|
|
8843
|
+
case "lock_statement":
|
|
8844
|
+
case "checked_statement":
|
|
8845
|
+
case "unsafe_statement": {
|
|
8846
|
+
const inner = stmt.childForFieldName("body") ?? lastBlockChild(stmt);
|
|
8847
|
+
if (inner) return processStatement(inner, startId, blocks, edges, dialect);
|
|
8848
|
+
return processSimpleStatement(stmt, startId, blocks);
|
|
8849
|
+
}
|
|
8753
8850
|
case "block":
|
|
8754
8851
|
case "statement_block":
|
|
8755
|
-
return processStatements(stmt, startId, blocks, edges,
|
|
8852
|
+
return processStatements(stmt, startId, blocks, edges, dialect);
|
|
8756
8853
|
default:
|
|
8757
8854
|
return processSimpleStatement(stmt, startId, blocks);
|
|
8758
8855
|
}
|
|
8759
8856
|
}
|
|
8857
|
+
function lastBlockChild(node) {
|
|
8858
|
+
for (let i2 = node.childCount - 1; i2 >= 0; i2--) {
|
|
8859
|
+
const c = node.child(i2);
|
|
8860
|
+
if (c && c.type === "block") return c;
|
|
8861
|
+
}
|
|
8862
|
+
return null;
|
|
8863
|
+
}
|
|
8760
8864
|
function processSimpleStatement(stmt, startId, blocks) {
|
|
8761
8865
|
const block = {
|
|
8762
8866
|
id: startId,
|
|
@@ -8771,7 +8875,7 @@ function processSimpleStatement(stmt, startId, blocks) {
|
|
|
8771
8875
|
nextId: startId + 1
|
|
8772
8876
|
};
|
|
8773
8877
|
}
|
|
8774
|
-
function processIfStatement(stmt, startId, blocks, edges,
|
|
8878
|
+
function processIfStatement(stmt, startId, blocks, edges, dialect) {
|
|
8775
8879
|
let currentId = startId;
|
|
8776
8880
|
const condBlock = {
|
|
8777
8881
|
id: currentId++,
|
|
@@ -8783,7 +8887,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8783
8887
|
const exitIds = [];
|
|
8784
8888
|
const consequence = stmt.childForFieldName("consequence");
|
|
8785
8889
|
if (consequence) {
|
|
8786
|
-
const thenResult = processStatement(consequence, currentId, blocks, edges,
|
|
8890
|
+
const thenResult = processStatement(consequence, currentId, blocks, edges, dialect);
|
|
8787
8891
|
currentId = thenResult.nextId;
|
|
8788
8892
|
edges.push({
|
|
8789
8893
|
from: condBlock.id,
|
|
@@ -8794,7 +8898,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8794
8898
|
}
|
|
8795
8899
|
const alternative = stmt.childForFieldName("alternative");
|
|
8796
8900
|
if (alternative) {
|
|
8797
|
-
const elseResult = processStatement(alternative, currentId, blocks, edges,
|
|
8901
|
+
const elseResult = processStatement(alternative, currentId, blocks, edges, dialect);
|
|
8798
8902
|
currentId = elseResult.nextId;
|
|
8799
8903
|
edges.push({
|
|
8800
8904
|
from: condBlock.id,
|
|
@@ -8811,7 +8915,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8811
8915
|
nextId: currentId
|
|
8812
8916
|
};
|
|
8813
8917
|
}
|
|
8814
|
-
function processForStatement(stmt, startId, blocks, edges,
|
|
8918
|
+
function processForStatement(stmt, startId, blocks, edges, dialect) {
|
|
8815
8919
|
let currentId = startId;
|
|
8816
8920
|
const loopBlock = {
|
|
8817
8921
|
id: currentId++,
|
|
@@ -8822,7 +8926,7 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8822
8926
|
blocks.push(loopBlock);
|
|
8823
8927
|
const body2 = stmt.childForFieldName("body");
|
|
8824
8928
|
if (body2) {
|
|
8825
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8929
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8826
8930
|
currentId = bodyResult.nextId;
|
|
8827
8931
|
edges.push({
|
|
8828
8932
|
from: loopBlock.id,
|
|
@@ -8844,16 +8948,16 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8844
8948
|
nextId: currentId
|
|
8845
8949
|
};
|
|
8846
8950
|
}
|
|
8847
|
-
function processWhileStatement(stmt, startId, blocks, edges,
|
|
8848
|
-
return processForStatement(stmt, startId, blocks, edges,
|
|
8951
|
+
function processWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8952
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8849
8953
|
}
|
|
8850
|
-
function processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8954
|
+
function processDoWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8851
8955
|
let currentId = startId;
|
|
8852
8956
|
const body2 = stmt.childForFieldName("body");
|
|
8853
8957
|
let bodyEntryId = currentId;
|
|
8854
8958
|
let bodyExitIds = [];
|
|
8855
8959
|
if (body2) {
|
|
8856
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8960
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8857
8961
|
currentId = bodyResult.nextId;
|
|
8858
8962
|
bodyEntryId = bodyResult.entryId;
|
|
8859
8963
|
bodyExitIds = bodyResult.exitIds;
|
|
@@ -8883,13 +8987,13 @@ function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8883
8987
|
nextId: currentId
|
|
8884
8988
|
};
|
|
8885
8989
|
}
|
|
8886
|
-
function processTryStatement(stmt, startId, blocks, edges,
|
|
8990
|
+
function processTryStatement(stmt, startId, blocks, edges, dialect) {
|
|
8887
8991
|
let currentId = startId;
|
|
8888
8992
|
const exitIds = [];
|
|
8889
8993
|
const body2 = stmt.childForFieldName("body");
|
|
8890
8994
|
let tryEntryId = -1;
|
|
8891
8995
|
if (body2) {
|
|
8892
|
-
const bodyResult = processStatements(body2, currentId, blocks, edges,
|
|
8996
|
+
const bodyResult = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8893
8997
|
currentId = bodyResult.nextId;
|
|
8894
8998
|
tryEntryId = bodyResult.entryId;
|
|
8895
8999
|
exitIds.push(...bodyResult.exitIds);
|
|
@@ -8899,7 +9003,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8899
9003
|
if (child?.type === "catch_clause") {
|
|
8900
9004
|
const catchBody = child.childForFieldName("body");
|
|
8901
9005
|
if (catchBody) {
|
|
8902
|
-
const catchResult = processStatements(catchBody, currentId, blocks, edges,
|
|
9006
|
+
const catchResult = processStatements(catchBody, currentId, blocks, edges, dialect);
|
|
8903
9007
|
currentId = catchResult.nextId;
|
|
8904
9008
|
if (tryEntryId !== -1) {
|
|
8905
9009
|
edges.push({
|
|
@@ -8912,9 +9016,18 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8912
9016
|
}
|
|
8913
9017
|
}
|
|
8914
9018
|
}
|
|
8915
|
-
|
|
9019
|
+
let finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
|
|
9020
|
+
if (!finallyClause && dialect === "csharp") {
|
|
9021
|
+
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
9022
|
+
const child = stmt.child(i2);
|
|
9023
|
+
if (child?.type === "finally_clause") {
|
|
9024
|
+
finallyClause = lastBlockChild(child);
|
|
9025
|
+
break;
|
|
9026
|
+
}
|
|
9027
|
+
}
|
|
9028
|
+
}
|
|
8916
9029
|
if (finallyClause) {
|
|
8917
|
-
const finallyResult = processStatements(finallyClause, currentId, blocks, edges,
|
|
9030
|
+
const finallyResult = processStatements(finallyClause, currentId, blocks, edges, dialect);
|
|
8918
9031
|
currentId = finallyResult.nextId;
|
|
8919
9032
|
for (const exitId of exitIds) {
|
|
8920
9033
|
edges.push({
|
|
@@ -8935,7 +9048,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8935
9048
|
nextId: currentId
|
|
8936
9049
|
};
|
|
8937
9050
|
}
|
|
8938
|
-
function processSwitchStatement(stmt, startId, blocks, edges,
|
|
9051
|
+
function processSwitchStatement(stmt, startId, blocks, edges, dialect) {
|
|
8939
9052
|
let currentId = startId;
|
|
8940
9053
|
const switchBlock = {
|
|
8941
9054
|
id: currentId++,
|
|
@@ -8947,11 +9060,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8947
9060
|
const exitIds = [];
|
|
8948
9061
|
const body2 = stmt.childForFieldName("body");
|
|
8949
9062
|
if (body2) {
|
|
8950
|
-
const caseTypes =
|
|
9063
|
+
const caseTypes = dialect === "js" ? ["switch_case", "switch_default"] : dialect === "csharp" ? ["switch_section"] : ["switch_block_statement_group", "switch_rule"];
|
|
8951
9064
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
8952
9065
|
const child = body2.child(i2);
|
|
8953
9066
|
if (child && caseTypes.includes(child.type)) {
|
|
8954
|
-
const caseResult = processStatements(child, currentId, blocks, edges,
|
|
9067
|
+
const caseResult = processStatements(child, currentId, blocks, edges, dialect);
|
|
8955
9068
|
currentId = caseResult.nextId;
|
|
8956
9069
|
if (caseResult.entryId !== -1) {
|
|
8957
9070
|
edges.push({
|
|
@@ -8981,7 +9094,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8981
9094
|
for (const func2 of functions) {
|
|
8982
9095
|
const body2 = func2.childForFieldName("body");
|
|
8983
9096
|
if (!body2) continue;
|
|
8984
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
9097
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8985
9098
|
allBlocks.push(...blocks);
|
|
8986
9099
|
allEdges.push(...edges);
|
|
8987
9100
|
blockIdCounter = nextId;
|
|
@@ -9004,7 +9117,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
9004
9117
|
let lastExitIds = [];
|
|
9005
9118
|
let firstBlockId = -1;
|
|
9006
9119
|
for (const stmt of topLevelStatements) {
|
|
9007
|
-
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges,
|
|
9120
|
+
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, "java");
|
|
9008
9121
|
blockIdCounter = result.nextId;
|
|
9009
9122
|
if (firstBlockId === -1) {
|
|
9010
9123
|
firstBlockId = result.entryId;
|
|
@@ -9048,7 +9161,8 @@ function isBashStatement(node) {
|
|
|
9048
9161
|
]);
|
|
9049
9162
|
return bashStatementTypes.has(node.type);
|
|
9050
9163
|
}
|
|
9051
|
-
function isStatement(node,
|
|
9164
|
+
function isStatement(node, dialect) {
|
|
9165
|
+
if (dialect === "csharp") return csharpStatementTypes.has(node.type);
|
|
9052
9166
|
const javaStatementTypes = /* @__PURE__ */ new Set([
|
|
9053
9167
|
"local_variable_declaration",
|
|
9054
9168
|
"expression_statement",
|
|
@@ -9092,8 +9206,30 @@ function isStatement(node, isJavaScript) {
|
|
|
9092
9206
|
"export_statement",
|
|
9093
9207
|
"import_statement"
|
|
9094
9208
|
]);
|
|
9095
|
-
return
|
|
9096
|
-
}
|
|
9209
|
+
return dialect === "js" ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
|
|
9210
|
+
}
|
|
9211
|
+
var csharpStatementTypes = /* @__PURE__ */ new Set([
|
|
9212
|
+
"local_declaration_statement",
|
|
9213
|
+
"expression_statement",
|
|
9214
|
+
"if_statement",
|
|
9215
|
+
"for_statement",
|
|
9216
|
+
"foreach_statement",
|
|
9217
|
+
"while_statement",
|
|
9218
|
+
"do_statement",
|
|
9219
|
+
"try_statement",
|
|
9220
|
+
"switch_statement",
|
|
9221
|
+
"return_statement",
|
|
9222
|
+
"throw_statement",
|
|
9223
|
+
"break_statement",
|
|
9224
|
+
"continue_statement",
|
|
9225
|
+
"using_statement",
|
|
9226
|
+
"lock_statement",
|
|
9227
|
+
"checked_statement",
|
|
9228
|
+
"unsafe_statement",
|
|
9229
|
+
"yield_statement",
|
|
9230
|
+
"goto_statement",
|
|
9231
|
+
"block"
|
|
9232
|
+
]);
|
|
9097
9233
|
function buildGoCFG(tree, blockIdCounter, cache) {
|
|
9098
9234
|
const allBlocks = [];
|
|
9099
9235
|
const allEdges = [];
|
|
@@ -9104,7 +9240,7 @@ function buildGoCFG(tree, blockIdCounter, cache) {
|
|
|
9104
9240
|
for (const func2 of functions) {
|
|
9105
9241
|
const body2 = func2.childForFieldName("body");
|
|
9106
9242
|
if (!body2 || body2.type !== "block") continue;
|
|
9107
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
9243
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
9108
9244
|
allBlocks.push(...blocks);
|
|
9109
9245
|
allEdges.push(...edges);
|
|
9110
9246
|
blockIdCounter = nextId;
|
|
@@ -13184,6 +13320,11 @@ var DEFAULT_SINKS = [
|
|
|
13184
13320
|
// ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
|
|
13185
13321
|
// extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
|
|
13186
13322
|
{ method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13323
|
+
// `DirectorySearcher.Filter = "(uid=" + x + ")"` — LDAP injection (cognium-ai#272).
|
|
13324
|
+
// Emitted as a synthetic call by extractCSharpCalls ONLY when the receiver
|
|
13325
|
+
// resolves to a DirectorySearcher, so this classless entry never over-matches
|
|
13326
|
+
// other `.Filter =` assignments (DataView/BindingSource/collection filters).
|
|
13327
|
+
{ method: "Filter", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13187
13328
|
// ADO.NET `cmd.Execute*()` — object-carried sink (cognium-dev#271). The taint
|
|
13188
13329
|
// rides the SqlCommand receiver (CommandText set from tainted data); the
|
|
13189
13330
|
// receiver is surfaced as arg[0] by extractCSharpCalls, and the command object
|
|
@@ -13200,6 +13341,9 @@ var DEFAULT_SINKS = [
|
|
|
13200
13341
|
// injectable — the second is the argv path where taint rides arg[1] (#276).
|
|
13201
13342
|
{ method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13202
13343
|
{ method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13344
|
+
// P/Invoke of libc `system(cmd)` — lowercase `system` in C# is virtually
|
|
13345
|
+
// always the imported shell entry point (cognium-ai#275 interop/IlPinvoke).
|
|
13346
|
+
{ method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13203
13347
|
// C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
|
|
13204
13348
|
{ method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13205
13349
|
{ method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13228,6 +13372,9 @@ var DEFAULT_SINKS = [
|
|
|
13228
13372
|
{ method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13229
13373
|
{ method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13230
13374
|
{ method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13375
|
+
// ASP.NET `ControllerBase.PhysicalFile(path, contentType)` serves a file from
|
|
13376
|
+
// an absolute disk path — attacker-controllable path is CWE-22 (cognium-ai#326/#275).
|
|
13377
|
+
{ method: "PhysicalFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13231
13378
|
// C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
|
|
13232
13379
|
{ method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13233
13380
|
{ method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13252,6 +13399,17 @@ var DEFAULT_SINKS = [
|
|
|
13252
13399
|
{ method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13253
13400
|
{ method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13254
13401
|
{ method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13402
|
+
// C# server-side template injection (SSTI). Compiling an attacker-controlled
|
|
13403
|
+
// template string is arbitrary code execution — classified as code_injection
|
|
13404
|
+
// (CWE-94), matching how Python (Jinja2/Mako) and Node SSTI are modelled.
|
|
13405
|
+
// Restricted to DISTINCTIVE template-compile APIs so the generic
|
|
13406
|
+
// `Template.Parse` / `.Compile` names (int.Parse, Regex.Compile, …) are not
|
|
13407
|
+
// over-matched. Taint-gated: a constant template never fires. `RunCompile`
|
|
13408
|
+
// = RazorEngine; `CompileRenderStringAsync` = RazorLight; `Compile` is
|
|
13409
|
+
// class-scoped to Handlebars. (cognium-dev#273)
|
|
13410
|
+
{ method: "RunCompile", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13411
|
+
{ method: "CompileRenderStringAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13412
|
+
{ method: "Compile", class: "Handlebars", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13255
13413
|
// C# insecure deserialization — the polymorphic BCL formatters that can
|
|
13256
13414
|
// instantiate arbitrary types named in the payload (CWE-502, cognium-ai#318).
|
|
13257
13415
|
// Each of these classes exists only to deserialize and is unsafe on untrusted
|
|
@@ -13266,6 +13424,14 @@ var DEFAULT_SINKS = [
|
|
|
13266
13424
|
{ method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13267
13425
|
{ method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13268
13426
|
{ method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13427
|
+
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
13428
|
+
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
13429
|
+
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13430
|
+
// `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
|
|
13431
|
+
// Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
|
|
13432
|
+
// only an argument is tainted never fires. .NET composite formatting can't corrupt
|
|
13433
|
+
// memory, so medium (FormatException DoS / unintended arg access), unlike C printf.
|
|
13434
|
+
{ method: "Format", class: "String", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13269
13435
|
// C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
|
|
13270
13436
|
// filter is the constructor argument.
|
|
13271
13437
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13304,6 +13470,10 @@ var DEFAULT_SINKS = [
|
|
|
13304
13470
|
{ method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13305
13471
|
{ method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13306
13472
|
{ method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13473
|
+
// XPathNavigator.Select/Evaluate — class-scoped (both names collide with LINQ
|
|
13474
|
+
// `.Select`/`.Evaluate`, so they must resolve to an XPathNavigator receiver).
|
|
13475
|
+
{ method: "Select", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13476
|
+
{ method: "Evaluate", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13307
13477
|
// C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
|
|
13308
13478
|
{ method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13309
13479
|
{ method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -14272,7 +14442,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14272
14442
|
const skipMethods = ["toString", "hashCode", "equals", "compareTo"];
|
|
14273
14443
|
if (skipMethods.includes(method.name)) continue;
|
|
14274
14444
|
for (const param of method.parameters) {
|
|
14275
|
-
const
|
|
14445
|
+
const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
|
|
14446
|
+
const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
|
|
14276
14447
|
if (isTaintable) {
|
|
14277
14448
|
const paramLine = param.line ?? method.start_line;
|
|
14278
14449
|
sources.push({
|
|
@@ -14392,6 +14563,13 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14392
14563
|
}
|
|
14393
14564
|
return result;
|
|
14394
14565
|
}
|
|
14566
|
+
var CSHARP_BINDING_ATTRS = /* @__PURE__ */ new Set([
|
|
14567
|
+
"FromBody",
|
|
14568
|
+
"FromQuery",
|
|
14569
|
+
"FromRoute",
|
|
14570
|
+
"FromForm",
|
|
14571
|
+
"FromHeader"
|
|
14572
|
+
]);
|
|
14395
14573
|
function isInterproceduralTaintableType(typeName, language) {
|
|
14396
14574
|
const baseType = typeName.split("<")[0].trim();
|
|
14397
14575
|
const excludedTypes = [
|
|
@@ -14630,33 +14808,195 @@ function isSafeJSChildProcessCall(call, pattern, language) {
|
|
|
14630
14808
|
if (SHELL_PROGRAMS.has(program)) return false;
|
|
14631
14809
|
return true;
|
|
14632
14810
|
}
|
|
14633
|
-
|
|
14811
|
+
var CSHARP_SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
14812
|
+
"sh",
|
|
14813
|
+
"bash",
|
|
14814
|
+
"zsh",
|
|
14815
|
+
"dash",
|
|
14816
|
+
"ash",
|
|
14817
|
+
"ksh",
|
|
14818
|
+
"cmd",
|
|
14819
|
+
"powershell",
|
|
14820
|
+
"pwsh"
|
|
14821
|
+
]);
|
|
14822
|
+
function isConstNonShellExe(raw) {
|
|
14823
|
+
if (!raw) return false;
|
|
14824
|
+
const t = raw.trim();
|
|
14825
|
+
if (!/^@?"[^"]*"$/.test(t)) return false;
|
|
14826
|
+
const program = (t.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
14827
|
+
return !CSHARP_SHELL_PROGRAMS.has(program);
|
|
14828
|
+
}
|
|
14829
|
+
var PSI_CTOR_EXE_RE = /\bnew\s+ProcessStartInfo\s*(?:<[^>]*>)?\s*\(\s*(@?"[^"]*")/;
|
|
14830
|
+
function processStartInfoExe(expr, sourceLines) {
|
|
14831
|
+
const inline = PSI_CTOR_EXE_RE.exec(expr);
|
|
14832
|
+
if (inline) return inline[1];
|
|
14833
|
+
if (sourceLines && /^[A-Za-z_]\w*$/.test(expr.trim())) {
|
|
14834
|
+
const varName = expr.trim();
|
|
14835
|
+
const assignRe = new RegExp(
|
|
14836
|
+
`\\b${varName}\\s*=\\s*new\\s+ProcessStartInfo\\s*(?:<[^>]*>)?\\s*\\(\\s*(@?"[^"]*")`
|
|
14837
|
+
);
|
|
14838
|
+
for (const line of sourceLines) {
|
|
14839
|
+
const m = assignRe.exec(line);
|
|
14840
|
+
if (m) return m[1];
|
|
14841
|
+
}
|
|
14842
|
+
}
|
|
14843
|
+
return null;
|
|
14844
|
+
}
|
|
14845
|
+
function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
14634
14846
|
if (language !== "csharp") return false;
|
|
14635
14847
|
if (pattern.type !== "command_injection") return false;
|
|
14636
|
-
|
|
14637
|
-
if (
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14848
|
+
const method = call.method_name;
|
|
14849
|
+
if (method !== "Start" && method !== "ProcessStartInfo") return false;
|
|
14850
|
+
if (call.arguments.length >= 2) {
|
|
14851
|
+
const fileArg = call.arguments.find((a) => a.position === 0);
|
|
14852
|
+
const raw = fileArg?.literal != null ? String(fileArg.literal) : fileArg?.expression;
|
|
14853
|
+
return isConstNonShellExe(raw);
|
|
14854
|
+
}
|
|
14855
|
+
if (method === "Start" && call.arguments.length === 1) {
|
|
14856
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
14857
|
+
const expr = (arg0?.expression ?? "").trim();
|
|
14858
|
+
return isConstNonShellExe(processStartInfoExe(expr, sourceLines));
|
|
14859
|
+
}
|
|
14860
|
+
return false;
|
|
14861
|
+
}
|
|
14862
|
+
function stripCsLiterals(line) {
|
|
14863
|
+
return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
|
|
14864
|
+
}
|
|
14865
|
+
function csBraceDepthBefore(lines, idx) {
|
|
14866
|
+
let depth = 0;
|
|
14867
|
+
for (let i2 = 0; i2 < idx && i2 < lines.length; i2++) {
|
|
14868
|
+
for (const ch of stripCsLiterals(lines[i2])) {
|
|
14869
|
+
if (ch === "{") depth++;
|
|
14870
|
+
else if (ch === "}") depth--;
|
|
14871
|
+
}
|
|
14872
|
+
}
|
|
14873
|
+
return depth;
|
|
14874
|
+
}
|
|
14875
|
+
function netBraces(fragment) {
|
|
14876
|
+
let n = 0;
|
|
14877
|
+
for (const ch of fragment) {
|
|
14878
|
+
if (ch === "{") n++;
|
|
14879
|
+
else if (ch === "}") n--;
|
|
14880
|
+
}
|
|
14881
|
+
return n;
|
|
14882
|
+
}
|
|
14883
|
+
function splitCsIf(line) {
|
|
14884
|
+
const m = /\bif\s*\(/.exec(line);
|
|
14885
|
+
if (!m) return null;
|
|
14886
|
+
const ifCol = m.index;
|
|
14887
|
+
let i2 = m.index + m[0].length;
|
|
14888
|
+
let depth = 1;
|
|
14889
|
+
let inStr = false;
|
|
14890
|
+
let strCh = "";
|
|
14891
|
+
for (; i2 < line.length; i2++) {
|
|
14892
|
+
const c = line[i2];
|
|
14893
|
+
if (inStr) {
|
|
14894
|
+
if (c === "\\") {
|
|
14895
|
+
i2++;
|
|
14896
|
+
continue;
|
|
14897
|
+
}
|
|
14898
|
+
if (c === strCh) inStr = false;
|
|
14899
|
+
continue;
|
|
14900
|
+
}
|
|
14901
|
+
if (c === '"' || c === "'") {
|
|
14902
|
+
inStr = true;
|
|
14903
|
+
strCh = c;
|
|
14904
|
+
continue;
|
|
14905
|
+
}
|
|
14906
|
+
if (c === "(") depth++;
|
|
14907
|
+
else if (c === ")") {
|
|
14908
|
+
depth--;
|
|
14909
|
+
if (depth === 0) break;
|
|
14910
|
+
}
|
|
14911
|
+
}
|
|
14912
|
+
if (depth !== 0) return null;
|
|
14913
|
+
return { cond: line.slice(m.index + m[0].length, i2), rest: line.slice(i2 + 1), ifCol };
|
|
14914
|
+
}
|
|
14915
|
+
function csEqualityGuard(cond) {
|
|
14916
|
+
const c = cond.trim();
|
|
14917
|
+
let m = /^(.*?)\s*(==|!=)\s*@?"[^"]*"\s*$/.exec(c);
|
|
14918
|
+
if (m) return { op: m[2], exprSide: m[1].trim() };
|
|
14919
|
+
m = /^@?"[^"]*"\s*(==|!=)\s*(.*)$/.exec(c);
|
|
14920
|
+
if (m) return { op: m[1], exprSide: m[2].trim() };
|
|
14921
|
+
return null;
|
|
14922
|
+
}
|
|
14923
|
+
var CS_IDENT_RE = /[A-Za-z_]\w*/g;
|
|
14924
|
+
function csIdentifiers(expr) {
|
|
14925
|
+
return new Set(expr.match(CS_IDENT_RE) ?? []);
|
|
14926
|
+
}
|
|
14927
|
+
function csThenBlock(lines, ifIdx, rest) {
|
|
14928
|
+
const isExit = (s) => /^\s*(?:return|throw|continue|break)\b/.test(s);
|
|
14929
|
+
let firstIdx;
|
|
14930
|
+
let firstText;
|
|
14931
|
+
if (rest.trim()) {
|
|
14932
|
+
firstIdx = ifIdx;
|
|
14933
|
+
firstText = rest;
|
|
14643
14934
|
} else {
|
|
14644
|
-
|
|
14935
|
+
let j = ifIdx + 1;
|
|
14936
|
+
while (j < lines.length && stripCsLiterals(lines[j]).trim() === "") j++;
|
|
14937
|
+
firstIdx = j;
|
|
14938
|
+
firstText = lines[j] ?? "";
|
|
14645
14939
|
}
|
|
14646
|
-
if (
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14650
|
-
"
|
|
14651
|
-
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
|
|
14659
|
-
|
|
14940
|
+
if (firstText.trim().startsWith("{")) {
|
|
14941
|
+
let depth = 0;
|
|
14942
|
+
let started = false;
|
|
14943
|
+
let endLine = lines.length - 1;
|
|
14944
|
+
let body2 = "";
|
|
14945
|
+
for (let i2 = firstIdx; i2 < lines.length; i2++) {
|
|
14946
|
+
const stripped = stripCsLiterals(i2 === firstIdx ? firstText : lines[i2]);
|
|
14947
|
+
let broke = false;
|
|
14948
|
+
for (const ch of stripped) {
|
|
14949
|
+
if (ch === "{") {
|
|
14950
|
+
depth++;
|
|
14951
|
+
started = true;
|
|
14952
|
+
if (depth === 1) continue;
|
|
14953
|
+
} else if (ch === "}") {
|
|
14954
|
+
depth--;
|
|
14955
|
+
if (depth === 0) {
|
|
14956
|
+
endLine = i2;
|
|
14957
|
+
broke = true;
|
|
14958
|
+
break;
|
|
14959
|
+
}
|
|
14960
|
+
}
|
|
14961
|
+
if (started && depth >= 1) body2 += ch;
|
|
14962
|
+
}
|
|
14963
|
+
if (broke) break;
|
|
14964
|
+
if (started) body2 += " ";
|
|
14965
|
+
}
|
|
14966
|
+
return { start: ifIdx, end: endLine, earlyExit: isExit(body2.trim()) };
|
|
14967
|
+
}
|
|
14968
|
+
return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
|
|
14969
|
+
}
|
|
14970
|
+
function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
|
|
14971
|
+
if (language !== "csharp") return false;
|
|
14972
|
+
if (pattern.type !== "ssrf") return false;
|
|
14973
|
+
if (!sourceLines || sourceLines.length === 0) return false;
|
|
14974
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
14975
|
+
for (const a of call.arguments) {
|
|
14976
|
+
if (a.variable) candidates.add(a.variable);
|
|
14977
|
+
for (const id of csIdentifiers(a.expression ?? "")) candidates.add(id);
|
|
14978
|
+
}
|
|
14979
|
+
if (candidates.size === 0) return false;
|
|
14980
|
+
const sinkIdx = call.location.line - 1;
|
|
14981
|
+
const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
|
|
14982
|
+
for (let i2 = 0; i2 < sinkIdx; i2++) {
|
|
14983
|
+
const parts2 = splitCsIf(sourceLines[i2]);
|
|
14984
|
+
if (!parts2) continue;
|
|
14985
|
+
const guard = csEqualityGuard(parts2.cond);
|
|
14986
|
+
if (!guard) continue;
|
|
14987
|
+
const guardIds = csIdentifiers(guard.exprSide);
|
|
14988
|
+
if (![...guardIds].some((id) => candidates.has(id))) continue;
|
|
14989
|
+
const block = csThenBlock(sourceLines, i2, parts2.rest);
|
|
14990
|
+
if (guard.op === "==") {
|
|
14991
|
+
if (sinkIdx >= block.start && sinkIdx <= block.end) return true;
|
|
14992
|
+
} else {
|
|
14993
|
+
const guardDepth = csBraceDepthBefore(sourceLines, i2) + netBraces(stripCsLiterals(sourceLines[i2]).slice(0, parts2.ifCol));
|
|
14994
|
+
if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
|
|
14995
|
+
return true;
|
|
14996
|
+
}
|
|
14997
|
+
}
|
|
14998
|
+
}
|
|
14999
|
+
return false;
|
|
14660
15000
|
}
|
|
14661
15001
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
14662
15002
|
if (language !== "rust") return false;
|
|
@@ -14913,7 +15253,10 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
14913
15253
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
14914
15254
|
continue;
|
|
14915
15255
|
}
|
|
14916
|
-
if (isSafeCSharpProcessStartCall(call, pattern, language)) {
|
|
15256
|
+
if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
|
|
15257
|
+
continue;
|
|
15258
|
+
}
|
|
15259
|
+
if (isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)) {
|
|
14917
15260
|
continue;
|
|
14918
15261
|
}
|
|
14919
15262
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|