circle-ir 3.19.5 → 3.21.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/passes/language-sources-pass.d.ts +2 -1
- package/dist/analysis/passes/language-sources-pass.js +216 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +361 -0
- package/dist/core/circle-ir-core.cjs +176 -0
- package/dist/core/circle-ir-core.js +176 -0
- package/dist/core/extractors/cfg.js +95 -0
- package/dist/core/extractors/cfg.js.map +1 -1
- package/dist/core/extractors/dfg.js +127 -0
- package/dist/core/extractors/dfg.js.map +1 -1
- package/package.json +1 -1
|
@@ -7450,6 +7450,9 @@ function buildCFG(tree, language) {
|
|
|
7450
7450
|
const allBlocks = [];
|
|
7451
7451
|
const allEdges = [];
|
|
7452
7452
|
let blockIdCounter = 0;
|
|
7453
|
+
if (effectiveLanguage === "bash") {
|
|
7454
|
+
return buildBashCFG(tree, blockIdCounter);
|
|
7455
|
+
}
|
|
7453
7456
|
if (isJavaScript) {
|
|
7454
7457
|
const functions = [
|
|
7455
7458
|
...findNodes(tree.rootNode, "function_declaration"),
|
|
@@ -7805,6 +7808,81 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
7805
7808
|
nextId: currentId
|
|
7806
7809
|
};
|
|
7807
7810
|
}
|
|
7811
|
+
function buildBashCFG(tree, startId) {
|
|
7812
|
+
const allBlocks = [];
|
|
7813
|
+
const allEdges = [];
|
|
7814
|
+
let blockIdCounter = startId;
|
|
7815
|
+
const functions = findNodes(tree.rootNode, "function_definition");
|
|
7816
|
+
for (const func2 of functions) {
|
|
7817
|
+
const body2 = func2.childForFieldName("body");
|
|
7818
|
+
if (!body2) continue;
|
|
7819
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, false);
|
|
7820
|
+
allBlocks.push(...blocks);
|
|
7821
|
+
allEdges.push(...edges);
|
|
7822
|
+
blockIdCounter = nextId;
|
|
7823
|
+
}
|
|
7824
|
+
const topLevelStatements = [];
|
|
7825
|
+
for (let i2 = 0; i2 < tree.rootNode.childCount; i2++) {
|
|
7826
|
+
const child = tree.rootNode.child(i2);
|
|
7827
|
+
if (child && child.type !== "function_definition" && isBashStatement(child)) {
|
|
7828
|
+
topLevelStatements.push(child);
|
|
7829
|
+
}
|
|
7830
|
+
}
|
|
7831
|
+
if (topLevelStatements.length > 0) {
|
|
7832
|
+
const entryBlock = {
|
|
7833
|
+
id: blockIdCounter++,
|
|
7834
|
+
type: "entry",
|
|
7835
|
+
start_line: topLevelStatements[0].startPosition.row + 1,
|
|
7836
|
+
end_line: topLevelStatements[0].startPosition.row + 1
|
|
7837
|
+
};
|
|
7838
|
+
allBlocks.push(entryBlock);
|
|
7839
|
+
let lastExitIds = [];
|
|
7840
|
+
let firstBlockId = -1;
|
|
7841
|
+
for (const stmt of topLevelStatements) {
|
|
7842
|
+
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, false);
|
|
7843
|
+
blockIdCounter = result.nextId;
|
|
7844
|
+
if (firstBlockId === -1) {
|
|
7845
|
+
firstBlockId = result.entryId;
|
|
7846
|
+
} else {
|
|
7847
|
+
for (const exitId of lastExitIds) {
|
|
7848
|
+
allEdges.push({ from: exitId, to: result.entryId, type: "sequential" });
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
lastExitIds = result.exitIds;
|
|
7852
|
+
}
|
|
7853
|
+
if (firstBlockId !== -1) {
|
|
7854
|
+
allEdges.push({ from: entryBlock.id, to: firstBlockId, type: "sequential" });
|
|
7855
|
+
}
|
|
7856
|
+
const exitBlock = {
|
|
7857
|
+
id: blockIdCounter++,
|
|
7858
|
+
type: "exit",
|
|
7859
|
+
start_line: topLevelStatements[topLevelStatements.length - 1].endPosition.row + 1,
|
|
7860
|
+
end_line: topLevelStatements[topLevelStatements.length - 1].endPosition.row + 1
|
|
7861
|
+
};
|
|
7862
|
+
allBlocks.push(exitBlock);
|
|
7863
|
+
for (const exitId of lastExitIds) {
|
|
7864
|
+
allEdges.push({ from: exitId, to: exitBlock.id, type: "sequential" });
|
|
7865
|
+
}
|
|
7866
|
+
}
|
|
7867
|
+
return { blocks: allBlocks, edges: allEdges };
|
|
7868
|
+
}
|
|
7869
|
+
function isBashStatement(node) {
|
|
7870
|
+
const bashStatementTypes = /* @__PURE__ */ new Set([
|
|
7871
|
+
"command",
|
|
7872
|
+
"variable_assignment",
|
|
7873
|
+
"if_statement",
|
|
7874
|
+
"for_statement",
|
|
7875
|
+
"while_statement",
|
|
7876
|
+
"case_statement",
|
|
7877
|
+
"pipeline",
|
|
7878
|
+
"list",
|
|
7879
|
+
"redirected_statement",
|
|
7880
|
+
"compound_statement",
|
|
7881
|
+
"subshell",
|
|
7882
|
+
"declaration_command"
|
|
7883
|
+
]);
|
|
7884
|
+
return bashStatementTypes.has(node.type);
|
|
7885
|
+
}
|
|
7808
7886
|
function isStatement(node, isJavaScript) {
|
|
7809
7887
|
const javaStatementTypes = /* @__PURE__ */ new Set([
|
|
7810
7888
|
"local_variable_declaration",
|
|
@@ -7895,6 +7973,9 @@ function buildDFG(tree, cache, language) {
|
|
|
7895
7973
|
if (effectiveLanguage === "rust") {
|
|
7896
7974
|
return buildRustDFG(tree, cache);
|
|
7897
7975
|
}
|
|
7976
|
+
if (effectiveLanguage === "bash") {
|
|
7977
|
+
return buildBashDFG(tree);
|
|
7978
|
+
}
|
|
7898
7979
|
return buildJavaDFG(tree, cache);
|
|
7899
7980
|
}
|
|
7900
7981
|
function buildJavaDFG(tree, cache) {
|
|
@@ -8621,6 +8702,101 @@ function computeChains(defs, uses) {
|
|
|
8621
8702
|
chains.sort((a, b) => a.from_def - b.from_def || a.to_def - b.to_def);
|
|
8622
8703
|
return chains;
|
|
8623
8704
|
}
|
|
8705
|
+
function buildBashDFG(tree) {
|
|
8706
|
+
const defs = [];
|
|
8707
|
+
const uses = [];
|
|
8708
|
+
let defIdCounter = 1;
|
|
8709
|
+
let useIdCounter = 1;
|
|
8710
|
+
const scopeStack = [/* @__PURE__ */ new Map()];
|
|
8711
|
+
const positionalParams = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*"];
|
|
8712
|
+
for (const p of positionalParams) {
|
|
8713
|
+
const def = {
|
|
8714
|
+
id: defIdCounter++,
|
|
8715
|
+
variable: p,
|
|
8716
|
+
line: 0,
|
|
8717
|
+
kind: "param"
|
|
8718
|
+
};
|
|
8719
|
+
defs.push(def);
|
|
8720
|
+
currentScope(scopeStack).set(p, def.id);
|
|
8721
|
+
}
|
|
8722
|
+
walkTree(tree.rootNode, (node) => {
|
|
8723
|
+
if (node.type === "variable_assignment") {
|
|
8724
|
+
const nameNode = node.childForFieldName("name");
|
|
8725
|
+
if (nameNode) {
|
|
8726
|
+
const varName = getNodeText(nameNode);
|
|
8727
|
+
const def = {
|
|
8728
|
+
id: defIdCounter++,
|
|
8729
|
+
variable: varName,
|
|
8730
|
+
line: node.startPosition.row + 1,
|
|
8731
|
+
kind: "local"
|
|
8732
|
+
};
|
|
8733
|
+
defs.push(def);
|
|
8734
|
+
currentScope(scopeStack).set(varName, def.id);
|
|
8735
|
+
}
|
|
8736
|
+
} else if (node.type === "command") {
|
|
8737
|
+
const nameNode = node.childForFieldName("name");
|
|
8738
|
+
if (nameNode && getNodeText(nameNode) === "read") {
|
|
8739
|
+
for (let i2 = 0; i2 < node.namedChildCount; i2++) {
|
|
8740
|
+
const arg = node.namedChild(i2);
|
|
8741
|
+
if (!arg || arg === nameNode) continue;
|
|
8742
|
+
if (arg.type === "word") {
|
|
8743
|
+
const text = getNodeText(arg);
|
|
8744
|
+
if (text.startsWith("-")) continue;
|
|
8745
|
+
const def = {
|
|
8746
|
+
id: defIdCounter++,
|
|
8747
|
+
variable: text,
|
|
8748
|
+
line: node.startPosition.row + 1,
|
|
8749
|
+
kind: "local"
|
|
8750
|
+
};
|
|
8751
|
+
defs.push(def);
|
|
8752
|
+
currentScope(scopeStack).set(text, def.id);
|
|
8753
|
+
}
|
|
8754
|
+
}
|
|
8755
|
+
}
|
|
8756
|
+
} else if (node.type === "for_statement") {
|
|
8757
|
+
const varNode = node.childForFieldName("variable");
|
|
8758
|
+
if (varNode) {
|
|
8759
|
+
const varName = getNodeText(varNode);
|
|
8760
|
+
const def = {
|
|
8761
|
+
id: defIdCounter++,
|
|
8762
|
+
variable: varName,
|
|
8763
|
+
line: node.startPosition.row + 1,
|
|
8764
|
+
kind: "local"
|
|
8765
|
+
};
|
|
8766
|
+
defs.push(def);
|
|
8767
|
+
currentScope(scopeStack).set(varName, def.id);
|
|
8768
|
+
}
|
|
8769
|
+
} else if (node.type === "simple_expansion") {
|
|
8770
|
+
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
8771
|
+
if (varNameNode) {
|
|
8772
|
+
const varName = getNodeText(varNameNode);
|
|
8773
|
+
if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
|
|
8774
|
+
const reachingDef = findReachingDef(varName, scopeStack);
|
|
8775
|
+
uses.push({
|
|
8776
|
+
id: useIdCounter++,
|
|
8777
|
+
variable: varName,
|
|
8778
|
+
line: node.startPosition.row + 1,
|
|
8779
|
+
def_id: reachingDef
|
|
8780
|
+
});
|
|
8781
|
+
}
|
|
8782
|
+
}
|
|
8783
|
+
} else if (node.type === "expansion") {
|
|
8784
|
+
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
8785
|
+
if (varNameNode && varNameNode.type === "variable_name") {
|
|
8786
|
+
const varName = getNodeText(varNameNode);
|
|
8787
|
+
const reachingDef = findReachingDef(varName, scopeStack);
|
|
8788
|
+
uses.push({
|
|
8789
|
+
id: useIdCounter++,
|
|
8790
|
+
variable: varName,
|
|
8791
|
+
line: node.startPosition.row + 1,
|
|
8792
|
+
def_id: reachingDef
|
|
8793
|
+
});
|
|
8794
|
+
}
|
|
8795
|
+
}
|
|
8796
|
+
});
|
|
8797
|
+
const chains = computeChains(defs, uses);
|
|
8798
|
+
return { defs, uses, chains };
|
|
8799
|
+
}
|
|
8624
8800
|
function buildRustDFG(tree, cache) {
|
|
8625
8801
|
const defs = [];
|
|
8626
8802
|
const uses = [];
|
|
@@ -18706,6 +18882,13 @@ var LanguageSourcesPass = class {
|
|
|
18706
18882
|
}
|
|
18707
18883
|
}
|
|
18708
18884
|
const jsTaintedVars = buildJavaScriptTaintedVars(code, language);
|
|
18885
|
+
if (language === "bash") {
|
|
18886
|
+
additionalSources.push(...findBashTaintSources(code, graph.ir.dfg));
|
|
18887
|
+
const bashFindings = findBashPatternFindings(code, graph.ir.meta.file);
|
|
18888
|
+
for (const finding of bashFindings) {
|
|
18889
|
+
ctx.addFinding(finding);
|
|
18890
|
+
}
|
|
18891
|
+
}
|
|
18709
18892
|
return { additionalSources, additionalSinks, pyTaintedVars, pySanitizedVars, jsTaintedVars };
|
|
18710
18893
|
}
|
|
18711
18894
|
};
|
|
@@ -19008,6 +19191,184 @@ function buildJavaScriptTaintedVars(sourceCode, language) {
|
|
|
19008
19191
|
}
|
|
19009
19192
|
return tainted;
|
|
19010
19193
|
}
|
|
19194
|
+
var BASH_UNTRUSTED_ENV_PATTERNS = [
|
|
19195
|
+
/^USER_INPUT$/i,
|
|
19196
|
+
/^QUERY_STRING$/i,
|
|
19197
|
+
/^REQUEST_/i,
|
|
19198
|
+
/^HTTP_/i,
|
|
19199
|
+
/^REMOTE_/i,
|
|
19200
|
+
/^CONTENT_TYPE$/i,
|
|
19201
|
+
/^CONTENT_LENGTH$/i,
|
|
19202
|
+
/^PATH_INFO$/i,
|
|
19203
|
+
/^SCRIPT_NAME$/i,
|
|
19204
|
+
/^SERVER_NAME$/i
|
|
19205
|
+
];
|
|
19206
|
+
var BASH_NETWORK_COMMANDS = /* @__PURE__ */ new Set(["curl", "wget", "nc", "ncat"]);
|
|
19207
|
+
var BASH_FILE_COMMANDS = /* @__PURE__ */ new Set(["cat", "head", "tail", "less", "more", "awk", "sed", "cut", "grep"]);
|
|
19208
|
+
function findBashTaintSources(sourceCode, dfg) {
|
|
19209
|
+
const sources = [];
|
|
19210
|
+
const lines = sourceCode.split("\n");
|
|
19211
|
+
const definedVars = new Set(dfg.defs.filter((d) => d.kind === "local").map((d) => d.variable));
|
|
19212
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
19213
|
+
const line = lines[i2];
|
|
19214
|
+
const trimmed = line.trim();
|
|
19215
|
+
const lineNumber = i2 + 1;
|
|
19216
|
+
if (trimmed.startsWith("#")) continue;
|
|
19217
|
+
const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
|
|
19218
|
+
let m;
|
|
19219
|
+
while ((m = positionalRe.exec(line)) !== null) {
|
|
19220
|
+
const param = m[1] ?? m[2];
|
|
19221
|
+
const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
|
|
19222
|
+
if (!alreadyExists) {
|
|
19223
|
+
sources.push({
|
|
19224
|
+
type: "io_input",
|
|
19225
|
+
location: `positional parameter $${param}`,
|
|
19226
|
+
severity: "high",
|
|
19227
|
+
line: lineNumber,
|
|
19228
|
+
confidence: 1,
|
|
19229
|
+
variable: param
|
|
19230
|
+
});
|
|
19231
|
+
}
|
|
19232
|
+
}
|
|
19233
|
+
const cmdSubAssign = trimmed.match(/^(\w+)=\$\((\w+)\s/);
|
|
19234
|
+
const cmdSubBacktick = trimmed.match(/^(\w+)=`(\w+)\s/);
|
|
19235
|
+
const csMatch = cmdSubAssign ?? cmdSubBacktick;
|
|
19236
|
+
if (csMatch) {
|
|
19237
|
+
const [, varName, cmd] = csMatch;
|
|
19238
|
+
if (BASH_NETWORK_COMMANDS.has(cmd)) {
|
|
19239
|
+
sources.push({
|
|
19240
|
+
type: "network_input",
|
|
19241
|
+
location: `${varName}=$(${cmd} ...) \u2014 network command output`,
|
|
19242
|
+
severity: "high",
|
|
19243
|
+
line: lineNumber,
|
|
19244
|
+
confidence: 0.9,
|
|
19245
|
+
variable: varName
|
|
19246
|
+
});
|
|
19247
|
+
} else if (BASH_FILE_COMMANDS.has(cmd)) {
|
|
19248
|
+
sources.push({
|
|
19249
|
+
type: "file_input",
|
|
19250
|
+
location: `${varName}=$(${cmd} ...) \u2014 file command output`,
|
|
19251
|
+
severity: "medium",
|
|
19252
|
+
line: lineNumber,
|
|
19253
|
+
confidence: 0.7,
|
|
19254
|
+
variable: varName
|
|
19255
|
+
});
|
|
19256
|
+
}
|
|
19257
|
+
}
|
|
19258
|
+
const envRe = /\$([A-Z][A-Z0-9_]{2,})|\$\{([A-Z][A-Z0-9_]{2,})\}/g;
|
|
19259
|
+
let em;
|
|
19260
|
+
while ((em = envRe.exec(line)) !== null) {
|
|
19261
|
+
const envVar = em[1] ?? em[2];
|
|
19262
|
+
if (!definedVars.has(envVar) && BASH_UNTRUSTED_ENV_PATTERNS.some((p) => p.test(envVar))) {
|
|
19263
|
+
const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === envVar);
|
|
19264
|
+
if (!alreadyExists) {
|
|
19265
|
+
sources.push({
|
|
19266
|
+
type: "env_input",
|
|
19267
|
+
location: `environment variable $${envVar}`,
|
|
19268
|
+
severity: "medium",
|
|
19269
|
+
line: lineNumber,
|
|
19270
|
+
confidence: 0.8,
|
|
19271
|
+
variable: envVar
|
|
19272
|
+
});
|
|
19273
|
+
}
|
|
19274
|
+
}
|
|
19275
|
+
}
|
|
19276
|
+
}
|
|
19277
|
+
return sources;
|
|
19278
|
+
}
|
|
19279
|
+
var BASH_CREDENTIAL_PATTERN = /^(.*?)(password|passwd|secret|api_?key|token|auth_token|private_key|access_key)\s*=\s*["']?([^"'\s$][^"'\s]*)["']?\s*$/i;
|
|
19280
|
+
function findBashPatternFindings(sourceCode, file) {
|
|
19281
|
+
const findings = [];
|
|
19282
|
+
const lines = sourceCode.split("\n");
|
|
19283
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
19284
|
+
const line = lines[i2];
|
|
19285
|
+
const trimmed = line.trim();
|
|
19286
|
+
const lineNumber = i2 + 1;
|
|
19287
|
+
if (trimmed.startsWith("#")) continue;
|
|
19288
|
+
const credMatch = trimmed.match(BASH_CREDENTIAL_PATTERN);
|
|
19289
|
+
if (credMatch) {
|
|
19290
|
+
const value = credMatch[3];
|
|
19291
|
+
if (value && !value.startsWith("$") && !value.startsWith("(") && value.length > 1) {
|
|
19292
|
+
findings.push({
|
|
19293
|
+
id: `hardcoded-credential-${file}-${lineNumber}`,
|
|
19294
|
+
pass: "language-sources",
|
|
19295
|
+
category: "security",
|
|
19296
|
+
rule_id: "hardcoded-credential",
|
|
19297
|
+
cwe: "CWE-798",
|
|
19298
|
+
severity: "high",
|
|
19299
|
+
level: "error",
|
|
19300
|
+
message: `Hardcoded credential: ${credMatch[2]} contains a literal value`,
|
|
19301
|
+
file,
|
|
19302
|
+
line: lineNumber,
|
|
19303
|
+
snippet: trimmed.substring(0, 80)
|
|
19304
|
+
});
|
|
19305
|
+
}
|
|
19306
|
+
}
|
|
19307
|
+
if (/\b(curl|wget)\b/.test(trimmed) && /\bhttp:\/\//.test(trimmed)) {
|
|
19308
|
+
findings.push({
|
|
19309
|
+
id: `cleartext-transmission-${file}-${lineNumber}`,
|
|
19310
|
+
pass: "language-sources",
|
|
19311
|
+
category: "security",
|
|
19312
|
+
rule_id: "cleartext-transmission",
|
|
19313
|
+
cwe: "CWE-319",
|
|
19314
|
+
severity: "medium",
|
|
19315
|
+
level: "warning",
|
|
19316
|
+
message: "Cleartext HTTP transmission: use https:// instead of http://",
|
|
19317
|
+
file,
|
|
19318
|
+
line: lineNumber,
|
|
19319
|
+
snippet: trimmed.substring(0, 80)
|
|
19320
|
+
});
|
|
19321
|
+
}
|
|
19322
|
+
const tmpMatch = trimmed.match(/\/tmp\/([^\s"'$]+)/);
|
|
19323
|
+
if (tmpMatch && !/mktemp/.test(trimmed)) {
|
|
19324
|
+
findings.push({
|
|
19325
|
+
id: `predictable-temp-file-${file}-${lineNumber}`,
|
|
19326
|
+
pass: "language-sources",
|
|
19327
|
+
category: "security",
|
|
19328
|
+
rule_id: "predictable-temp-file",
|
|
19329
|
+
cwe: "CWE-377",
|
|
19330
|
+
severity: "medium",
|
|
19331
|
+
level: "warning",
|
|
19332
|
+
message: `Predictable temp file: /tmp/${tmpMatch[1]}. Use mktemp instead`,
|
|
19333
|
+
file,
|
|
19334
|
+
line: lineNumber,
|
|
19335
|
+
snippet: trimmed.substring(0, 80)
|
|
19336
|
+
});
|
|
19337
|
+
}
|
|
19338
|
+
if (/\bchmod\b/.test(trimmed) && /\b(777|666)\b/.test(trimmed)) {
|
|
19339
|
+
const mode = trimmed.match(/\b(777|666)\b/)[1];
|
|
19340
|
+
findings.push({
|
|
19341
|
+
id: `insecure-file-permission-${file}-${lineNumber}`,
|
|
19342
|
+
pass: "language-sources",
|
|
19343
|
+
category: "security",
|
|
19344
|
+
rule_id: "insecure-file-permission",
|
|
19345
|
+
cwe: "CWE-732",
|
|
19346
|
+
severity: "medium",
|
|
19347
|
+
level: "warning",
|
|
19348
|
+
message: `Insecure file permission: chmod ${mode} grants excessive access`,
|
|
19349
|
+
file,
|
|
19350
|
+
line: lineNumber,
|
|
19351
|
+
snippet: trimmed.substring(0, 80)
|
|
19352
|
+
});
|
|
19353
|
+
}
|
|
19354
|
+
if (/\btar\b/.test(trimmed) && /(-x|--extract)/.test(trimmed) && !/--strip-components/.test(trimmed)) {
|
|
19355
|
+
findings.push({
|
|
19356
|
+
id: `unsafe-archive-extraction-${file}-${lineNumber}`,
|
|
19357
|
+
pass: "language-sources",
|
|
19358
|
+
category: "security",
|
|
19359
|
+
rule_id: "unsafe-archive-extraction",
|
|
19360
|
+
cwe: "CWE-22",
|
|
19361
|
+
severity: "medium",
|
|
19362
|
+
level: "warning",
|
|
19363
|
+
message: "Unsafe archive extraction: tar -x without --strip-components may allow path traversal",
|
|
19364
|
+
file,
|
|
19365
|
+
line: lineNumber,
|
|
19366
|
+
snippet: trimmed.substring(0, 80)
|
|
19367
|
+
});
|
|
19368
|
+
}
|
|
19369
|
+
}
|
|
19370
|
+
return findings;
|
|
19371
|
+
}
|
|
19011
19372
|
|
|
19012
19373
|
// src/analysis/passes/sink-filter-pass.ts
|
|
19013
19374
|
var JS_XSS_SANITIZERS = [
|
|
@@ -7526,6 +7526,9 @@ function buildCFG(tree, language) {
|
|
|
7526
7526
|
const allBlocks = [];
|
|
7527
7527
|
const allEdges = [];
|
|
7528
7528
|
let blockIdCounter = 0;
|
|
7529
|
+
if (effectiveLanguage === "bash") {
|
|
7530
|
+
return buildBashCFG(tree, blockIdCounter);
|
|
7531
|
+
}
|
|
7529
7532
|
if (isJavaScript) {
|
|
7530
7533
|
const functions = [
|
|
7531
7534
|
...findNodes(tree.rootNode, "function_declaration"),
|
|
@@ -7881,6 +7884,81 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
|
|
|
7881
7884
|
nextId: currentId
|
|
7882
7885
|
};
|
|
7883
7886
|
}
|
|
7887
|
+
function buildBashCFG(tree, startId) {
|
|
7888
|
+
const allBlocks = [];
|
|
7889
|
+
const allEdges = [];
|
|
7890
|
+
let blockIdCounter = startId;
|
|
7891
|
+
const functions = findNodes(tree.rootNode, "function_definition");
|
|
7892
|
+
for (const func2 of functions) {
|
|
7893
|
+
const body2 = func2.childForFieldName("body");
|
|
7894
|
+
if (!body2) continue;
|
|
7895
|
+
const { blocks, edges, nextId } = buildMethodCFG(body2, blockIdCounter, false);
|
|
7896
|
+
allBlocks.push(...blocks);
|
|
7897
|
+
allEdges.push(...edges);
|
|
7898
|
+
blockIdCounter = nextId;
|
|
7899
|
+
}
|
|
7900
|
+
const topLevelStatements = [];
|
|
7901
|
+
for (let i2 = 0; i2 < tree.rootNode.childCount; i2++) {
|
|
7902
|
+
const child = tree.rootNode.child(i2);
|
|
7903
|
+
if (child && child.type !== "function_definition" && isBashStatement(child)) {
|
|
7904
|
+
topLevelStatements.push(child);
|
|
7905
|
+
}
|
|
7906
|
+
}
|
|
7907
|
+
if (topLevelStatements.length > 0) {
|
|
7908
|
+
const entryBlock = {
|
|
7909
|
+
id: blockIdCounter++,
|
|
7910
|
+
type: "entry",
|
|
7911
|
+
start_line: topLevelStatements[0].startPosition.row + 1,
|
|
7912
|
+
end_line: topLevelStatements[0].startPosition.row + 1
|
|
7913
|
+
};
|
|
7914
|
+
allBlocks.push(entryBlock);
|
|
7915
|
+
let lastExitIds = [];
|
|
7916
|
+
let firstBlockId = -1;
|
|
7917
|
+
for (const stmt of topLevelStatements) {
|
|
7918
|
+
const result = processStatement(stmt, blockIdCounter, allBlocks, allEdges, false);
|
|
7919
|
+
blockIdCounter = result.nextId;
|
|
7920
|
+
if (firstBlockId === -1) {
|
|
7921
|
+
firstBlockId = result.entryId;
|
|
7922
|
+
} else {
|
|
7923
|
+
for (const exitId of lastExitIds) {
|
|
7924
|
+
allEdges.push({ from: exitId, to: result.entryId, type: "sequential" });
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
lastExitIds = result.exitIds;
|
|
7928
|
+
}
|
|
7929
|
+
if (firstBlockId !== -1) {
|
|
7930
|
+
allEdges.push({ from: entryBlock.id, to: firstBlockId, type: "sequential" });
|
|
7931
|
+
}
|
|
7932
|
+
const exitBlock = {
|
|
7933
|
+
id: blockIdCounter++,
|
|
7934
|
+
type: "exit",
|
|
7935
|
+
start_line: topLevelStatements[topLevelStatements.length - 1].endPosition.row + 1,
|
|
7936
|
+
end_line: topLevelStatements[topLevelStatements.length - 1].endPosition.row + 1
|
|
7937
|
+
};
|
|
7938
|
+
allBlocks.push(exitBlock);
|
|
7939
|
+
for (const exitId of lastExitIds) {
|
|
7940
|
+
allEdges.push({ from: exitId, to: exitBlock.id, type: "sequential" });
|
|
7941
|
+
}
|
|
7942
|
+
}
|
|
7943
|
+
return { blocks: allBlocks, edges: allEdges };
|
|
7944
|
+
}
|
|
7945
|
+
function isBashStatement(node) {
|
|
7946
|
+
const bashStatementTypes = /* @__PURE__ */ new Set([
|
|
7947
|
+
"command",
|
|
7948
|
+
"variable_assignment",
|
|
7949
|
+
"if_statement",
|
|
7950
|
+
"for_statement",
|
|
7951
|
+
"while_statement",
|
|
7952
|
+
"case_statement",
|
|
7953
|
+
"pipeline",
|
|
7954
|
+
"list",
|
|
7955
|
+
"redirected_statement",
|
|
7956
|
+
"compound_statement",
|
|
7957
|
+
"subshell",
|
|
7958
|
+
"declaration_command"
|
|
7959
|
+
]);
|
|
7960
|
+
return bashStatementTypes.has(node.type);
|
|
7961
|
+
}
|
|
7884
7962
|
function isStatement(node, isJavaScript) {
|
|
7885
7963
|
const javaStatementTypes = /* @__PURE__ */ new Set([
|
|
7886
7964
|
"local_variable_declaration",
|
|
@@ -7971,6 +8049,9 @@ function buildDFG(tree, cache, language) {
|
|
|
7971
8049
|
if (effectiveLanguage === "rust") {
|
|
7972
8050
|
return buildRustDFG(tree, cache);
|
|
7973
8051
|
}
|
|
8052
|
+
if (effectiveLanguage === "bash") {
|
|
8053
|
+
return buildBashDFG(tree);
|
|
8054
|
+
}
|
|
7974
8055
|
return buildJavaDFG(tree, cache);
|
|
7975
8056
|
}
|
|
7976
8057
|
function buildJavaDFG(tree, cache) {
|
|
@@ -8697,6 +8778,101 @@ function computeChains(defs, uses) {
|
|
|
8697
8778
|
chains.sort((a, b) => a.from_def - b.from_def || a.to_def - b.to_def);
|
|
8698
8779
|
return chains;
|
|
8699
8780
|
}
|
|
8781
|
+
function buildBashDFG(tree) {
|
|
8782
|
+
const defs = [];
|
|
8783
|
+
const uses = [];
|
|
8784
|
+
let defIdCounter = 1;
|
|
8785
|
+
let useIdCounter = 1;
|
|
8786
|
+
const scopeStack = [/* @__PURE__ */ new Map()];
|
|
8787
|
+
const positionalParams = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*"];
|
|
8788
|
+
for (const p of positionalParams) {
|
|
8789
|
+
const def = {
|
|
8790
|
+
id: defIdCounter++,
|
|
8791
|
+
variable: p,
|
|
8792
|
+
line: 0,
|
|
8793
|
+
kind: "param"
|
|
8794
|
+
};
|
|
8795
|
+
defs.push(def);
|
|
8796
|
+
currentScope(scopeStack).set(p, def.id);
|
|
8797
|
+
}
|
|
8798
|
+
walkTree(tree.rootNode, (node) => {
|
|
8799
|
+
if (node.type === "variable_assignment") {
|
|
8800
|
+
const nameNode = node.childForFieldName("name");
|
|
8801
|
+
if (nameNode) {
|
|
8802
|
+
const varName = getNodeText(nameNode);
|
|
8803
|
+
const def = {
|
|
8804
|
+
id: defIdCounter++,
|
|
8805
|
+
variable: varName,
|
|
8806
|
+
line: node.startPosition.row + 1,
|
|
8807
|
+
kind: "local"
|
|
8808
|
+
};
|
|
8809
|
+
defs.push(def);
|
|
8810
|
+
currentScope(scopeStack).set(varName, def.id);
|
|
8811
|
+
}
|
|
8812
|
+
} else if (node.type === "command") {
|
|
8813
|
+
const nameNode = node.childForFieldName("name");
|
|
8814
|
+
if (nameNode && getNodeText(nameNode) === "read") {
|
|
8815
|
+
for (let i2 = 0; i2 < node.namedChildCount; i2++) {
|
|
8816
|
+
const arg = node.namedChild(i2);
|
|
8817
|
+
if (!arg || arg === nameNode) continue;
|
|
8818
|
+
if (arg.type === "word") {
|
|
8819
|
+
const text = getNodeText(arg);
|
|
8820
|
+
if (text.startsWith("-")) continue;
|
|
8821
|
+
const def = {
|
|
8822
|
+
id: defIdCounter++,
|
|
8823
|
+
variable: text,
|
|
8824
|
+
line: node.startPosition.row + 1,
|
|
8825
|
+
kind: "local"
|
|
8826
|
+
};
|
|
8827
|
+
defs.push(def);
|
|
8828
|
+
currentScope(scopeStack).set(text, def.id);
|
|
8829
|
+
}
|
|
8830
|
+
}
|
|
8831
|
+
}
|
|
8832
|
+
} else if (node.type === "for_statement") {
|
|
8833
|
+
const varNode = node.childForFieldName("variable");
|
|
8834
|
+
if (varNode) {
|
|
8835
|
+
const varName = getNodeText(varNode);
|
|
8836
|
+
const def = {
|
|
8837
|
+
id: defIdCounter++,
|
|
8838
|
+
variable: varName,
|
|
8839
|
+
line: node.startPosition.row + 1,
|
|
8840
|
+
kind: "local"
|
|
8841
|
+
};
|
|
8842
|
+
defs.push(def);
|
|
8843
|
+
currentScope(scopeStack).set(varName, def.id);
|
|
8844
|
+
}
|
|
8845
|
+
} else if (node.type === "simple_expansion") {
|
|
8846
|
+
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
8847
|
+
if (varNameNode) {
|
|
8848
|
+
const varName = getNodeText(varNameNode);
|
|
8849
|
+
if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
|
|
8850
|
+
const reachingDef = findReachingDef(varName, scopeStack);
|
|
8851
|
+
uses.push({
|
|
8852
|
+
id: useIdCounter++,
|
|
8853
|
+
variable: varName,
|
|
8854
|
+
line: node.startPosition.row + 1,
|
|
8855
|
+
def_id: reachingDef
|
|
8856
|
+
});
|
|
8857
|
+
}
|
|
8858
|
+
}
|
|
8859
|
+
} else if (node.type === "expansion") {
|
|
8860
|
+
const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
|
|
8861
|
+
if (varNameNode && varNameNode.type === "variable_name") {
|
|
8862
|
+
const varName = getNodeText(varNameNode);
|
|
8863
|
+
const reachingDef = findReachingDef(varName, scopeStack);
|
|
8864
|
+
uses.push({
|
|
8865
|
+
id: useIdCounter++,
|
|
8866
|
+
variable: varName,
|
|
8867
|
+
line: node.startPosition.row + 1,
|
|
8868
|
+
def_id: reachingDef
|
|
8869
|
+
});
|
|
8870
|
+
}
|
|
8871
|
+
}
|
|
8872
|
+
});
|
|
8873
|
+
const chains = computeChains(defs, uses);
|
|
8874
|
+
return { defs, uses, chains };
|
|
8875
|
+
}
|
|
8700
8876
|
function buildRustDFG(tree, cache) {
|
|
8701
8877
|
const defs = [];
|
|
8702
8878
|
const uses = [];
|