circle-ir 4.9.10 → 4.9.11

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.
@@ -15056,14 +15056,30 @@ var PYTHON_TAINTED_PATTERNS = [
15056
15056
  // regex registry so it's recognized in subscript/var contexts.
15057
15057
  { pattern: /\binput\s*\(/, sourceType: "io_input" }
15058
15058
  ];
15059
+ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
15060
+ const zeroArgCtors = /* @__PURE__ */ new Set();
15061
+ for (const call of calls) {
15062
+ if (call.is_constructor && call.arguments.length === 0) {
15063
+ zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
15064
+ }
15065
+ }
15066
+ return sinks.filter((sink) => {
15067
+ if (sink.type !== "sql_injection") return true;
15068
+ if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
15069
+ return false;
15070
+ }
15071
+ return true;
15072
+ });
15073
+ }
15059
15074
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
15060
15075
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
15061
15076
  const sources = findSources(calls, types, config.sources, sourceLines, language);
15062
15077
  let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
15063
15078
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
15064
15079
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
15080
+ const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
15065
15081
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
15066
- return { sources, sinks, sanitizers };
15082
+ return { sources, sinks: gatedSinks, sanitizers };
15067
15083
  }
15068
15084
  function sinkPatternAppliesTo(pattern, language) {
15069
15085
  if (language === void 0) return true;
@@ -15334,7 +15350,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
15334
15350
  }
15335
15351
  const sourceMap = /* @__PURE__ */ new Map();
15336
15352
  for (const source of sources) {
15337
- const key = `${source.line}:${source.type}`;
15353
+ const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
15338
15354
  const existing = sourceMap.get(key);
15339
15355
  if (!existing || source.confidence > existing.confidence) {
15340
15356
  sourceMap.set(key, source);
@@ -15952,6 +15968,15 @@ var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
15952
15968
  // hutool
15953
15969
  "RuntimeUtil"
15954
15970
  ]);
15971
+ function isRegexReceiver(receiver, sourceLines) {
15972
+ const r = (receiver ?? "").trim();
15973
+ if (r.length === 0) return false;
15974
+ if (r.startsWith("/") && /\/[a-z]*$/.test(r)) return true;
15975
+ if (/^(?:new\s+)?RegExp\s*\(/.test(r)) return true;
15976
+ if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines) return false;
15977
+ const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
15978
+ return sourceLines.some((l) => decl.test(l));
15979
+ }
15955
15980
  function isFunctionCallbackArgument(arg) {
15956
15981
  if (arg.literal !== null && arg.literal !== void 0) return false;
15957
15982
  const expr = (arg.expression ?? "").trim();
@@ -16134,6 +16159,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
16134
16159
  continue;
16135
16160
  }
16136
16161
  }
16162
+ if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
16163
+ continue;
16164
+ }
16165
+ if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
16166
+ continue;
16167
+ }
16137
16168
  if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
16138
16169
  continue;
16139
16170
  }
@@ -18784,6 +18815,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
18784
18815
  for (const source of sources) {
18785
18816
  const defsOnLine = defsByLine.get(source.line) ?? [];
18786
18817
  for (const def of defsOnLine) {
18818
+ if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
18819
+ continue;
18820
+ }
18787
18821
  tainted.push({
18788
18822
  variable: def.variable,
18789
18823
  defId: def.id,
@@ -36332,7 +36366,7 @@ var TaintPropagationPass = class {
36332
36366
  for (const f of paramFlows) {
36333
36367
  pushIfNew(f);
36334
36368
  }
36335
- const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
36369
+ const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language, types) ?? [];
36336
36370
  for (const f of exprScanFlows) {
36337
36371
  if (flowKeys.has(flowKey(f))) continue;
36338
36372
  const flowForCheck = {
@@ -36484,6 +36518,36 @@ var TaintPropagationPass = class {
36484
36518
  return bestByKey.get(key) === f;
36485
36519
  });
36486
36520
  }
36521
+ if (ctx.language === "csharp" && finalFlows.length > 1) {
36522
+ const CSHARP_ADO_EXECUTE = /^Execute(?:Reader|NonQuery|Scalar)(?:Async)?$/;
36523
+ const commandTextReceiverByLine = /* @__PURE__ */ new Map();
36524
+ const execLinesByReceiver = /* @__PURE__ */ new Map();
36525
+ for (const call of calls) {
36526
+ const receiver = (call.receiver ?? "").trim();
36527
+ if (!receiver) continue;
36528
+ if (call.method_name === "CommandText") {
36529
+ commandTextReceiverByLine.set(call.location.line, receiver);
36530
+ } else if (CSHARP_ADO_EXECUTE.test(call.method_name)) {
36531
+ const lines = execLinesByReceiver.get(receiver) ?? [];
36532
+ lines.push(call.location.line);
36533
+ execLinesByReceiver.set(receiver, lines);
36534
+ }
36535
+ }
36536
+ if (commandTextReceiverByLine.size > 0) {
36537
+ const reportedSqlSinkLines = new Set(
36538
+ finalFlows.filter((f) => f.sink_type === "sql_injection").map((f) => f.sink_line)
36539
+ );
36540
+ finalFlows = finalFlows.filter((f) => {
36541
+ if (f.sink_type !== "sql_injection") return true;
36542
+ const receiver = commandTextReceiverByLine.get(f.sink_line);
36543
+ if (receiver === void 0) return true;
36544
+ const execLines = execLinesByReceiver.get(receiver) ?? [];
36545
+ return !execLines.some(
36546
+ (line) => line > f.sink_line && reportedSqlSinkLines.has(line)
36547
+ );
36548
+ });
36549
+ }
36550
+ }
36487
36551
  return { flows: finalFlows };
36488
36552
  }
36489
36553
  };
@@ -36901,7 +36965,7 @@ function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
36901
36965
  }
36902
36966
  return false;
36903
36967
  }
36904
- function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language) {
36968
+ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language, types) {
36905
36969
  const flows = [];
36906
36970
  const sourcesWithVar = sources.filter(
36907
36971
  (s) => typeof s.variable === "string" && s.variable.length > 0
@@ -37158,12 +37222,32 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
37158
37222
  for (const s of sourcesWithVar) {
37159
37223
  if (s.line < anchor.line) anchor = s;
37160
37224
  }
37225
+ const methodAt = (line) => {
37226
+ for (const t of types ?? []) {
37227
+ for (const m of t.methods) {
37228
+ if (line >= m.start_line && line <= m.end_line) return m.name;
37229
+ }
37230
+ }
37231
+ return null;
37232
+ };
37161
37233
  const existingVars = new Set(sourcesWithVar.map((s) => s.variable));
37162
- for (const [varName] of derived) {
37234
+ for (const [varName, derivedLine] of derived) {
37163
37235
  if (!varName || existingVars.has(varName)) continue;
37236
+ const owner = methodAt(derivedLine);
37237
+ let scopedAnchor = anchor;
37238
+ if (owner) {
37239
+ let best;
37240
+ for (const s of sourcesWithVar) {
37241
+ const sOwner = s.in_method ?? methodAt(s.line);
37242
+ if (sOwner !== owner) continue;
37243
+ if (!best || s.line < best.line) best = s;
37244
+ }
37245
+ if (best) scopedAnchor = best;
37246
+ }
37164
37247
  sourcesWithVar.push({
37165
- ...anchor,
37166
- variable: varName
37248
+ ...scopedAnchor,
37249
+ variable: varName,
37250
+ ...owner ? { in_method: owner } : {}
37167
37251
  });
37168
37252
  existingVars.add(varName);
37169
37253
  }
@@ -14356,14 +14356,30 @@ var PYTHON_TAINTED_PATTERNS = [
14356
14356
  // regex registry so it's recognized in subscript/var contexts.
14357
14357
  { pattern: /\binput\s*\(/, sourceType: "io_input" }
14358
14358
  ];
14359
+ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
14360
+ const zeroArgCtors = /* @__PURE__ */ new Set();
14361
+ for (const call of calls) {
14362
+ if (call.is_constructor && call.arguments.length === 0) {
14363
+ zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
14364
+ }
14365
+ }
14366
+ return sinks.filter((sink) => {
14367
+ if (sink.type !== "sql_injection") return true;
14368
+ if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
14369
+ return false;
14370
+ }
14371
+ return true;
14372
+ });
14373
+ }
14359
14374
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
14360
14375
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
14361
14376
  const sources = findSources(calls, types, config.sources, sourceLines, language);
14362
14377
  let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
14363
14378
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
14364
14379
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
14380
+ const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
14365
14381
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
14366
- return { sources, sinks, sanitizers };
14382
+ return { sources, sinks: gatedSinks, sanitizers };
14367
14383
  }
14368
14384
  function sinkPatternAppliesTo(pattern, language) {
14369
14385
  if (language === void 0) return true;
@@ -14634,7 +14650,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
14634
14650
  }
14635
14651
  const sourceMap = /* @__PURE__ */ new Map();
14636
14652
  for (const source of sources) {
14637
- const key = `${source.line}:${source.type}`;
14653
+ const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
14638
14654
  const existing = sourceMap.get(key);
14639
14655
  if (!existing || source.confidence > existing.confidence) {
14640
14656
  sourceMap.set(key, source);
@@ -15252,6 +15268,15 @@ var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
15252
15268
  // hutool
15253
15269
  "RuntimeUtil"
15254
15270
  ]);
15271
+ function isRegexReceiver(receiver, sourceLines) {
15272
+ const r = (receiver ?? "").trim();
15273
+ if (r.length === 0) return false;
15274
+ if (r.startsWith("/") && /\/[a-z]*$/.test(r)) return true;
15275
+ if (/^(?:new\s+)?RegExp\s*\(/.test(r)) return true;
15276
+ if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines) return false;
15277
+ const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
15278
+ return sourceLines.some((l) => decl.test(l));
15279
+ }
15255
15280
  function isFunctionCallbackArgument(arg) {
15256
15281
  if (arg.literal !== null && arg.literal !== void 0) return false;
15257
15282
  const expr = (arg.expression ?? "").trim();
@@ -15434,6 +15459,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
15434
15459
  continue;
15435
15460
  }
15436
15461
  }
15462
+ if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
15463
+ continue;
15464
+ }
15465
+ if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
15466
+ continue;
15467
+ }
15437
15468
  if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
15438
15469
  continue;
15439
15470
  }
@@ -16749,6 +16780,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
16749
16780
  for (const source of sources) {
16750
16781
  const defsOnLine = defsByLine.get(source.line) ?? [];
16751
16782
  for (const def of defsOnLine) {
16783
+ if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
16784
+ continue;
16785
+ }
16752
16786
  tainted.push({
16753
16787
  variable: def.variable,
16754
16788
  defId: def.id,
@@ -14290,14 +14290,30 @@ var PYTHON_TAINTED_PATTERNS = [
14290
14290
  // regex registry so it's recognized in subscript/var contexts.
14291
14291
  { pattern: /\binput\s*\(/, sourceType: "io_input" }
14292
14292
  ];
14293
+ function dropCSharpObjectCarriedWaypoints(sinks, calls) {
14294
+ const zeroArgCtors = /* @__PURE__ */ new Set();
14295
+ for (const call of calls) {
14296
+ if (call.is_constructor && call.arguments.length === 0) {
14297
+ zeroArgCtors.add(`${call.location.line}:${call.method_name}`);
14298
+ }
14299
+ }
14300
+ return sinks.filter((sink) => {
14301
+ if (sink.type !== "sql_injection") return true;
14302
+ if (sink.method && zeroArgCtors.has(`${sink.line}:${sink.method}`)) {
14303
+ return false;
14304
+ }
14305
+ return true;
14306
+ });
14307
+ }
14293
14308
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
14294
14309
  const sourceLines = code !== void 0 ? code.split("\n") : void 0;
14295
14310
  const sources = findSources(calls, types, config.sources, sourceLines, language);
14296
14311
  let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
14297
14312
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
14298
14313
  const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
14314
+ const gatedSinks = language === "csharp" ? dropCSharpObjectCarriedWaypoints(sinks, calls) : sinks;
14299
14315
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
14300
- return { sources, sinks, sanitizers };
14316
+ return { sources, sinks: gatedSinks, sanitizers };
14301
14317
  }
14302
14318
  function sinkPatternAppliesTo(pattern, language) {
14303
14319
  if (language === void 0) return true;
@@ -14568,7 +14584,7 @@ function findSources(calls, types, patterns, sourceLines, language) {
14568
14584
  }
14569
14585
  const sourceMap = /* @__PURE__ */ new Map();
14570
14586
  for (const source of sources) {
14571
- const key = `${source.line}:${source.type}`;
14587
+ const key = source.type === "interprocedural_param" && source.variable ? `${source.line}:${source.type}:${source.variable}` : `${source.line}:${source.type}`;
14572
14588
  const existing = sourceMap.get(key);
14573
14589
  if (!existing || source.confidence > existing.confidence) {
14574
14590
  sourceMap.set(key, source);
@@ -15186,6 +15202,15 @@ var CWE_78_RECEIVER_ALLOWLIST = /* @__PURE__ */ new Set([
15186
15202
  // hutool
15187
15203
  "RuntimeUtil"
15188
15204
  ]);
15205
+ function isRegexReceiver(receiver, sourceLines) {
15206
+ const r = (receiver ?? "").trim();
15207
+ if (r.length === 0) return false;
15208
+ if (r.startsWith("/") && /\/[a-z]*$/.test(r)) return true;
15209
+ if (/^(?:new\s+)?RegExp\s*\(/.test(r)) return true;
15210
+ if (!/^[A-Za-z_$][\w$]*$/.test(r) || !sourceLines) return false;
15211
+ const decl = new RegExp(`(?:const|let|var)\\s+${escapeRe(r)}\\s*=\\s*(?:/|(?:new\\s+)?RegExp\\s*\\()`);
15212
+ return sourceLines.some((l) => decl.test(l));
15213
+ }
15189
15214
  function isFunctionCallbackArgument(arg) {
15190
15215
  if (arg.literal !== null && arg.literal !== void 0) return false;
15191
15216
  const expr = (arg.expression ?? "").trim();
@@ -15368,6 +15393,12 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
15368
15393
  continue;
15369
15394
  }
15370
15395
  }
15396
+ if (pattern.type === "command_injection" && call.method_name === "exec" && (language === "javascript" || language === "typescript") && isRegexReceiver(call.receiver, sourceLines)) {
15397
+ continue;
15398
+ }
15399
+ if (pattern.type === "code_injection" && language === "python" && call.method_name === "compile" && call.receiver && call.receiver !== "builtins") {
15400
+ continue;
15401
+ }
15371
15402
  if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
15372
15403
  continue;
15373
15404
  }
@@ -16683,6 +16714,9 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
16683
16714
  for (const source of sources) {
16684
16715
  const defsOnLine = defsByLine.get(source.line) ?? [];
16685
16716
  for (const def of defsOnLine) {
16717
+ if (source.type === "interprocedural_param" && source.variable && def.kind === "param" && def.variable !== source.variable) {
16718
+ continue;
16719
+ }
16686
16720
  tainted.push({
16687
16721
  variable: def.variable,
16688
16722
  defId: def.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "4.9.10",
3
+ "version": "4.9.11",
4
4
  "description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",