circle-ir 3.3.3 → 3.6.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/analyzer.js +52 -2
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +453 -1
- package/dist/core/circle-ir-core.cjs +85 -0
- package/dist/core/circle-ir-core.js +85 -0
- package/dist/core/extractors/calls.js +119 -0
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/parser.d.ts +1 -1
- package/dist/languages/plugins/bash.d.ts +51 -0
- package/dist/languages/plugins/bash.js +243 -0
- package/dist/languages/plugins/bash.js.map +1 -0
- package/dist/languages/plugins/index.d.ts +1 -0
- package/dist/languages/plugins/index.js +3 -0
- package/dist/languages/plugins/index.js.map +1 -1
- package/dist/languages/plugins/javascript.js +89 -0
- package/dist/languages/plugins/javascript.js.map +1 -1
- package/dist/languages/types.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/wasm/tree-sitter-bash.wasm +0 -0
- package/package.json +2 -1
- package/wasm/tree-sitter-bash.wasm +0 -0
|
@@ -5470,6 +5470,9 @@ function extractCalls(tree, cache, language) {
|
|
|
5470
5470
|
const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
|
|
5471
5471
|
const isPython = detectedLanguage === "python";
|
|
5472
5472
|
const isRust = detectedLanguage === "rust";
|
|
5473
|
+
if (detectedLanguage === "bash") {
|
|
5474
|
+
return extractBashCalls(tree, cache);
|
|
5475
|
+
}
|
|
5473
5476
|
if (isRust) {
|
|
5474
5477
|
return extractRustCalls(tree, cache);
|
|
5475
5478
|
}
|
|
@@ -6159,6 +6162,88 @@ function inferTypeFromReceiverName(receiver) {
|
|
|
6159
6162
|
const lowerReceiver = receiver.toLowerCase();
|
|
6160
6163
|
return patterns[lowerReceiver] ?? null;
|
|
6161
6164
|
}
|
|
6165
|
+
function extractBashCalls(tree, cache) {
|
|
6166
|
+
const calls = [];
|
|
6167
|
+
const commands = getNodesFromCache(tree.rootNode, "command", cache);
|
|
6168
|
+
for (const cmd of commands) {
|
|
6169
|
+
const callInfo = extractBashCommandInfo(cmd);
|
|
6170
|
+
if (callInfo) {
|
|
6171
|
+
calls.push(callInfo);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
return calls;
|
|
6175
|
+
}
|
|
6176
|
+
function extractBashCommandInfo(node) {
|
|
6177
|
+
const nameNode = node.childForFieldName("name");
|
|
6178
|
+
if (!nameNode) return null;
|
|
6179
|
+
const commandName = getNodeText(nameNode);
|
|
6180
|
+
if (!commandName) return null;
|
|
6181
|
+
const args2 = [];
|
|
6182
|
+
let position = 0;
|
|
6183
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6184
|
+
const child = node.child(i2);
|
|
6185
|
+
if (!child) continue;
|
|
6186
|
+
if (child === nameNode) continue;
|
|
6187
|
+
if (child.type.includes("redirect") || child.type === "heredoc_body" || child.type === "file_descriptor") {
|
|
6188
|
+
continue;
|
|
6189
|
+
}
|
|
6190
|
+
const expression = getNodeText(child);
|
|
6191
|
+
if (!expression.trim()) continue;
|
|
6192
|
+
const variable = extractBashVariableRef(child);
|
|
6193
|
+
args2.push({
|
|
6194
|
+
position: position++,
|
|
6195
|
+
expression,
|
|
6196
|
+
variable,
|
|
6197
|
+
literal: null
|
|
6198
|
+
});
|
|
6199
|
+
}
|
|
6200
|
+
const inMethod = findBashEnclosingFunction(node);
|
|
6201
|
+
return {
|
|
6202
|
+
method_name: commandName,
|
|
6203
|
+
receiver: null,
|
|
6204
|
+
arguments: args2,
|
|
6205
|
+
location: {
|
|
6206
|
+
line: node.startPosition.row + 1,
|
|
6207
|
+
column: node.startPosition.column
|
|
6208
|
+
},
|
|
6209
|
+
in_method: inMethod,
|
|
6210
|
+
resolved: false,
|
|
6211
|
+
resolution: { status: "external_method" }
|
|
6212
|
+
};
|
|
6213
|
+
}
|
|
6214
|
+
function extractBashVariableRef(node) {
|
|
6215
|
+
const type = node.type;
|
|
6216
|
+
if (type === "simple_expansion" || type === "expansion") {
|
|
6217
|
+
return getNodeText(node).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6218
|
+
}
|
|
6219
|
+
if (type === "string" || type === "concatenation") {
|
|
6220
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6221
|
+
const child = node.child(i2);
|
|
6222
|
+
if (!child) continue;
|
|
6223
|
+
if (child.type === "simple_expansion" || child.type === "expansion") {
|
|
6224
|
+
return getNodeText(child).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
if (type === "word") {
|
|
6229
|
+
const text = getNodeText(node);
|
|
6230
|
+
if (text.startsWith("$")) {
|
|
6231
|
+
return text.slice(1).replace(/^\{/, "").replace(/\}$/, "");
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
return null;
|
|
6235
|
+
}
|
|
6236
|
+
function findBashEnclosingFunction(node) {
|
|
6237
|
+
let current = node.parent;
|
|
6238
|
+
while (current) {
|
|
6239
|
+
if (current.type === "function_definition") {
|
|
6240
|
+
const nameNode = current.childForFieldName("name");
|
|
6241
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
6242
|
+
}
|
|
6243
|
+
current = current.parent;
|
|
6244
|
+
}
|
|
6245
|
+
return null;
|
|
6246
|
+
}
|
|
6162
6247
|
function extractPythonCalls(tree, cache) {
|
|
6163
6248
|
const calls = [];
|
|
6164
6249
|
const context = buildPythonResolutionContext(tree, cache);
|
|
@@ -13896,6 +13981,9 @@ function getLanguageRegistry() {
|
|
|
13896
13981
|
function registerLanguage(plugin) {
|
|
13897
13982
|
getLanguageRegistry().register(plugin);
|
|
13898
13983
|
}
|
|
13984
|
+
function getLanguagePlugin(language) {
|
|
13985
|
+
return getLanguageRegistry().get(language);
|
|
13986
|
+
}
|
|
13899
13987
|
|
|
13900
13988
|
// src/languages/plugins/base.ts
|
|
13901
13989
|
var BaseLanguagePlugin = class {
|
|
@@ -14497,6 +14585,80 @@ var JavaScriptPlugin = class extends BaseLanguagePlugin {
|
|
|
14497
14585
|
confidence: 0.85,
|
|
14498
14586
|
returnTainted: true
|
|
14499
14587
|
},
|
|
14588
|
+
// Fastify-specific sources (request object)
|
|
14589
|
+
{
|
|
14590
|
+
method: "raw",
|
|
14591
|
+
class: "request",
|
|
14592
|
+
type: "http_param",
|
|
14593
|
+
severity: "high",
|
|
14594
|
+
confidence: 0.85,
|
|
14595
|
+
returnTainted: true
|
|
14596
|
+
},
|
|
14597
|
+
{
|
|
14598
|
+
method: "hostname",
|
|
14599
|
+
class: "request",
|
|
14600
|
+
type: "http_header",
|
|
14601
|
+
severity: "medium",
|
|
14602
|
+
confidence: 0.8,
|
|
14603
|
+
returnTainted: true
|
|
14604
|
+
},
|
|
14605
|
+
// Koa context sources (ctx.* and ctx.request.*)
|
|
14606
|
+
{
|
|
14607
|
+
method: "header",
|
|
14608
|
+
class: "ctx",
|
|
14609
|
+
type: "http_header",
|
|
14610
|
+
severity: "high",
|
|
14611
|
+
confidence: 0.85,
|
|
14612
|
+
returnTainted: true
|
|
14613
|
+
},
|
|
14614
|
+
{
|
|
14615
|
+
method: "headers",
|
|
14616
|
+
class: "ctx",
|
|
14617
|
+
type: "http_header",
|
|
14618
|
+
severity: "high",
|
|
14619
|
+
confidence: 0.85,
|
|
14620
|
+
returnTainted: true
|
|
14621
|
+
},
|
|
14622
|
+
{
|
|
14623
|
+
method: "host",
|
|
14624
|
+
class: "ctx",
|
|
14625
|
+
type: "http_header",
|
|
14626
|
+
severity: "medium",
|
|
14627
|
+
confidence: 0.8,
|
|
14628
|
+
returnTainted: true
|
|
14629
|
+
},
|
|
14630
|
+
{
|
|
14631
|
+
method: "hostname",
|
|
14632
|
+
class: "ctx",
|
|
14633
|
+
type: "http_header",
|
|
14634
|
+
severity: "medium",
|
|
14635
|
+
confidence: 0.8,
|
|
14636
|
+
returnTainted: true
|
|
14637
|
+
},
|
|
14638
|
+
{
|
|
14639
|
+
method: "path",
|
|
14640
|
+
class: "ctx",
|
|
14641
|
+
type: "http_path",
|
|
14642
|
+
severity: "high",
|
|
14643
|
+
confidence: 0.85,
|
|
14644
|
+
returnTainted: true
|
|
14645
|
+
},
|
|
14646
|
+
{
|
|
14647
|
+
method: "url",
|
|
14648
|
+
class: "ctx",
|
|
14649
|
+
type: "http_path",
|
|
14650
|
+
severity: "high",
|
|
14651
|
+
confidence: 0.85,
|
|
14652
|
+
returnTainted: true
|
|
14653
|
+
},
|
|
14654
|
+
{
|
|
14655
|
+
method: "querystring",
|
|
14656
|
+
class: "ctx",
|
|
14657
|
+
type: "http_param",
|
|
14658
|
+
severity: "high",
|
|
14659
|
+
confidence: 0.85,
|
|
14660
|
+
returnTainted: true
|
|
14661
|
+
},
|
|
14500
14662
|
// DOM sources (for browser code)
|
|
14501
14663
|
{
|
|
14502
14664
|
method: "location",
|
|
@@ -14839,6 +15001,21 @@ var JavaScriptPlugin = class extends BaseLanguagePlugin {
|
|
|
14839
15001
|
severity: "critical",
|
|
14840
15002
|
argPositions: [0]
|
|
14841
15003
|
},
|
|
15004
|
+
// Prisma ORM - unsafe raw query methods ($executeRaw/$queryRaw with template literals are safe/parameterized)
|
|
15005
|
+
{
|
|
15006
|
+
method: "$executeRawUnsafe",
|
|
15007
|
+
type: "sql_injection",
|
|
15008
|
+
cwe: "CWE-89",
|
|
15009
|
+
severity: "critical",
|
|
15010
|
+
argPositions: [0]
|
|
15011
|
+
},
|
|
15012
|
+
{
|
|
15013
|
+
method: "$queryRawUnsafe",
|
|
15014
|
+
type: "sql_injection",
|
|
15015
|
+
cwe: "CWE-89",
|
|
15016
|
+
severity: "critical",
|
|
15017
|
+
argPositions: [0]
|
|
15018
|
+
},
|
|
14842
15019
|
// SSRF
|
|
14843
15020
|
{
|
|
14844
15021
|
method: "fetch",
|
|
@@ -15944,12 +16121,241 @@ var RustPlugin = class extends BaseLanguagePlugin {
|
|
|
15944
16121
|
}
|
|
15945
16122
|
};
|
|
15946
16123
|
|
|
16124
|
+
// src/languages/plugins/bash.ts
|
|
16125
|
+
var BashPlugin = class extends BaseLanguagePlugin {
|
|
16126
|
+
id = "bash";
|
|
16127
|
+
name = "Bash/Shell";
|
|
16128
|
+
extensions = [".sh", ".bash", ".zsh", ".ksh"];
|
|
16129
|
+
wasmPath = "tree-sitter-bash.wasm";
|
|
16130
|
+
nodeTypes = {
|
|
16131
|
+
// Type declarations — shell has no OOP types
|
|
16132
|
+
classDeclaration: [],
|
|
16133
|
+
interfaceDeclaration: [],
|
|
16134
|
+
enumDeclaration: [],
|
|
16135
|
+
functionDeclaration: ["function_definition"],
|
|
16136
|
+
methodDeclaration: ["function_definition"],
|
|
16137
|
+
// Expressions — commands are treated as calls
|
|
16138
|
+
methodCall: ["command"],
|
|
16139
|
+
functionCall: ["command"],
|
|
16140
|
+
assignment: ["variable_assignment"],
|
|
16141
|
+
variableDeclaration: ["variable_assignment", "declaration_command"],
|
|
16142
|
+
// Parameters and arguments — positional args are child words
|
|
16143
|
+
parameter: [],
|
|
16144
|
+
argument: [],
|
|
16145
|
+
// Annotations/decorators — none in shell
|
|
16146
|
+
annotation: [],
|
|
16147
|
+
decorator: [],
|
|
16148
|
+
// Imports — shell uses `source` / `.` but no formal import system
|
|
16149
|
+
importStatement: [],
|
|
16150
|
+
// Control flow
|
|
16151
|
+
ifStatement: ["if_statement"],
|
|
16152
|
+
forStatement: ["for_statement", "c_style_for_statement"],
|
|
16153
|
+
whileStatement: ["while_statement"],
|
|
16154
|
+
tryStatement: [],
|
|
16155
|
+
returnStatement: []
|
|
16156
|
+
};
|
|
16157
|
+
/**
|
|
16158
|
+
* Shell scripts don't have a formal framework concept.
|
|
16159
|
+
*/
|
|
16160
|
+
detectFramework(_context) {
|
|
16161
|
+
return void 0;
|
|
16162
|
+
}
|
|
16163
|
+
/**
|
|
16164
|
+
* Bash taint source patterns.
|
|
16165
|
+
* In shell, tainted data enters via `read` (stdin).
|
|
16166
|
+
* curl/wget are excluded as sources (see comment in implementation).
|
|
16167
|
+
*/
|
|
16168
|
+
getBuiltinSources() {
|
|
16169
|
+
return [
|
|
16170
|
+
// read built-in reads user input from stdin.
|
|
16171
|
+
// curl/wget are intentionally excluded: they're also registered as sinks (SSRF),
|
|
16172
|
+
// and without DFG tracking of $() command substitution, including them as sources
|
|
16173
|
+
// would generate false positives for safe curl calls.
|
|
16174
|
+
{
|
|
16175
|
+
method: "read",
|
|
16176
|
+
type: "io_input",
|
|
16177
|
+
severity: "high",
|
|
16178
|
+
confidence: 0.9,
|
|
16179
|
+
returnTainted: true
|
|
16180
|
+
}
|
|
16181
|
+
];
|
|
16182
|
+
}
|
|
16183
|
+
/**
|
|
16184
|
+
* Bash taint sink patterns.
|
|
16185
|
+
* Key sinks: eval (CWE-94), bash/sh -c (CWE-78), DB clients (CWE-89),
|
|
16186
|
+
* file operations (CWE-22), SSRF via curl/wget (CWE-918).
|
|
16187
|
+
*/
|
|
16188
|
+
getBuiltinSinks() {
|
|
16189
|
+
return [
|
|
16190
|
+
// Code / command injection via eval
|
|
16191
|
+
{
|
|
16192
|
+
method: "eval",
|
|
16193
|
+
type: "code_injection",
|
|
16194
|
+
cwe: "CWE-94",
|
|
16195
|
+
severity: "critical",
|
|
16196
|
+
argPositions: [0]
|
|
16197
|
+
},
|
|
16198
|
+
// Command injection: spawning a sub-shell with -c flag
|
|
16199
|
+
{
|
|
16200
|
+
method: "bash",
|
|
16201
|
+
type: "command_injection",
|
|
16202
|
+
cwe: "CWE-78",
|
|
16203
|
+
severity: "critical",
|
|
16204
|
+
argPositions: [1]
|
|
16205
|
+
},
|
|
16206
|
+
{
|
|
16207
|
+
method: "sh",
|
|
16208
|
+
type: "command_injection",
|
|
16209
|
+
cwe: "CWE-78",
|
|
16210
|
+
severity: "critical",
|
|
16211
|
+
argPositions: [1]
|
|
16212
|
+
},
|
|
16213
|
+
{
|
|
16214
|
+
method: "zsh",
|
|
16215
|
+
type: "command_injection",
|
|
16216
|
+
cwe: "CWE-78",
|
|
16217
|
+
severity: "critical",
|
|
16218
|
+
argPositions: [1]
|
|
16219
|
+
},
|
|
16220
|
+
{
|
|
16221
|
+
method: "ksh",
|
|
16222
|
+
type: "command_injection",
|
|
16223
|
+
cwe: "CWE-78",
|
|
16224
|
+
severity: "critical",
|
|
16225
|
+
argPositions: [1]
|
|
16226
|
+
},
|
|
16227
|
+
// SQL injection via DB CLI clients (first arg is query/expression)
|
|
16228
|
+
{
|
|
16229
|
+
method: "mysql",
|
|
16230
|
+
type: "sql_injection",
|
|
16231
|
+
cwe: "CWE-89",
|
|
16232
|
+
severity: "critical",
|
|
16233
|
+
argPositions: [1]
|
|
16234
|
+
},
|
|
16235
|
+
{
|
|
16236
|
+
method: "psql",
|
|
16237
|
+
type: "sql_injection",
|
|
16238
|
+
cwe: "CWE-89",
|
|
16239
|
+
severity: "critical",
|
|
16240
|
+
argPositions: [1]
|
|
16241
|
+
},
|
|
16242
|
+
{
|
|
16243
|
+
method: "sqlite3",
|
|
16244
|
+
type: "sql_injection",
|
|
16245
|
+
cwe: "CWE-89",
|
|
16246
|
+
severity: "critical",
|
|
16247
|
+
argPositions: [1]
|
|
16248
|
+
},
|
|
16249
|
+
// Path traversal via file operations (first arg is path)
|
|
16250
|
+
{
|
|
16251
|
+
method: "cat",
|
|
16252
|
+
type: "path_traversal",
|
|
16253
|
+
cwe: "CWE-22",
|
|
16254
|
+
severity: "high",
|
|
16255
|
+
argPositions: [0]
|
|
16256
|
+
},
|
|
16257
|
+
{
|
|
16258
|
+
method: "rm",
|
|
16259
|
+
type: "path_traversal",
|
|
16260
|
+
cwe: "CWE-22",
|
|
16261
|
+
severity: "high",
|
|
16262
|
+
argPositions: [0]
|
|
16263
|
+
},
|
|
16264
|
+
{
|
|
16265
|
+
method: "cp",
|
|
16266
|
+
type: "path_traversal",
|
|
16267
|
+
cwe: "CWE-22",
|
|
16268
|
+
severity: "high",
|
|
16269
|
+
argPositions: [0]
|
|
16270
|
+
},
|
|
16271
|
+
{
|
|
16272
|
+
method: "mv",
|
|
16273
|
+
type: "path_traversal",
|
|
16274
|
+
cwe: "CWE-22",
|
|
16275
|
+
severity: "high",
|
|
16276
|
+
argPositions: [0]
|
|
16277
|
+
},
|
|
16278
|
+
{
|
|
16279
|
+
method: "chmod",
|
|
16280
|
+
type: "path_traversal",
|
|
16281
|
+
cwe: "CWE-22",
|
|
16282
|
+
severity: "medium",
|
|
16283
|
+
argPositions: [1]
|
|
16284
|
+
},
|
|
16285
|
+
{
|
|
16286
|
+
method: "chown",
|
|
16287
|
+
type: "path_traversal",
|
|
16288
|
+
cwe: "CWE-22",
|
|
16289
|
+
severity: "medium",
|
|
16290
|
+
argPositions: [1]
|
|
16291
|
+
},
|
|
16292
|
+
// SSRF — curl/wget with externally-controlled URL
|
|
16293
|
+
{
|
|
16294
|
+
method: "curl",
|
|
16295
|
+
type: "ssrf",
|
|
16296
|
+
cwe: "CWE-918",
|
|
16297
|
+
severity: "high",
|
|
16298
|
+
argPositions: [0]
|
|
16299
|
+
},
|
|
16300
|
+
{
|
|
16301
|
+
method: "wget",
|
|
16302
|
+
type: "ssrf",
|
|
16303
|
+
cwe: "CWE-918",
|
|
16304
|
+
severity: "high",
|
|
16305
|
+
argPositions: [0]
|
|
16306
|
+
}
|
|
16307
|
+
];
|
|
16308
|
+
}
|
|
16309
|
+
/**
|
|
16310
|
+
* Shell has no OOP receiver types.
|
|
16311
|
+
*/
|
|
16312
|
+
getReceiverType(_node, _context) {
|
|
16313
|
+
return void 0;
|
|
16314
|
+
}
|
|
16315
|
+
/**
|
|
16316
|
+
* Bash string literals: quoted strings and raw ($'...') strings.
|
|
16317
|
+
*/
|
|
16318
|
+
isStringLiteral(node) {
|
|
16319
|
+
return node.type === "string" || node.type === "raw_string" || node.type === "ansi_c_string";
|
|
16320
|
+
}
|
|
16321
|
+
/**
|
|
16322
|
+
* Extract string value from bash string literal, stripping quotes.
|
|
16323
|
+
*/
|
|
16324
|
+
getStringValue(node) {
|
|
16325
|
+
if (!this.isStringLiteral(node)) return void 0;
|
|
16326
|
+
const text = node.text;
|
|
16327
|
+
if (node.type === "raw_string") {
|
|
16328
|
+
return text.slice(1, -1);
|
|
16329
|
+
}
|
|
16330
|
+
if (node.type === "ansi_c_string") {
|
|
16331
|
+
return text.slice(2, -1);
|
|
16332
|
+
}
|
|
16333
|
+
const match = text.match(/^"(.*)"$/s);
|
|
16334
|
+
if (match) return match[1];
|
|
16335
|
+
return text;
|
|
16336
|
+
}
|
|
16337
|
+
// Extraction methods — delegate to base extractors via generic walker
|
|
16338
|
+
extractTypes(_context) {
|
|
16339
|
+
return [];
|
|
16340
|
+
}
|
|
16341
|
+
extractCalls(_context) {
|
|
16342
|
+
return [];
|
|
16343
|
+
}
|
|
16344
|
+
extractImports(_context) {
|
|
16345
|
+
return [];
|
|
16346
|
+
}
|
|
16347
|
+
extractPackage(_context) {
|
|
16348
|
+
return void 0;
|
|
16349
|
+
}
|
|
16350
|
+
};
|
|
16351
|
+
|
|
15947
16352
|
// src/languages/plugins/index.ts
|
|
15948
16353
|
function registerBuiltinPlugins() {
|
|
15949
16354
|
registerLanguage(new JavaPlugin());
|
|
15950
16355
|
registerLanguage(new JavaScriptPlugin());
|
|
15951
16356
|
registerLanguage(new PythonPlugin());
|
|
15952
16357
|
registerLanguage(new RustPlugin());
|
|
16358
|
+
registerLanguage(new BashPlugin());
|
|
15953
16359
|
}
|
|
15954
16360
|
|
|
15955
16361
|
// src/utils/logger.ts
|
|
@@ -16278,6 +16684,18 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
16278
16684
|
"member_expression",
|
|
16279
16685
|
"assignment_expression"
|
|
16280
16686
|
]);
|
|
16687
|
+
} else if (language === "bash") {
|
|
16688
|
+
nodeTypesToCollect = /* @__PURE__ */ new Set([
|
|
16689
|
+
// Bash AST nodes
|
|
16690
|
+
"command",
|
|
16691
|
+
"function_definition",
|
|
16692
|
+
"variable_assignment",
|
|
16693
|
+
"declaration_command",
|
|
16694
|
+
"if_statement",
|
|
16695
|
+
"for_statement",
|
|
16696
|
+
"c_style_for_statement",
|
|
16697
|
+
"while_statement"
|
|
16698
|
+
]);
|
|
16281
16699
|
} else {
|
|
16282
16700
|
nodeTypesToCollect = /* @__PURE__ */ new Set([
|
|
16283
16701
|
// Java AST nodes
|
|
@@ -16308,7 +16726,41 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
16308
16726
|
}
|
|
16309
16727
|
}
|
|
16310
16728
|
}
|
|
16311
|
-
|
|
16729
|
+
let baseConfig = options.taintConfig ?? getDefaultConfig();
|
|
16730
|
+
if (!options.taintConfig) {
|
|
16731
|
+
const plugin = getLanguagePlugin(language);
|
|
16732
|
+
if (plugin) {
|
|
16733
|
+
const pluginSources = plugin.getBuiltinSources();
|
|
16734
|
+
const pluginSinks = plugin.getBuiltinSinks();
|
|
16735
|
+
if (pluginSources.length > 0 || pluginSinks.length > 0) {
|
|
16736
|
+
baseConfig = {
|
|
16737
|
+
...baseConfig,
|
|
16738
|
+
sources: [
|
|
16739
|
+
...baseConfig.sources,
|
|
16740
|
+
...pluginSources.map((s) => ({
|
|
16741
|
+
method: s.method,
|
|
16742
|
+
class: s.class,
|
|
16743
|
+
annotation: s.annotation,
|
|
16744
|
+
type: s.type,
|
|
16745
|
+
severity: s.severity,
|
|
16746
|
+
return_tainted: s.returnTainted ?? false
|
|
16747
|
+
}))
|
|
16748
|
+
],
|
|
16749
|
+
sinks: [
|
|
16750
|
+
...baseConfig.sinks,
|
|
16751
|
+
...pluginSinks.map((s) => ({
|
|
16752
|
+
method: s.method,
|
|
16753
|
+
class: s.class,
|
|
16754
|
+
type: s.type,
|
|
16755
|
+
cwe: s.cwe,
|
|
16756
|
+
severity: s.severity,
|
|
16757
|
+
arg_positions: s.argPositions
|
|
16758
|
+
}))
|
|
16759
|
+
]
|
|
16760
|
+
};
|
|
16761
|
+
}
|
|
16762
|
+
}
|
|
16763
|
+
}
|
|
16312
16764
|
const preliminaryTaint = analyzeTaint(calls, types, baseConfig);
|
|
16313
16765
|
const taintedParameters = [];
|
|
16314
16766
|
for (const source of preliminaryTaint.sources) {
|
|
@@ -5546,6 +5546,9 @@ function extractCalls(tree, cache, language) {
|
|
|
5546
5546
|
const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
|
|
5547
5547
|
const isPython = detectedLanguage === "python";
|
|
5548
5548
|
const isRust = detectedLanguage === "rust";
|
|
5549
|
+
if (detectedLanguage === "bash") {
|
|
5550
|
+
return extractBashCalls(tree, cache);
|
|
5551
|
+
}
|
|
5549
5552
|
if (isRust) {
|
|
5550
5553
|
return extractRustCalls(tree, cache);
|
|
5551
5554
|
}
|
|
@@ -6235,6 +6238,88 @@ function inferTypeFromReceiverName(receiver) {
|
|
|
6235
6238
|
const lowerReceiver = receiver.toLowerCase();
|
|
6236
6239
|
return patterns[lowerReceiver] ?? null;
|
|
6237
6240
|
}
|
|
6241
|
+
function extractBashCalls(tree, cache) {
|
|
6242
|
+
const calls = [];
|
|
6243
|
+
const commands = getNodesFromCache(tree.rootNode, "command", cache);
|
|
6244
|
+
for (const cmd of commands) {
|
|
6245
|
+
const callInfo = extractBashCommandInfo(cmd);
|
|
6246
|
+
if (callInfo) {
|
|
6247
|
+
calls.push(callInfo);
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
return calls;
|
|
6251
|
+
}
|
|
6252
|
+
function extractBashCommandInfo(node) {
|
|
6253
|
+
const nameNode = node.childForFieldName("name");
|
|
6254
|
+
if (!nameNode) return null;
|
|
6255
|
+
const commandName = getNodeText(nameNode);
|
|
6256
|
+
if (!commandName) return null;
|
|
6257
|
+
const args2 = [];
|
|
6258
|
+
let position = 0;
|
|
6259
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6260
|
+
const child = node.child(i2);
|
|
6261
|
+
if (!child) continue;
|
|
6262
|
+
if (child === nameNode) continue;
|
|
6263
|
+
if (child.type.includes("redirect") || child.type === "heredoc_body" || child.type === "file_descriptor") {
|
|
6264
|
+
continue;
|
|
6265
|
+
}
|
|
6266
|
+
const expression = getNodeText(child);
|
|
6267
|
+
if (!expression.trim()) continue;
|
|
6268
|
+
const variable = extractBashVariableRef(child);
|
|
6269
|
+
args2.push({
|
|
6270
|
+
position: position++,
|
|
6271
|
+
expression,
|
|
6272
|
+
variable,
|
|
6273
|
+
literal: null
|
|
6274
|
+
});
|
|
6275
|
+
}
|
|
6276
|
+
const inMethod = findBashEnclosingFunction(node);
|
|
6277
|
+
return {
|
|
6278
|
+
method_name: commandName,
|
|
6279
|
+
receiver: null,
|
|
6280
|
+
arguments: args2,
|
|
6281
|
+
location: {
|
|
6282
|
+
line: node.startPosition.row + 1,
|
|
6283
|
+
column: node.startPosition.column
|
|
6284
|
+
},
|
|
6285
|
+
in_method: inMethod,
|
|
6286
|
+
resolved: false,
|
|
6287
|
+
resolution: { status: "external_method" }
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6290
|
+
function extractBashVariableRef(node) {
|
|
6291
|
+
const type = node.type;
|
|
6292
|
+
if (type === "simple_expansion" || type === "expansion") {
|
|
6293
|
+
return getNodeText(node).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6294
|
+
}
|
|
6295
|
+
if (type === "string" || type === "concatenation") {
|
|
6296
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6297
|
+
const child = node.child(i2);
|
|
6298
|
+
if (!child) continue;
|
|
6299
|
+
if (child.type === "simple_expansion" || child.type === "expansion") {
|
|
6300
|
+
return getNodeText(child).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
if (type === "word") {
|
|
6305
|
+
const text = getNodeText(node);
|
|
6306
|
+
if (text.startsWith("$")) {
|
|
6307
|
+
return text.slice(1).replace(/^\{/, "").replace(/\}$/, "");
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
return null;
|
|
6311
|
+
}
|
|
6312
|
+
function findBashEnclosingFunction(node) {
|
|
6313
|
+
let current = node.parent;
|
|
6314
|
+
while (current) {
|
|
6315
|
+
if (current.type === "function_definition") {
|
|
6316
|
+
const nameNode = current.childForFieldName("name");
|
|
6317
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
6318
|
+
}
|
|
6319
|
+
current = current.parent;
|
|
6320
|
+
}
|
|
6321
|
+
return null;
|
|
6322
|
+
}
|
|
6238
6323
|
function extractPythonCalls(tree, cache) {
|
|
6239
6324
|
const calls = [];
|
|
6240
6325
|
const context = buildPythonResolutionContext(tree, cache);
|
|
@@ -5481,6 +5481,9 @@ function extractCalls(tree, cache, language) {
|
|
|
5481
5481
|
const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
|
|
5482
5482
|
const isPython = detectedLanguage === "python";
|
|
5483
5483
|
const isRust = detectedLanguage === "rust";
|
|
5484
|
+
if (detectedLanguage === "bash") {
|
|
5485
|
+
return extractBashCalls(tree, cache);
|
|
5486
|
+
}
|
|
5484
5487
|
if (isRust) {
|
|
5485
5488
|
return extractRustCalls(tree, cache);
|
|
5486
5489
|
}
|
|
@@ -6170,6 +6173,88 @@ function inferTypeFromReceiverName(receiver) {
|
|
|
6170
6173
|
const lowerReceiver = receiver.toLowerCase();
|
|
6171
6174
|
return patterns[lowerReceiver] ?? null;
|
|
6172
6175
|
}
|
|
6176
|
+
function extractBashCalls(tree, cache) {
|
|
6177
|
+
const calls = [];
|
|
6178
|
+
const commands = getNodesFromCache(tree.rootNode, "command", cache);
|
|
6179
|
+
for (const cmd of commands) {
|
|
6180
|
+
const callInfo = extractBashCommandInfo(cmd);
|
|
6181
|
+
if (callInfo) {
|
|
6182
|
+
calls.push(callInfo);
|
|
6183
|
+
}
|
|
6184
|
+
}
|
|
6185
|
+
return calls;
|
|
6186
|
+
}
|
|
6187
|
+
function extractBashCommandInfo(node) {
|
|
6188
|
+
const nameNode = node.childForFieldName("name");
|
|
6189
|
+
if (!nameNode) return null;
|
|
6190
|
+
const commandName = getNodeText(nameNode);
|
|
6191
|
+
if (!commandName) return null;
|
|
6192
|
+
const args2 = [];
|
|
6193
|
+
let position = 0;
|
|
6194
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6195
|
+
const child = node.child(i2);
|
|
6196
|
+
if (!child) continue;
|
|
6197
|
+
if (child === nameNode) continue;
|
|
6198
|
+
if (child.type.includes("redirect") || child.type === "heredoc_body" || child.type === "file_descriptor") {
|
|
6199
|
+
continue;
|
|
6200
|
+
}
|
|
6201
|
+
const expression = getNodeText(child);
|
|
6202
|
+
if (!expression.trim()) continue;
|
|
6203
|
+
const variable = extractBashVariableRef(child);
|
|
6204
|
+
args2.push({
|
|
6205
|
+
position: position++,
|
|
6206
|
+
expression,
|
|
6207
|
+
variable,
|
|
6208
|
+
literal: null
|
|
6209
|
+
});
|
|
6210
|
+
}
|
|
6211
|
+
const inMethod = findBashEnclosingFunction(node);
|
|
6212
|
+
return {
|
|
6213
|
+
method_name: commandName,
|
|
6214
|
+
receiver: null,
|
|
6215
|
+
arguments: args2,
|
|
6216
|
+
location: {
|
|
6217
|
+
line: node.startPosition.row + 1,
|
|
6218
|
+
column: node.startPosition.column
|
|
6219
|
+
},
|
|
6220
|
+
in_method: inMethod,
|
|
6221
|
+
resolved: false,
|
|
6222
|
+
resolution: { status: "external_method" }
|
|
6223
|
+
};
|
|
6224
|
+
}
|
|
6225
|
+
function extractBashVariableRef(node) {
|
|
6226
|
+
const type = node.type;
|
|
6227
|
+
if (type === "simple_expansion" || type === "expansion") {
|
|
6228
|
+
return getNodeText(node).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6229
|
+
}
|
|
6230
|
+
if (type === "string" || type === "concatenation") {
|
|
6231
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6232
|
+
const child = node.child(i2);
|
|
6233
|
+
if (!child) continue;
|
|
6234
|
+
if (child.type === "simple_expansion" || child.type === "expansion") {
|
|
6235
|
+
return getNodeText(child).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
if (type === "word") {
|
|
6240
|
+
const text = getNodeText(node);
|
|
6241
|
+
if (text.startsWith("$")) {
|
|
6242
|
+
return text.slice(1).replace(/^\{/, "").replace(/\}$/, "");
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
return null;
|
|
6246
|
+
}
|
|
6247
|
+
function findBashEnclosingFunction(node) {
|
|
6248
|
+
let current = node.parent;
|
|
6249
|
+
while (current) {
|
|
6250
|
+
if (current.type === "function_definition") {
|
|
6251
|
+
const nameNode = current.childForFieldName("name");
|
|
6252
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
6253
|
+
}
|
|
6254
|
+
current = current.parent;
|
|
6255
|
+
}
|
|
6256
|
+
return null;
|
|
6257
|
+
}
|
|
6173
6258
|
function extractPythonCalls(tree, cache) {
|
|
6174
6259
|
const calls = [];
|
|
6175
6260
|
const context = buildPythonResolutionContext(tree, cache);
|