circle-ir 4.13.1 → 4.14.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/README.md +3 -3
- package/configs/sinks/golang.json +20 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +7 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/interprocedural.js +1 -1
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +9 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +26 -4
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/library-profile-source-gate-pass.d.ts +19 -18
- package/dist/analysis/passes/library-profile-source-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/library-profile-source-gate-pass.js +16 -15
- package/dist/analysis/passes/library-profile-source-gate-pass.js.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.d.ts +2 -2
- package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.js +23 -7
- package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +40 -6
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +40 -11
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +140 -9
- package/dist/core/circle-ir-core.cjs +34 -5
- package/dist/core/circle-ir-core.js +34 -5
- package/dist/core/extractors/calls.js +18 -1
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/languages/plugins/go.d.ts.map +1 -1
- package/dist/languages/plugins/go.js +40 -0
- package/dist/languages/plugins/go.js.map +1 -1
- package/package.json +1 -1
|
@@ -6230,6 +6230,13 @@ function csharpBareName(node) {
|
|
|
6230
6230
|
}
|
|
6231
6231
|
return getNodeText(node);
|
|
6232
6232
|
}
|
|
6233
|
+
function csharpCreatedSimpleType(creation) {
|
|
6234
|
+
const typeNode = creation.childForFieldName("type");
|
|
6235
|
+
if (!typeNode) return null;
|
|
6236
|
+
const bare = csharpBareName(typeNode).replace(/<[^>]*>/g, "");
|
|
6237
|
+
const simple = bare.split(".").pop()?.trim() ?? "";
|
|
6238
|
+
return simple && simple !== "var" ? simple : null;
|
|
6239
|
+
}
|
|
6233
6240
|
function extractCSharpCalls(tree, cache) {
|
|
6234
6241
|
const calls = [];
|
|
6235
6242
|
const typeMap = buildCSharpReceiverTypeMap(tree, cache);
|
|
@@ -6238,11 +6245,15 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6238
6245
|
const fn = inv.childForFieldName("function");
|
|
6239
6246
|
let methodName = "unknown";
|
|
6240
6247
|
let receiver = null;
|
|
6248
|
+
let receiverTypeFromCreation = null;
|
|
6241
6249
|
if (fn?.type === "member_access_expression") {
|
|
6242
6250
|
const nameNode = fn.childForFieldName("name");
|
|
6243
6251
|
const exprNode = fn.childForFieldName("expression");
|
|
6244
6252
|
methodName = nameNode ? csharpBareName(nameNode) : "unknown";
|
|
6245
6253
|
receiver = exprNode ? getNodeText(exprNode) : null;
|
|
6254
|
+
if (exprNode?.type === "object_creation_expression") {
|
|
6255
|
+
receiverTypeFromCreation = csharpCreatedSimpleType(exprNode);
|
|
6256
|
+
}
|
|
6246
6257
|
} else if (fn) {
|
|
6247
6258
|
methodName = csharpBareName(fn);
|
|
6248
6259
|
}
|
|
@@ -6260,7 +6271,7 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6260
6271
|
calls.push({
|
|
6261
6272
|
method_name: methodName,
|
|
6262
6273
|
receiver,
|
|
6263
|
-
receiver_type: receiver ? typeMap.get(receiver) ?? null : null,
|
|
6274
|
+
receiver_type: receiverTypeFromCreation ?? (receiver ? typeMap.get(receiver) ?? null : null),
|
|
6264
6275
|
receiver_type_fqn: null,
|
|
6265
6276
|
arguments: args2,
|
|
6266
6277
|
location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
|
|
@@ -11414,6 +11425,9 @@ var DEFAULT_SOURCES = [
|
|
|
11414
11425
|
// entry.getName() returns a path that may contain ../ — flowing into File()/FileOutputStream()
|
|
11415
11426
|
// is a classic Zip-Slip vulnerability.
|
|
11416
11427
|
{ method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11428
|
+
// JarEntry extends ZipEntry. A declared JarEntry does not match the ZipEntry
|
|
11429
|
+
// row, and a receiver not named `entry` misses the name heuristic. cognium-dev#620.
|
|
11430
|
+
{ method: "getName", class: "JarEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11417
11431
|
{ method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11418
11432
|
{ method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11419
11433
|
{ method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
@@ -12170,8 +12184,10 @@ var DEFAULT_SINKS = [
|
|
|
12170
12184
|
{ method: "checkout", class: "SCM", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12171
12185
|
{ method: "retrieve", class: "LibraryAdder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12172
12186
|
{ method: "add", class: "LibraryAdder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12173
|
-
// Jenkins CPS
|
|
12174
|
-
|
|
12187
|
+
// Jenkins CPS compiles the script argument. That is code injection (CWE-94),
|
|
12188
|
+
// the same class as GroovyShell.evaluate. Registered as command_injection it
|
|
12189
|
+
// was dropped by the CWE-78 constructor allowlist (#129). cognium-dev#619.
|
|
12190
|
+
{ method: "CpsFlowExecution", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
12175
12191
|
{ method: "CpsFlowDefinition", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12176
12192
|
{ method: "FlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12177
12193
|
// ActiveMQ control commands
|
|
@@ -15280,12 +15296,14 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
15280
15296
|
}
|
|
15281
15297
|
function isParameterizedQueryCall(call, pattern) {
|
|
15282
15298
|
if (pattern.type !== "sql_injection") return false;
|
|
15283
|
-
const
|
|
15299
|
+
const queryPos = pattern.arg_positions.length > 0 ? Math.min(...pattern.arg_positions) : 0;
|
|
15300
|
+
const queryArg = call.arguments.find((a) => a.position === queryPos);
|
|
15284
15301
|
if (queryArg) {
|
|
15285
15302
|
const queryText = queryArg.literal ?? queryArg.expression ?? "";
|
|
15286
15303
|
const hasPlaceholders = /(\?(?:\s|,|$|\))|\$\d+|:\w+|%s)/.test(queryText);
|
|
15287
15304
|
const hasConcatenation = /\+\s*[^+]/.test(queryText) || queryText.includes("${");
|
|
15288
|
-
|
|
15305
|
+
const hasBoundArg = queryPos === 0 ? call.arguments.length >= 2 : call.arguments.some((a) => a.position > queryPos);
|
|
15306
|
+
if (hasPlaceholders && !hasConcatenation && hasBoundArg) {
|
|
15289
15307
|
return true;
|
|
15290
15308
|
}
|
|
15291
15309
|
}
|
|
@@ -15935,6 +15953,14 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
15935
15953
|
}
|
|
15936
15954
|
return false;
|
|
15937
15955
|
}
|
|
15956
|
+
function isArgumentlessJsResponseWriter(call, pattern, language) {
|
|
15957
|
+
if (pattern.type !== "xss" || pattern.class !== "Response") return false;
|
|
15958
|
+
if (language !== "javascript" && language !== "typescript") return false;
|
|
15959
|
+
const positions = pattern.arg_positions;
|
|
15960
|
+
if (!positions || positions.length === 0) return false;
|
|
15961
|
+
const argc = call.arguments?.length ?? 0;
|
|
15962
|
+
return positions.every((pos) => pos >= argc);
|
|
15963
|
+
}
|
|
15938
15964
|
function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
|
|
15939
15965
|
const patternsForLanguage = language === void 0 ? patterns : patterns.filter((p) => sinkPatternAppliesTo(p, language));
|
|
15940
15966
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
@@ -15995,6 +16021,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15995
16021
|
continue;
|
|
15996
16022
|
}
|
|
15997
16023
|
}
|
|
16024
|
+
if (isArgumentlessJsResponseWriter(call, pattern, language)) {
|
|
16025
|
+
continue;
|
|
16026
|
+
}
|
|
15998
16027
|
if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
15999
16028
|
continue;
|
|
16000
16029
|
}
|
|
@@ -6164,6 +6164,13 @@ function csharpBareName(node) {
|
|
|
6164
6164
|
}
|
|
6165
6165
|
return getNodeText(node);
|
|
6166
6166
|
}
|
|
6167
|
+
function csharpCreatedSimpleType(creation) {
|
|
6168
|
+
const typeNode = creation.childForFieldName("type");
|
|
6169
|
+
if (!typeNode) return null;
|
|
6170
|
+
const bare = csharpBareName(typeNode).replace(/<[^>]*>/g, "");
|
|
6171
|
+
const simple = bare.split(".").pop()?.trim() ?? "";
|
|
6172
|
+
return simple && simple !== "var" ? simple : null;
|
|
6173
|
+
}
|
|
6167
6174
|
function extractCSharpCalls(tree, cache) {
|
|
6168
6175
|
const calls = [];
|
|
6169
6176
|
const typeMap = buildCSharpReceiverTypeMap(tree, cache);
|
|
@@ -6172,11 +6179,15 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6172
6179
|
const fn = inv.childForFieldName("function");
|
|
6173
6180
|
let methodName = "unknown";
|
|
6174
6181
|
let receiver = null;
|
|
6182
|
+
let receiverTypeFromCreation = null;
|
|
6175
6183
|
if (fn?.type === "member_access_expression") {
|
|
6176
6184
|
const nameNode = fn.childForFieldName("name");
|
|
6177
6185
|
const exprNode = fn.childForFieldName("expression");
|
|
6178
6186
|
methodName = nameNode ? csharpBareName(nameNode) : "unknown";
|
|
6179
6187
|
receiver = exprNode ? getNodeText(exprNode) : null;
|
|
6188
|
+
if (exprNode?.type === "object_creation_expression") {
|
|
6189
|
+
receiverTypeFromCreation = csharpCreatedSimpleType(exprNode);
|
|
6190
|
+
}
|
|
6180
6191
|
} else if (fn) {
|
|
6181
6192
|
methodName = csharpBareName(fn);
|
|
6182
6193
|
}
|
|
@@ -6194,7 +6205,7 @@ function extractCSharpCalls(tree, cache) {
|
|
|
6194
6205
|
calls.push({
|
|
6195
6206
|
method_name: methodName,
|
|
6196
6207
|
receiver,
|
|
6197
|
-
receiver_type: receiver ? typeMap.get(receiver) ?? null : null,
|
|
6208
|
+
receiver_type: receiverTypeFromCreation ?? (receiver ? typeMap.get(receiver) ?? null : null),
|
|
6198
6209
|
receiver_type_fqn: null,
|
|
6199
6210
|
arguments: args2,
|
|
6200
6211
|
location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
|
|
@@ -11348,6 +11359,9 @@ var DEFAULT_SOURCES = [
|
|
|
11348
11359
|
// entry.getName() returns a path that may contain ../ — flowing into File()/FileOutputStream()
|
|
11349
11360
|
// is a classic Zip-Slip vulnerability.
|
|
11350
11361
|
{ method: "getName", class: "ZipEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11362
|
+
// JarEntry extends ZipEntry. A declared JarEntry does not match the ZipEntry
|
|
11363
|
+
// row, and a receiver not named `entry` misses the name heuristic. cognium-dev#620.
|
|
11364
|
+
{ method: "getName", class: "JarEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11351
11365
|
{ method: "getName", class: "ZipArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11352
11366
|
{ method: "getName", class: "TarArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
11353
11367
|
{ method: "getName", class: "ArchiveEntry", type: "file_input", severity: "high", return_tainted: true },
|
|
@@ -12104,8 +12118,10 @@ var DEFAULT_SINKS = [
|
|
|
12104
12118
|
{ method: "checkout", class: "SCM", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12105
12119
|
{ method: "retrieve", class: "LibraryAdder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12106
12120
|
{ method: "add", class: "LibraryAdder", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12107
|
-
// Jenkins CPS
|
|
12108
|
-
|
|
12121
|
+
// Jenkins CPS compiles the script argument. That is code injection (CWE-94),
|
|
12122
|
+
// the same class as GroovyShell.evaluate. Registered as command_injection it
|
|
12123
|
+
// was dropped by the CWE-78 constructor allowlist (#129). cognium-dev#619.
|
|
12124
|
+
{ method: "CpsFlowExecution", class: "constructor", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
12109
12125
|
{ method: "CpsFlowDefinition", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12110
12126
|
{ method: "FlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
12111
12127
|
// ActiveMQ control commands
|
|
@@ -15214,12 +15230,14 @@ function isInterproceduralTaintableType(typeName, language) {
|
|
|
15214
15230
|
}
|
|
15215
15231
|
function isParameterizedQueryCall(call, pattern) {
|
|
15216
15232
|
if (pattern.type !== "sql_injection") return false;
|
|
15217
|
-
const
|
|
15233
|
+
const queryPos = pattern.arg_positions.length > 0 ? Math.min(...pattern.arg_positions) : 0;
|
|
15234
|
+
const queryArg = call.arguments.find((a) => a.position === queryPos);
|
|
15218
15235
|
if (queryArg) {
|
|
15219
15236
|
const queryText = queryArg.literal ?? queryArg.expression ?? "";
|
|
15220
15237
|
const hasPlaceholders = /(\?(?:\s|,|$|\))|\$\d+|:\w+|%s)/.test(queryText);
|
|
15221
15238
|
const hasConcatenation = /\+\s*[^+]/.test(queryText) || queryText.includes("${");
|
|
15222
|
-
|
|
15239
|
+
const hasBoundArg = queryPos === 0 ? call.arguments.length >= 2 : call.arguments.some((a) => a.position > queryPos);
|
|
15240
|
+
if (hasPlaceholders && !hasConcatenation && hasBoundArg) {
|
|
15223
15241
|
return true;
|
|
15224
15242
|
}
|
|
15225
15243
|
}
|
|
@@ -15869,6 +15887,14 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
|
|
|
15869
15887
|
}
|
|
15870
15888
|
return false;
|
|
15871
15889
|
}
|
|
15890
|
+
function isArgumentlessJsResponseWriter(call, pattern, language) {
|
|
15891
|
+
if (pattern.type !== "xss" || pattern.class !== "Response") return false;
|
|
15892
|
+
if (language !== "javascript" && language !== "typescript") return false;
|
|
15893
|
+
const positions = pattern.arg_positions;
|
|
15894
|
+
if (!positions || positions.length === 0) return false;
|
|
15895
|
+
const argc = call.arguments?.length ?? 0;
|
|
15896
|
+
return positions.every((pos) => pos >= argc);
|
|
15897
|
+
}
|
|
15872
15898
|
function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
|
|
15873
15899
|
const patternsForLanguage = language === void 0 ? patterns : patterns.filter((p) => sinkPatternAppliesTo(p, language));
|
|
15874
15900
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
@@ -15929,6 +15955,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
15929
15955
|
continue;
|
|
15930
15956
|
}
|
|
15931
15957
|
}
|
|
15958
|
+
if (isArgumentlessJsResponseWriter(call, pattern, language)) {
|
|
15959
|
+
continue;
|
|
15960
|
+
}
|
|
15932
15961
|
if ((pattern.type === "command_injection" || pattern.type === "sql_injection") && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
|
|
15933
15962
|
continue;
|
|
15934
15963
|
}
|
|
@@ -227,6 +227,19 @@ function csharpBareName(node) {
|
|
|
227
227
|
}
|
|
228
228
|
return getNodeText(node);
|
|
229
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Simple type of `new T(...)`. `new BinaryFormatter().Deserialize(s)` has no
|
|
232
|
+
* variable for the receiver-type map, so the creation expression itself is
|
|
233
|
+
* the type (#630).
|
|
234
|
+
*/
|
|
235
|
+
function csharpCreatedSimpleType(creation) {
|
|
236
|
+
const typeNode = creation.childForFieldName('type');
|
|
237
|
+
if (!typeNode)
|
|
238
|
+
return null;
|
|
239
|
+
const bare = csharpBareName(typeNode).replace(/<[^>]*>/g, '');
|
|
240
|
+
const simple = bare.split('.').pop()?.trim() ?? '';
|
|
241
|
+
return simple && simple !== 'var' ? simple : null;
|
|
242
|
+
}
|
|
230
243
|
function extractCSharpCalls(tree, cache) {
|
|
231
244
|
const calls = [];
|
|
232
245
|
const typeMap = buildCSharpReceiverTypeMap(tree, cache);
|
|
@@ -235,11 +248,15 @@ function extractCSharpCalls(tree, cache) {
|
|
|
235
248
|
const fn = inv.childForFieldName('function');
|
|
236
249
|
let methodName = 'unknown';
|
|
237
250
|
let receiver = null;
|
|
251
|
+
let receiverTypeFromCreation = null;
|
|
238
252
|
if (fn?.type === 'member_access_expression') {
|
|
239
253
|
const nameNode = fn.childForFieldName('name');
|
|
240
254
|
const exprNode = fn.childForFieldName('expression');
|
|
241
255
|
methodName = nameNode ? csharpBareName(nameNode) : 'unknown';
|
|
242
256
|
receiver = exprNode ? getNodeText(exprNode) : null;
|
|
257
|
+
if (exprNode?.type === 'object_creation_expression') {
|
|
258
|
+
receiverTypeFromCreation = csharpCreatedSimpleType(exprNode);
|
|
259
|
+
}
|
|
243
260
|
}
|
|
244
261
|
else if (fn) {
|
|
245
262
|
methodName = csharpBareName(fn);
|
|
@@ -271,7 +288,7 @@ function extractCSharpCalls(tree, cache) {
|
|
|
271
288
|
calls.push({
|
|
272
289
|
method_name: methodName,
|
|
273
290
|
receiver,
|
|
274
|
-
receiver_type: receiver ? (typeMap.get(receiver) ?? null) : null,
|
|
291
|
+
receiver_type: receiverTypeFromCreation ?? (receiver ? (typeMap.get(receiver) ?? null) : null),
|
|
275
292
|
receiver_type_fqn: null,
|
|
276
293
|
arguments: args,
|
|
277
294
|
location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
|