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
|
@@ -4355,6 +4355,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4355
4355
|
const nameNode = node.childForFieldName("name");
|
|
4356
4356
|
const body2 = node.childForFieldName("body");
|
|
4357
4357
|
const methods = [];
|
|
4358
|
+
const fields = extractCSharpFields(body2);
|
|
4358
4359
|
if (body2) {
|
|
4359
4360
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4360
4361
|
const m = body2.child(i2);
|
|
@@ -4372,7 +4373,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4372
4373
|
parameters.push({
|
|
4373
4374
|
name: getNodeText(pName),
|
|
4374
4375
|
type: pType ? getNodeText(pType) : null,
|
|
4375
|
-
annotations:
|
|
4376
|
+
annotations: extractCSharpParamAnnotations(pnode),
|
|
4376
4377
|
line: pnode.startPosition.row + 1
|
|
4377
4378
|
});
|
|
4378
4379
|
}
|
|
@@ -4398,7 +4399,7 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4398
4399
|
implements: [],
|
|
4399
4400
|
annotations: [],
|
|
4400
4401
|
methods,
|
|
4401
|
-
fields
|
|
4402
|
+
fields,
|
|
4402
4403
|
start_line: node.startPosition.row + 1,
|
|
4403
4404
|
end_line: node.endPosition.row + 1
|
|
4404
4405
|
});
|
|
@@ -4406,6 +4407,59 @@ function extractCSharpTypes(tree, cache) {
|
|
|
4406
4407
|
}
|
|
4407
4408
|
return types;
|
|
4408
4409
|
}
|
|
4410
|
+
function extractCSharpFields(body2) {
|
|
4411
|
+
const fields = [];
|
|
4412
|
+
if (!body2) return fields;
|
|
4413
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
4414
|
+
const c = body2.child(i2);
|
|
4415
|
+
if (!c) continue;
|
|
4416
|
+
if (c.type === "field_declaration") {
|
|
4417
|
+
let varDecl = null;
|
|
4418
|
+
for (let k = 0; k < c.childCount; k++) {
|
|
4419
|
+
const cc = c.child(k);
|
|
4420
|
+
if (cc?.type === "variable_declaration") {
|
|
4421
|
+
varDecl = cc;
|
|
4422
|
+
break;
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
if (!varDecl) continue;
|
|
4426
|
+
const typeNode = varDecl.childForFieldName("type");
|
|
4427
|
+
const type = typeNode ? getNodeText(typeNode) : null;
|
|
4428
|
+
const modifiers = extractCSharpModifiers(c);
|
|
4429
|
+
for (let k = 0; k < varDecl.childCount; k++) {
|
|
4430
|
+
const decl = varDecl.child(k);
|
|
4431
|
+
if (decl?.type !== "variable_declarator") continue;
|
|
4432
|
+
const nameNode = decl.childForFieldName("name");
|
|
4433
|
+
fields.push({ name: nameNode ? getNodeText(nameNode) : "unknown", type, modifiers, annotations: [] });
|
|
4434
|
+
}
|
|
4435
|
+
} else if (c.type === "property_declaration") {
|
|
4436
|
+
const nameNode = c.childForFieldName("name");
|
|
4437
|
+
if (!nameNode) continue;
|
|
4438
|
+
const typeNode = c.childForFieldName("type");
|
|
4439
|
+
fields.push({
|
|
4440
|
+
name: getNodeText(nameNode),
|
|
4441
|
+
type: typeNode ? getNodeText(typeNode) : null,
|
|
4442
|
+
modifiers: extractCSharpModifiers(c),
|
|
4443
|
+
annotations: []
|
|
4444
|
+
});
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
return fields;
|
|
4448
|
+
}
|
|
4449
|
+
function extractCSharpParamAnnotations(param) {
|
|
4450
|
+
const out2 = [];
|
|
4451
|
+
for (let i2 = 0; i2 < param.childCount; i2++) {
|
|
4452
|
+
const list = param.child(i2);
|
|
4453
|
+
if (list?.type !== "attribute_list") continue;
|
|
4454
|
+
for (let j = 0; j < list.childCount; j++) {
|
|
4455
|
+
const attr = list.child(j);
|
|
4456
|
+
if (attr?.type !== "attribute") continue;
|
|
4457
|
+
const name2 = attr.childForFieldName("name");
|
|
4458
|
+
if (name2) out2.push(getNodeText(name2));
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
return out2;
|
|
4462
|
+
}
|
|
4409
4463
|
function extractCSharpModifiers(node) {
|
|
4410
4464
|
const mods = [];
|
|
4411
4465
|
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
@@ -5980,13 +6034,21 @@ function extractCSharpCalls(tree, cache) {
|
|
|
5980
6034
|
const left = asn.childForFieldName("left");
|
|
5981
6035
|
if (left?.type !== "member_access_expression") continue;
|
|
5982
6036
|
const nameNode = left.childForFieldName("name");
|
|
5983
|
-
if (!nameNode
|
|
6037
|
+
if (!nameNode) continue;
|
|
6038
|
+
const propName = getNodeText(nameNode);
|
|
6039
|
+
const exprNode = left.childForFieldName("expression");
|
|
6040
|
+
if (propName === "Filter") {
|
|
6041
|
+
const recv = exprNode ? getNodeText(exprNode) : null;
|
|
6042
|
+
const recvType = recv ? typeMap.get(recv) : void 0;
|
|
6043
|
+
if (recvType !== "DirectorySearcher") continue;
|
|
6044
|
+
} else if (propName !== "CommandText") {
|
|
6045
|
+
continue;
|
|
6046
|
+
}
|
|
5984
6047
|
const right = asn.childForFieldName("right");
|
|
5985
6048
|
if (!right) continue;
|
|
5986
6049
|
const rhsText = getNodeText(right);
|
|
5987
|
-
const exprNode = left.childForFieldName("expression");
|
|
5988
6050
|
calls.push({
|
|
5989
|
-
method_name:
|
|
6051
|
+
method_name: propName,
|
|
5990
6052
|
receiver: exprNode ? getNodeText(exprNode) : null,
|
|
5991
6053
|
receiver_type: null,
|
|
5992
6054
|
receiver_type_fqn: null,
|
|
@@ -8552,6 +8614,9 @@ function buildCFG(tree, language, cache) {
|
|
|
8552
8614
|
if (effectiveLanguage === "go") {
|
|
8553
8615
|
return buildGoCFG(tree, blockIdCounter, cache);
|
|
8554
8616
|
}
|
|
8617
|
+
if (effectiveLanguage === "csharp") {
|
|
8618
|
+
return buildCSharpCFG(tree, blockIdCounter, cache);
|
|
8619
|
+
}
|
|
8555
8620
|
if (isJavaScript) {
|
|
8556
8621
|
const functions = [
|
|
8557
8622
|
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
@@ -8564,7 +8629,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8564
8629
|
const body2 = func2.childForFieldName("body");
|
|
8565
8630
|
if (!body2) continue;
|
|
8566
8631
|
if (body2.type === "statement_block") {
|
|
8567
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8632
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "js");
|
|
8568
8633
|
allBlocks.push(...blocks);
|
|
8569
8634
|
allEdges.push(...edges);
|
|
8570
8635
|
blockIdCounter = nextId;
|
|
@@ -8586,7 +8651,7 @@ function buildCFG(tree, language, cache) {
|
|
|
8586
8651
|
for (const method of methods) {
|
|
8587
8652
|
const body2 = method.childForFieldName("body");
|
|
8588
8653
|
if (!body2) continue;
|
|
8589
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
8654
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8590
8655
|
allBlocks.push(...blocks);
|
|
8591
8656
|
allEdges.push(...edges);
|
|
8592
8657
|
blockIdCounter = nextId;
|
|
@@ -8594,7 +8659,28 @@ function buildCFG(tree, language, cache) {
|
|
|
8594
8659
|
}
|
|
8595
8660
|
return { blocks: allBlocks, edges: allEdges };
|
|
8596
8661
|
}
|
|
8597
|
-
function
|
|
8662
|
+
function buildCSharpCFG(tree, blockIdCounter, cache) {
|
|
8663
|
+
const allBlocks = [];
|
|
8664
|
+
const allEdges = [];
|
|
8665
|
+
const containers = [
|
|
8666
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache),
|
|
8667
|
+
...getNodesFromCache(tree.rootNode, "constructor_declaration", cache),
|
|
8668
|
+
...getNodesFromCache(tree.rootNode, "destructor_declaration", cache),
|
|
8669
|
+
...getNodesFromCache(tree.rootNode, "operator_declaration", cache),
|
|
8670
|
+
...getNodesFromCache(tree.rootNode, "local_function_statement", cache),
|
|
8671
|
+
...getNodesFromCache(tree.rootNode, "accessor_declaration", cache)
|
|
8672
|
+
];
|
|
8673
|
+
for (const container of containers) {
|
|
8674
|
+
const body2 = container.childForFieldName("body");
|
|
8675
|
+
if (!body2 || body2.type !== "block") continue;
|
|
8676
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "csharp");
|
|
8677
|
+
allBlocks.push(...blocks);
|
|
8678
|
+
allEdges.push(...edges);
|
|
8679
|
+
blockIdCounter = nextId;
|
|
8680
|
+
}
|
|
8681
|
+
return { blocks: allBlocks, edges: allEdges };
|
|
8682
|
+
}
|
|
8683
|
+
function buildMethodCFG(body2, startId, dialect) {
|
|
8598
8684
|
const blocks = [];
|
|
8599
8685
|
const edges = [];
|
|
8600
8686
|
let currentId = startId;
|
|
@@ -8605,7 +8691,7 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8605
8691
|
end_line: body2.startPosition.row + 1
|
|
8606
8692
|
};
|
|
8607
8693
|
blocks.push(entryBlock);
|
|
8608
|
-
const result = processStatements(body2, currentId, blocks, edges,
|
|
8694
|
+
const result = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8609
8695
|
currentId = result.nextId;
|
|
8610
8696
|
if (result.entryId !== -1) {
|
|
8611
8697
|
edges.push({
|
|
@@ -8637,15 +8723,15 @@ function buildMethodCFG(body2, startId, isJavaScript) {
|
|
|
8637
8723
|
}
|
|
8638
8724
|
return { blocks, edges, nextId: currentId };
|
|
8639
8725
|
}
|
|
8640
|
-
function processStatements(container, startId, blocks, edges,
|
|
8726
|
+
function processStatements(container, startId, blocks, edges, dialect) {
|
|
8641
8727
|
let currentId = startId;
|
|
8642
8728
|
let firstBlockId = -1;
|
|
8643
8729
|
let lastExitIds = [];
|
|
8644
8730
|
for (let i2 = 0; i2 < container.childCount; i2++) {
|
|
8645
8731
|
const stmt = container.child(i2);
|
|
8646
8732
|
if (!stmt) continue;
|
|
8647
|
-
if (!isStatement(stmt,
|
|
8648
|
-
const result = processStatement(stmt, currentId, blocks, edges,
|
|
8733
|
+
if (!isStatement(stmt, dialect)) continue;
|
|
8734
|
+
const result = processStatement(stmt, currentId, blocks, edges, dialect);
|
|
8649
8735
|
currentId = result.nextId;
|
|
8650
8736
|
if (firstBlockId === -1) {
|
|
8651
8737
|
firstBlockId = result.entryId;
|
|
@@ -8666,31 +8752,49 @@ function processStatements(container, startId, blocks, edges, isJavaScript) {
|
|
|
8666
8752
|
nextId: currentId
|
|
8667
8753
|
};
|
|
8668
8754
|
}
|
|
8669
|
-
function processStatement(stmt, startId, blocks, edges,
|
|
8755
|
+
function processStatement(stmt, startId, blocks, edges, dialect) {
|
|
8670
8756
|
switch (stmt.type) {
|
|
8671
8757
|
case "if_statement":
|
|
8672
|
-
return processIfStatement(stmt, startId, blocks, edges,
|
|
8758
|
+
return processIfStatement(stmt, startId, blocks, edges, dialect);
|
|
8673
8759
|
case "for_statement":
|
|
8674
8760
|
case "enhanced_for_statement":
|
|
8675
8761
|
case "for_in_statement":
|
|
8676
8762
|
case "for_of_statement":
|
|
8677
|
-
|
|
8763
|
+
case "foreach_statement":
|
|
8764
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8678
8765
|
case "while_statement":
|
|
8679
|
-
return processWhileStatement(stmt, startId, blocks, edges,
|
|
8766
|
+
return processWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8680
8767
|
case "do_statement":
|
|
8681
|
-
return processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8768
|
+
return processDoWhileStatement(stmt, startId, blocks, edges, dialect);
|
|
8682
8769
|
case "try_statement":
|
|
8683
|
-
return processTryStatement(stmt, startId, blocks, edges,
|
|
8770
|
+
return processTryStatement(stmt, startId, blocks, edges, dialect);
|
|
8684
8771
|
case "switch_expression":
|
|
8685
8772
|
case "switch_statement":
|
|
8686
|
-
return processSwitchStatement(stmt, startId, blocks, edges,
|
|
8773
|
+
return processSwitchStatement(stmt, startId, blocks, edges, dialect);
|
|
8774
|
+
// C# scoped blocks add no branching — flow through the inner block so its
|
|
8775
|
+
// nested statements are still captured.
|
|
8776
|
+
case "using_statement":
|
|
8777
|
+
case "lock_statement":
|
|
8778
|
+
case "checked_statement":
|
|
8779
|
+
case "unsafe_statement": {
|
|
8780
|
+
const inner = stmt.childForFieldName("body") ?? lastBlockChild(stmt);
|
|
8781
|
+
if (inner) return processStatement(inner, startId, blocks, edges, dialect);
|
|
8782
|
+
return processSimpleStatement(stmt, startId, blocks);
|
|
8783
|
+
}
|
|
8687
8784
|
case "block":
|
|
8688
8785
|
case "statement_block":
|
|
8689
|
-
return processStatements(stmt, startId, blocks, edges,
|
|
8786
|
+
return processStatements(stmt, startId, blocks, edges, dialect);
|
|
8690
8787
|
default:
|
|
8691
8788
|
return processSimpleStatement(stmt, startId, blocks);
|
|
8692
8789
|
}
|
|
8693
8790
|
}
|
|
8791
|
+
function lastBlockChild(node) {
|
|
8792
|
+
for (let i2 = node.childCount - 1; i2 >= 0; i2--) {
|
|
8793
|
+
const c = node.child(i2);
|
|
8794
|
+
if (c && c.type === "block") return c;
|
|
8795
|
+
}
|
|
8796
|
+
return null;
|
|
8797
|
+
}
|
|
8694
8798
|
function processSimpleStatement(stmt, startId, blocks) {
|
|
8695
8799
|
const block = {
|
|
8696
8800
|
id: startId,
|
|
@@ -8705,7 +8809,7 @@ function processSimpleStatement(stmt, startId, blocks) {
|
|
|
8705
8809
|
nextId: startId + 1
|
|
8706
8810
|
};
|
|
8707
8811
|
}
|
|
8708
|
-
function processIfStatement(stmt, startId, blocks, edges,
|
|
8812
|
+
function processIfStatement(stmt, startId, blocks, edges, dialect) {
|
|
8709
8813
|
let currentId = startId;
|
|
8710
8814
|
const condBlock = {
|
|
8711
8815
|
id: currentId++,
|
|
@@ -8717,7 +8821,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8717
8821
|
const exitIds = [];
|
|
8718
8822
|
const consequence = stmt.childForFieldName("consequence");
|
|
8719
8823
|
if (consequence) {
|
|
8720
|
-
const thenResult = processStatement(consequence, currentId, blocks, edges,
|
|
8824
|
+
const thenResult = processStatement(consequence, currentId, blocks, edges, dialect);
|
|
8721
8825
|
currentId = thenResult.nextId;
|
|
8722
8826
|
edges.push({
|
|
8723
8827
|
from: condBlock.id,
|
|
@@ -8728,7 +8832,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8728
8832
|
}
|
|
8729
8833
|
const alternative = stmt.childForFieldName("alternative");
|
|
8730
8834
|
if (alternative) {
|
|
8731
|
-
const elseResult = processStatement(alternative, currentId, blocks, edges,
|
|
8835
|
+
const elseResult = processStatement(alternative, currentId, blocks, edges, dialect);
|
|
8732
8836
|
currentId = elseResult.nextId;
|
|
8733
8837
|
edges.push({
|
|
8734
8838
|
from: condBlock.id,
|
|
@@ -8745,7 +8849,7 @@ function processIfStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8745
8849
|
nextId: currentId
|
|
8746
8850
|
};
|
|
8747
8851
|
}
|
|
8748
|
-
function processForStatement(stmt, startId, blocks, edges,
|
|
8852
|
+
function processForStatement(stmt, startId, blocks, edges, dialect) {
|
|
8749
8853
|
let currentId = startId;
|
|
8750
8854
|
const loopBlock = {
|
|
8751
8855
|
id: currentId++,
|
|
@@ -8756,7 +8860,7 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8756
8860
|
blocks.push(loopBlock);
|
|
8757
8861
|
const body2 = stmt.childForFieldName("body");
|
|
8758
8862
|
if (body2) {
|
|
8759
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8863
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8760
8864
|
currentId = bodyResult.nextId;
|
|
8761
8865
|
edges.push({
|
|
8762
8866
|
from: loopBlock.id,
|
|
@@ -8778,16 +8882,16 @@ function processForStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8778
8882
|
nextId: currentId
|
|
8779
8883
|
};
|
|
8780
8884
|
}
|
|
8781
|
-
function processWhileStatement(stmt, startId, blocks, edges,
|
|
8782
|
-
return processForStatement(stmt, startId, blocks, edges,
|
|
8885
|
+
function processWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8886
|
+
return processForStatement(stmt, startId, blocks, edges, dialect);
|
|
8783
8887
|
}
|
|
8784
|
-
function processDoWhileStatement(stmt, startId, blocks, edges,
|
|
8888
|
+
function processDoWhileStatement(stmt, startId, blocks, edges, dialect) {
|
|
8785
8889
|
let currentId = startId;
|
|
8786
8890
|
const body2 = stmt.childForFieldName("body");
|
|
8787
8891
|
let bodyEntryId = currentId;
|
|
8788
8892
|
let bodyExitIds = [];
|
|
8789
8893
|
if (body2) {
|
|
8790
|
-
const bodyResult = processStatement(body2, currentId, blocks, edges,
|
|
8894
|
+
const bodyResult = processStatement(body2, currentId, blocks, edges, dialect);
|
|
8791
8895
|
currentId = bodyResult.nextId;
|
|
8792
8896
|
bodyEntryId = bodyResult.entryId;
|
|
8793
8897
|
bodyExitIds = bodyResult.exitIds;
|
|
@@ -8817,13 +8921,13 @@ function processDoWhileStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8817
8921
|
nextId: currentId
|
|
8818
8922
|
};
|
|
8819
8923
|
}
|
|
8820
|
-
function processTryStatement(stmt, startId, blocks, edges,
|
|
8924
|
+
function processTryStatement(stmt, startId, blocks, edges, dialect) {
|
|
8821
8925
|
let currentId = startId;
|
|
8822
8926
|
const exitIds = [];
|
|
8823
8927
|
const body2 = stmt.childForFieldName("body");
|
|
8824
8928
|
let tryEntryId = -1;
|
|
8825
8929
|
if (body2) {
|
|
8826
|
-
const bodyResult = processStatements(body2, currentId, blocks, edges,
|
|
8930
|
+
const bodyResult = processStatements(body2, currentId, blocks, edges, dialect);
|
|
8827
8931
|
currentId = bodyResult.nextId;
|
|
8828
8932
|
tryEntryId = bodyResult.entryId;
|
|
8829
8933
|
exitIds.push(...bodyResult.exitIds);
|
|
@@ -8833,7 +8937,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8833
8937
|
if (child?.type === "catch_clause") {
|
|
8834
8938
|
const catchBody = child.childForFieldName("body");
|
|
8835
8939
|
if (catchBody) {
|
|
8836
|
-
const catchResult = processStatements(catchBody, currentId, blocks, edges,
|
|
8940
|
+
const catchResult = processStatements(catchBody, currentId, blocks, edges, dialect);
|
|
8837
8941
|
currentId = catchResult.nextId;
|
|
8838
8942
|
if (tryEntryId !== -1) {
|
|
8839
8943
|
edges.push({
|
|
@@ -8846,9 +8950,18 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8846
8950
|
}
|
|
8847
8951
|
}
|
|
8848
8952
|
}
|
|
8849
|
-
|
|
8953
|
+
let finallyClause = stmt.childForFieldName("finally") ?? stmt.childForFieldName("finalizer");
|
|
8954
|
+
if (!finallyClause && dialect === "csharp") {
|
|
8955
|
+
for (let i2 = 0; i2 < stmt.childCount; i2++) {
|
|
8956
|
+
const child = stmt.child(i2);
|
|
8957
|
+
if (child?.type === "finally_clause") {
|
|
8958
|
+
finallyClause = lastBlockChild(child);
|
|
8959
|
+
break;
|
|
8960
|
+
}
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8850
8963
|
if (finallyClause) {
|
|
8851
|
-
const finallyResult = processStatements(finallyClause, currentId, blocks, edges,
|
|
8964
|
+
const finallyResult = processStatements(finallyClause, currentId, blocks, edges, dialect);
|
|
8852
8965
|
currentId = finallyResult.nextId;
|
|
8853
8966
|
for (const exitId of exitIds) {
|
|
8854
8967
|
edges.push({
|
|
@@ -8869,7 +8982,7 @@ function processTryStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8869
8982
|
nextId: currentId
|
|
8870
8983
|
};
|
|
8871
8984
|
}
|
|
8872
|
-
function processSwitchStatement(stmt, startId, blocks, edges,
|
|
8985
|
+
function processSwitchStatement(stmt, startId, blocks, edges, dialect) {
|
|
8873
8986
|
let currentId = startId;
|
|
8874
8987
|
const switchBlock = {
|
|
8875
8988
|
id: currentId++,
|
|
@@ -8881,11 +8994,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
8881
8994
|
const exitIds = [];
|
|
8882
8995
|
const body2 = stmt.childForFieldName("body");
|
|
8883
8996
|
if (body2) {
|
|
8884
|
-
const caseTypes =
|
|
8997
|
+
const caseTypes = dialect === "js" ? ["switch_case", "switch_default"] : dialect === "csharp" ? ["switch_section"] : ["switch_block_statement_group", "switch_rule"];
|
|
8885
8998
|
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
8886
8999
|
const child = body2.child(i2);
|
|
8887
9000
|
if (child && caseTypes.includes(child.type)) {
|
|
8888
|
-
const caseResult = processStatements(child, currentId, blocks, edges,
|
|
9001
|
+
const caseResult = processStatements(child, currentId, blocks, edges, dialect);
|
|
8889
9002
|
currentId = caseResult.nextId;
|
|
8890
9003
|
if (caseResult.entryId !== -1) {
|
|
8891
9004
|
edges.push({
|
|
@@ -8915,7 +9028,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8915
9028
|
for (const func2 of functions) {
|
|
8916
9029
|
const body2 = func2.childForFieldName("body");
|
|
8917
9030
|
if (!body2) continue;
|
|
8918
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
9031
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
8919
9032
|
allBlocks.push(...blocks);
|
|
8920
9033
|
allEdges.push(...edges);
|
|
8921
9034
|
blockIdCounter = nextId;
|
|
@@ -8938,7 +9051,7 @@ function buildBashCFG(tree, startId, cache) {
|
|
|
8938
9051
|
let lastExitIds = [];
|
|
8939
9052
|
let firstBlockId = -1;
|
|
8940
9053
|
for (const stmt of topLevelStatements) {
|
|
8941
|
-
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges,
|
|
9054
|
+
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, "java");
|
|
8942
9055
|
blockIdCounter = result.nextId;
|
|
8943
9056
|
if (firstBlockId === -1) {
|
|
8944
9057
|
firstBlockId = result.entryId;
|
|
@@ -8982,7 +9095,8 @@ function isBashStatement(node) {
|
|
|
8982
9095
|
]);
|
|
8983
9096
|
return bashStatementTypes.has(node.type);
|
|
8984
9097
|
}
|
|
8985
|
-
function isStatement(node,
|
|
9098
|
+
function isStatement(node, dialect) {
|
|
9099
|
+
if (dialect === "csharp") return csharpStatementTypes.has(node.type);
|
|
8986
9100
|
const javaStatementTypes = /* @__PURE__ */ new Set([
|
|
8987
9101
|
"local_variable_declaration",
|
|
8988
9102
|
"expression_statement",
|
|
@@ -9026,8 +9140,30 @@ function isStatement(node, isJavaScript) {
|
|
|
9026
9140
|
"export_statement",
|
|
9027
9141
|
"import_statement"
|
|
9028
9142
|
]);
|
|
9029
|
-
return
|
|
9030
|
-
}
|
|
9143
|
+
return dialect === "js" ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
|
|
9144
|
+
}
|
|
9145
|
+
var csharpStatementTypes = /* @__PURE__ */ new Set([
|
|
9146
|
+
"local_declaration_statement",
|
|
9147
|
+
"expression_statement",
|
|
9148
|
+
"if_statement",
|
|
9149
|
+
"for_statement",
|
|
9150
|
+
"foreach_statement",
|
|
9151
|
+
"while_statement",
|
|
9152
|
+
"do_statement",
|
|
9153
|
+
"try_statement",
|
|
9154
|
+
"switch_statement",
|
|
9155
|
+
"return_statement",
|
|
9156
|
+
"throw_statement",
|
|
9157
|
+
"break_statement",
|
|
9158
|
+
"continue_statement",
|
|
9159
|
+
"using_statement",
|
|
9160
|
+
"lock_statement",
|
|
9161
|
+
"checked_statement",
|
|
9162
|
+
"unsafe_statement",
|
|
9163
|
+
"yield_statement",
|
|
9164
|
+
"goto_statement",
|
|
9165
|
+
"block"
|
|
9166
|
+
]);
|
|
9031
9167
|
function buildGoCFG(tree, blockIdCounter, cache) {
|
|
9032
9168
|
const allBlocks = [];
|
|
9033
9169
|
const allEdges = [];
|
|
@@ -9038,7 +9174,7 @@ function buildGoCFG(tree, blockIdCounter, cache) {
|
|
|
9038
9174
|
for (const func2 of functions) {
|
|
9039
9175
|
const body2 = func2.childForFieldName("body");
|
|
9040
9176
|
if (!body2 || body2.type !== "block") continue;
|
|
9041
|
-
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter,
|
|
9177
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, "java");
|
|
9042
9178
|
allBlocks.push(...blocks);
|
|
9043
9179
|
allEdges.push(...edges);
|
|
9044
9180
|
blockIdCounter = nextId;
|
|
@@ -13118,6 +13254,11 @@ var DEFAULT_SINKS = [
|
|
|
13118
13254
|
// ADO.NET `cmd.CommandText = "…" + x` — emitted as a synthetic call by
|
|
13119
13255
|
// extractCSharpCalls (property-assignment sink; the ctor-arg sink misses it).
|
|
13120
13256
|
{ method: "CommandText", type: "sql_injection", cwe: "CWE-89", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13257
|
+
// `DirectorySearcher.Filter = "(uid=" + x + ")"` — LDAP injection (cognium-ai#272).
|
|
13258
|
+
// Emitted as a synthetic call by extractCSharpCalls ONLY when the receiver
|
|
13259
|
+
// resolves to a DirectorySearcher, so this classless entry never over-matches
|
|
13260
|
+
// other `.Filter =` assignments (DataView/BindingSource/collection filters).
|
|
13261
|
+
{ method: "Filter", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13121
13262
|
// ADO.NET `cmd.Execute*()` — object-carried sink (cognium-dev#271). The taint
|
|
13122
13263
|
// rides the SqlCommand receiver (CommandText set from tainted data); the
|
|
13123
13264
|
// receiver is surfaced as arg[0] by extractCSharpCalls, and the command object
|
|
@@ -13134,6 +13275,9 @@ var DEFAULT_SINKS = [
|
|
|
13134
13275
|
// injectable — the second is the argv path where taint rides arg[1] (#276).
|
|
13135
13276
|
{ method: "Start", class: "Process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13136
13277
|
{ method: "ProcessStartInfo", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13278
|
+
// P/Invoke of libc `system(cmd)` — lowercase `system` in C# is virtually
|
|
13279
|
+
// always the imported shell entry point (cognium-ai#275 interop/IlPinvoke).
|
|
13280
|
+
{ method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13137
13281
|
// C# path traversal — System.IO file APIs (CWE-22). Distinctive method names.
|
|
13138
13282
|
{ method: "ReadAllText", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13139
13283
|
{ method: "ReadAllBytes", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13162,6 +13306,9 @@ var DEFAULT_SINKS = [
|
|
|
13162
13306
|
{ method: "Delete", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13163
13307
|
{ method: "GetFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13164
13308
|
{ method: "EnumerateFiles", class: "Directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13309
|
+
// ASP.NET `ControllerBase.PhysicalFile(path, contentType)` serves a file from
|
|
13310
|
+
// an absolute disk path — attacker-controllable path is CWE-22 (cognium-ai#326/#275).
|
|
13311
|
+
{ method: "PhysicalFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13165
13312
|
// C# SSRF — HttpClient / WebClient / WebRequest (CWE-918).
|
|
13166
13313
|
{ method: "GetAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13167
13314
|
{ method: "PostAsync", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13186,6 +13333,17 @@ var DEFAULT_SINKS = [
|
|
|
13186
13333
|
{ method: "Load", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13187
13334
|
{ method: "LoadFrom", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13188
13335
|
{ method: "LoadFile", class: "Assembly", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13336
|
+
// C# server-side template injection (SSTI). Compiling an attacker-controlled
|
|
13337
|
+
// template string is arbitrary code execution — classified as code_injection
|
|
13338
|
+
// (CWE-94), matching how Python (Jinja2/Mako) and Node SSTI are modelled.
|
|
13339
|
+
// Restricted to DISTINCTIVE template-compile APIs so the generic
|
|
13340
|
+
// `Template.Parse` / `.Compile` names (int.Parse, Regex.Compile, …) are not
|
|
13341
|
+
// over-matched. Taint-gated: a constant template never fires. `RunCompile`
|
|
13342
|
+
// = RazorEngine; `CompileRenderStringAsync` = RazorLight; `Compile` is
|
|
13343
|
+
// class-scoped to Handlebars. (cognium-dev#273)
|
|
13344
|
+
{ method: "RunCompile", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13345
|
+
{ method: "CompileRenderStringAsync", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0, 1], languages: ["csharp"] },
|
|
13346
|
+
{ method: "Compile", class: "Handlebars", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["csharp"] },
|
|
13189
13347
|
// C# insecure deserialization — the polymorphic BCL formatters that can
|
|
13190
13348
|
// instantiate arbitrary types named in the payload (CWE-502, cognium-ai#318).
|
|
13191
13349
|
// Each of these classes exists only to deserialize and is unsafe on untrusted
|
|
@@ -13200,6 +13358,14 @@ var DEFAULT_SINKS = [
|
|
|
13200
13358
|
{ method: "Raw", class: "Html", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13201
13359
|
{ method: "Write", class: "Response", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13202
13360
|
{ method: "HtmlString", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13361
|
+
// Blazor `new MarkupString(x)` renders its argument as raw HTML (the framework's
|
|
13362
|
+
// documented "trusted markup" escape hatch) — attacker-controlled input is XSS. (ca#275)
|
|
13363
|
+
{ method: "MarkupString", class: "constructor", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13364
|
+
// `String.Format(fmt, …)` with an attacker-controlled FORMAT string — CWE-134.
|
|
13365
|
+
// Taint-gated on arg 0 (the format), so ordinary `String.Format("...", user)` where
|
|
13366
|
+
// only an argument is tainted never fires. .NET composite formatting can't corrupt
|
|
13367
|
+
// memory, so medium (FormatException DoS / unintended arg access), unlike C printf.
|
|
13368
|
+
{ method: "Format", class: "String", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["csharp"] },
|
|
13203
13369
|
// C# LDAP injection — System.DirectoryServices (CWE-90). The user-built
|
|
13204
13370
|
// filter is the constructor argument.
|
|
13205
13371
|
{ method: "DirectorySearcher", type: "ldap_injection", cwe: "CWE-90", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -13238,6 +13404,10 @@ var DEFAULT_SINKS = [
|
|
|
13238
13404
|
{ method: "SelectSingleNode", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13239
13405
|
{ method: "SelectNodes", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13240
13406
|
{ method: "Compile", class: "XPathExpression", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13407
|
+
// XPathNavigator.Select/Evaluate — class-scoped (both names collide with LINQ
|
|
13408
|
+
// `.Select`/`.Evaluate`, so they must resolve to an XPathNavigator receiver).
|
|
13409
|
+
{ method: "Select", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13410
|
+
{ method: "Evaluate", class: "XPathNavigator", type: "xpath_injection", cwe: "CWE-643", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13241
13411
|
// C# XXE — untrusted XML into a parser without DTD hardening (CWE-611).
|
|
13242
13412
|
{ method: "LoadXml", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
13243
13413
|
{ method: "Load", class: "XmlDocument", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0], languages: ["csharp"] },
|
|
@@ -14206,7 +14376,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14206
14376
|
const skipMethods = ["toString", "hashCode", "equals", "compareTo"];
|
|
14207
14377
|
if (skipMethods.includes(method.name)) continue;
|
|
14208
14378
|
for (const param of method.parameters) {
|
|
14209
|
-
const
|
|
14379
|
+
const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
|
|
14380
|
+
const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
|
|
14210
14381
|
if (isTaintable) {
|
|
14211
14382
|
const paramLine = param.line ?? method.start_line;
|
|
14212
14383
|
sources.push({
|
|
@@ -14326,6 +14497,13 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
14326
14497
|
}
|
|
14327
14498
|
return result;
|
|
14328
14499
|
}
|
|
14500
|
+
var CSHARP_BINDING_ATTRS = /* @__PURE__ */ new Set([
|
|
14501
|
+
"FromBody",
|
|
14502
|
+
"FromQuery",
|
|
14503
|
+
"FromRoute",
|
|
14504
|
+
"FromForm",
|
|
14505
|
+
"FromHeader"
|
|
14506
|
+
]);
|
|
14329
14507
|
function isInterproceduralTaintableType(typeName, language) {
|
|
14330
14508
|
const baseType = typeName.split("<")[0].trim();
|
|
14331
14509
|
const excludedTypes = [
|
|
@@ -14564,33 +14742,195 @@ function isSafeJSChildProcessCall(call, pattern, language) {
|
|
|
14564
14742
|
if (SHELL_PROGRAMS.has(program)) return false;
|
|
14565
14743
|
return true;
|
|
14566
14744
|
}
|
|
14567
|
-
|
|
14745
|
+
var CSHARP_SHELL_PROGRAMS = /* @__PURE__ */ new Set([
|
|
14746
|
+
"sh",
|
|
14747
|
+
"bash",
|
|
14748
|
+
"zsh",
|
|
14749
|
+
"dash",
|
|
14750
|
+
"ash",
|
|
14751
|
+
"ksh",
|
|
14752
|
+
"cmd",
|
|
14753
|
+
"powershell",
|
|
14754
|
+
"pwsh"
|
|
14755
|
+
]);
|
|
14756
|
+
function isConstNonShellExe(raw) {
|
|
14757
|
+
if (!raw) return false;
|
|
14758
|
+
const t = raw.trim();
|
|
14759
|
+
if (!/^@?"[^"]*"$/.test(t)) return false;
|
|
14760
|
+
const program = (t.replace(/^@?"|"$/g, "").split(/[\\/]/).pop() ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
14761
|
+
return !CSHARP_SHELL_PROGRAMS.has(program);
|
|
14762
|
+
}
|
|
14763
|
+
var PSI_CTOR_EXE_RE = /\bnew\s+ProcessStartInfo\s*(?:<[^>]*>)?\s*\(\s*(@?"[^"]*")/;
|
|
14764
|
+
function processStartInfoExe(expr, sourceLines) {
|
|
14765
|
+
const inline = PSI_CTOR_EXE_RE.exec(expr);
|
|
14766
|
+
if (inline) return inline[1];
|
|
14767
|
+
if (sourceLines && /^[A-Za-z_]\w*$/.test(expr.trim())) {
|
|
14768
|
+
const varName = expr.trim();
|
|
14769
|
+
const assignRe = new RegExp(
|
|
14770
|
+
`\\b${varName}\\s*=\\s*new\\s+ProcessStartInfo\\s*(?:<[^>]*>)?\\s*\\(\\s*(@?"[^"]*")`
|
|
14771
|
+
);
|
|
14772
|
+
for (const line of sourceLines) {
|
|
14773
|
+
const m = assignRe.exec(line);
|
|
14774
|
+
if (m) return m[1];
|
|
14775
|
+
}
|
|
14776
|
+
}
|
|
14777
|
+
return null;
|
|
14778
|
+
}
|
|
14779
|
+
function isSafeCSharpProcessStartCall(call, pattern, language, sourceLines) {
|
|
14568
14780
|
if (language !== "csharp") return false;
|
|
14569
14781
|
if (pattern.type !== "command_injection") return false;
|
|
14570
|
-
|
|
14571
|
-
if (
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14782
|
+
const method = call.method_name;
|
|
14783
|
+
if (method !== "Start" && method !== "ProcessStartInfo") return false;
|
|
14784
|
+
if (call.arguments.length >= 2) {
|
|
14785
|
+
const fileArg = call.arguments.find((a) => a.position === 0);
|
|
14786
|
+
const raw = fileArg?.literal != null ? String(fileArg.literal) : fileArg?.expression;
|
|
14787
|
+
return isConstNonShellExe(raw);
|
|
14788
|
+
}
|
|
14789
|
+
if (method === "Start" && call.arguments.length === 1) {
|
|
14790
|
+
const arg0 = call.arguments.find((a) => a.position === 0);
|
|
14791
|
+
const expr = (arg0?.expression ?? "").trim();
|
|
14792
|
+
return isConstNonShellExe(processStartInfoExe(expr, sourceLines));
|
|
14793
|
+
}
|
|
14794
|
+
return false;
|
|
14795
|
+
}
|
|
14796
|
+
function stripCsLiterals(line) {
|
|
14797
|
+
return line.replace(/\/\/.*$/, "").replace(/@?"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)'/g, "''");
|
|
14798
|
+
}
|
|
14799
|
+
function csBraceDepthBefore(lines, idx) {
|
|
14800
|
+
let depth = 0;
|
|
14801
|
+
for (let i2 = 0; i2 < idx && i2 < lines.length; i2++) {
|
|
14802
|
+
for (const ch of stripCsLiterals(lines[i2])) {
|
|
14803
|
+
if (ch === "{") depth++;
|
|
14804
|
+
else if (ch === "}") depth--;
|
|
14805
|
+
}
|
|
14806
|
+
}
|
|
14807
|
+
return depth;
|
|
14808
|
+
}
|
|
14809
|
+
function netBraces(fragment) {
|
|
14810
|
+
let n = 0;
|
|
14811
|
+
for (const ch of fragment) {
|
|
14812
|
+
if (ch === "{") n++;
|
|
14813
|
+
else if (ch === "}") n--;
|
|
14814
|
+
}
|
|
14815
|
+
return n;
|
|
14816
|
+
}
|
|
14817
|
+
function splitCsIf(line) {
|
|
14818
|
+
const m = /\bif\s*\(/.exec(line);
|
|
14819
|
+
if (!m) return null;
|
|
14820
|
+
const ifCol = m.index;
|
|
14821
|
+
let i2 = m.index + m[0].length;
|
|
14822
|
+
let depth = 1;
|
|
14823
|
+
let inStr = false;
|
|
14824
|
+
let strCh = "";
|
|
14825
|
+
for (; i2 < line.length; i2++) {
|
|
14826
|
+
const c = line[i2];
|
|
14827
|
+
if (inStr) {
|
|
14828
|
+
if (c === "\\") {
|
|
14829
|
+
i2++;
|
|
14830
|
+
continue;
|
|
14831
|
+
}
|
|
14832
|
+
if (c === strCh) inStr = false;
|
|
14833
|
+
continue;
|
|
14834
|
+
}
|
|
14835
|
+
if (c === '"' || c === "'") {
|
|
14836
|
+
inStr = true;
|
|
14837
|
+
strCh = c;
|
|
14838
|
+
continue;
|
|
14839
|
+
}
|
|
14840
|
+
if (c === "(") depth++;
|
|
14841
|
+
else if (c === ")") {
|
|
14842
|
+
depth--;
|
|
14843
|
+
if (depth === 0) break;
|
|
14844
|
+
}
|
|
14845
|
+
}
|
|
14846
|
+
if (depth !== 0) return null;
|
|
14847
|
+
return { cond: line.slice(m.index + m[0].length, i2), rest: line.slice(i2 + 1), ifCol };
|
|
14848
|
+
}
|
|
14849
|
+
function csEqualityGuard(cond) {
|
|
14850
|
+
const c = cond.trim();
|
|
14851
|
+
let m = /^(.*?)\s*(==|!=)\s*@?"[^"]*"\s*$/.exec(c);
|
|
14852
|
+
if (m) return { op: m[2], exprSide: m[1].trim() };
|
|
14853
|
+
m = /^@?"[^"]*"\s*(==|!=)\s*(.*)$/.exec(c);
|
|
14854
|
+
if (m) return { op: m[1], exprSide: m[2].trim() };
|
|
14855
|
+
return null;
|
|
14856
|
+
}
|
|
14857
|
+
var CS_IDENT_RE = /[A-Za-z_]\w*/g;
|
|
14858
|
+
function csIdentifiers(expr) {
|
|
14859
|
+
return new Set(expr.match(CS_IDENT_RE) ?? []);
|
|
14860
|
+
}
|
|
14861
|
+
function csThenBlock(lines, ifIdx, rest) {
|
|
14862
|
+
const isExit = (s) => /^\s*(?:return|throw|continue|break)\b/.test(s);
|
|
14863
|
+
let firstIdx;
|
|
14864
|
+
let firstText;
|
|
14865
|
+
if (rest.trim()) {
|
|
14866
|
+
firstIdx = ifIdx;
|
|
14867
|
+
firstText = rest;
|
|
14577
14868
|
} else {
|
|
14578
|
-
|
|
14869
|
+
let j = ifIdx + 1;
|
|
14870
|
+
while (j < lines.length && stripCsLiterals(lines[j]).trim() === "") j++;
|
|
14871
|
+
firstIdx = j;
|
|
14872
|
+
firstText = lines[j] ?? "";
|
|
14579
14873
|
}
|
|
14580
|
-
if (
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
"
|
|
14585
|
-
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14874
|
+
if (firstText.trim().startsWith("{")) {
|
|
14875
|
+
let depth = 0;
|
|
14876
|
+
let started = false;
|
|
14877
|
+
let endLine = lines.length - 1;
|
|
14878
|
+
let body2 = "";
|
|
14879
|
+
for (let i2 = firstIdx; i2 < lines.length; i2++) {
|
|
14880
|
+
const stripped = stripCsLiterals(i2 === firstIdx ? firstText : lines[i2]);
|
|
14881
|
+
let broke = false;
|
|
14882
|
+
for (const ch of stripped) {
|
|
14883
|
+
if (ch === "{") {
|
|
14884
|
+
depth++;
|
|
14885
|
+
started = true;
|
|
14886
|
+
if (depth === 1) continue;
|
|
14887
|
+
} else if (ch === "}") {
|
|
14888
|
+
depth--;
|
|
14889
|
+
if (depth === 0) {
|
|
14890
|
+
endLine = i2;
|
|
14891
|
+
broke = true;
|
|
14892
|
+
break;
|
|
14893
|
+
}
|
|
14894
|
+
}
|
|
14895
|
+
if (started && depth >= 1) body2 += ch;
|
|
14896
|
+
}
|
|
14897
|
+
if (broke) break;
|
|
14898
|
+
if (started) body2 += " ";
|
|
14899
|
+
}
|
|
14900
|
+
return { start: ifIdx, end: endLine, earlyExit: isExit(body2.trim()) };
|
|
14901
|
+
}
|
|
14902
|
+
return { start: ifIdx, end: firstIdx, earlyExit: isExit(firstText.trim()) };
|
|
14903
|
+
}
|
|
14904
|
+
function isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines) {
|
|
14905
|
+
if (language !== "csharp") return false;
|
|
14906
|
+
if (pattern.type !== "ssrf") return false;
|
|
14907
|
+
if (!sourceLines || sourceLines.length === 0) return false;
|
|
14908
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
14909
|
+
for (const a of call.arguments) {
|
|
14910
|
+
if (a.variable) candidates.add(a.variable);
|
|
14911
|
+
for (const id of csIdentifiers(a.expression ?? "")) candidates.add(id);
|
|
14912
|
+
}
|
|
14913
|
+
if (candidates.size === 0) return false;
|
|
14914
|
+
const sinkIdx = call.location.line - 1;
|
|
14915
|
+
const sinkDepth = csBraceDepthBefore(sourceLines, sinkIdx);
|
|
14916
|
+
for (let i2 = 0; i2 < sinkIdx; i2++) {
|
|
14917
|
+
const parts2 = splitCsIf(sourceLines[i2]);
|
|
14918
|
+
if (!parts2) continue;
|
|
14919
|
+
const guard = csEqualityGuard(parts2.cond);
|
|
14920
|
+
if (!guard) continue;
|
|
14921
|
+
const guardIds = csIdentifiers(guard.exprSide);
|
|
14922
|
+
if (![...guardIds].some((id) => candidates.has(id))) continue;
|
|
14923
|
+
const block = csThenBlock(sourceLines, i2, parts2.rest);
|
|
14924
|
+
if (guard.op === "==") {
|
|
14925
|
+
if (sinkIdx >= block.start && sinkIdx <= block.end) return true;
|
|
14926
|
+
} else {
|
|
14927
|
+
const guardDepth = csBraceDepthBefore(sourceLines, i2) + netBraces(stripCsLiterals(sourceLines[i2]).slice(0, parts2.ifCol));
|
|
14928
|
+
if (block.earlyExit && sinkIdx > block.end && guardDepth === sinkDepth) {
|
|
14929
|
+
return true;
|
|
14930
|
+
}
|
|
14931
|
+
}
|
|
14932
|
+
}
|
|
14933
|
+
return false;
|
|
14594
14934
|
}
|
|
14595
14935
|
function isSafeRustCommandCall(call, pattern, language) {
|
|
14596
14936
|
if (language !== "rust") return false;
|
|
@@ -14847,7 +15187,10 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
14847
15187
|
if (isSafeJSChildProcessCall(call, pattern, language)) {
|
|
14848
15188
|
continue;
|
|
14849
15189
|
}
|
|
14850
|
-
if (isSafeCSharpProcessStartCall(call, pattern, language)) {
|
|
15190
|
+
if (isSafeCSharpProcessStartCall(call, pattern, language, sourceLines)) {
|
|
15191
|
+
continue;
|
|
15192
|
+
}
|
|
15193
|
+
if (isCSharpSsrfHostAllowlistGuarded(call, pattern, language, sourceLines)) {
|
|
14851
15194
|
continue;
|
|
14852
15195
|
}
|
|
14853
15196
|
if (pattern.safe_if_class_literal_at !== void 0 && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
|