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.
Files changed (34) hide show
  1. package/README.md +3 -3
  2. package/configs/sinks/golang.json +20 -0
  3. package/dist/analysis/config-loader.d.ts.map +1 -1
  4. package/dist/analysis/config-loader.js +7 -2
  5. package/dist/analysis/config-loader.js.map +1 -1
  6. package/dist/analysis/interprocedural.js +1 -1
  7. package/dist/analysis/interprocedural.js.map +1 -1
  8. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +9 -0
  9. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  10. package/dist/analysis/passes/csharp-sanitizer-credit.js +26 -4
  11. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  12. package/dist/analysis/passes/library-profile-source-gate-pass.d.ts +19 -18
  13. package/dist/analysis/passes/library-profile-source-gate-pass.d.ts.map +1 -1
  14. package/dist/analysis/passes/library-profile-source-gate-pass.js +16 -15
  15. package/dist/analysis/passes/library-profile-source-gate-pass.js.map +1 -1
  16. package/dist/analysis/passes/scan-secrets-pass.d.ts +2 -2
  17. package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
  18. package/dist/analysis/passes/scan-secrets-pass.js +23 -7
  19. package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
  20. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  21. package/dist/analysis/taint-matcher.js +40 -6
  22. package/dist/analysis/taint-matcher.js.map +1 -1
  23. package/dist/analyzer.d.ts.map +1 -1
  24. package/dist/analyzer.js +40 -11
  25. package/dist/analyzer.js.map +1 -1
  26. package/dist/browser/circle-ir.js +140 -9
  27. package/dist/core/circle-ir-core.cjs +34 -5
  28. package/dist/core/circle-ir-core.js +34 -5
  29. package/dist/core/extractors/calls.js +18 -1
  30. package/dist/core/extractors/calls.js.map +1 -1
  31. package/dist/languages/plugins/go.d.ts.map +1 -1
  32. package/dist/languages/plugins/go.js +40 -0
  33. package/dist/languages/plugins/go.js.map +1 -1
  34. 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 Flow Execution (constructor)
12174
- { method: "CpsFlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
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 queryArg = call.arguments.find((a) => a.position === 0);
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
- if (hasPlaceholders && !hasConcatenation && call.arguments.length >= 2) {
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 Flow Execution (constructor)
12108
- { method: "CpsFlowExecution", class: "constructor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
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 queryArg = call.arguments.find((a) => a.position === 0);
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
- if (hasPlaceholders && !hasConcatenation && call.arguments.length >= 2) {
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 },