circle-ir 4.14.0 → 4.14.1

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.
@@ -10196,6 +10196,36 @@ function extractFieldDefs(body2, startId) {
10196
10196
  function extractJSParameterDefs(params, startId) {
10197
10197
  const defs = [];
10198
10198
  let id = startId;
10199
+ const addPatternDefs = (patternNode, line) => {
10200
+ if (patternNode.type === "identifier" || patternNode.type === "shorthand_property_identifier_pattern") {
10201
+ defs.push({
10202
+ id: id++,
10203
+ variable: getNodeText(patternNode),
10204
+ line,
10205
+ kind: "param"
10206
+ });
10207
+ return;
10208
+ }
10209
+ if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
10210
+ const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
10211
+ if (!nameNode) return;
10212
+ addPatternDefs(nameNode, line);
10213
+ return;
10214
+ }
10215
+ if (patternNode.type === "assignment_pattern") {
10216
+ const leftNode = patternNode.childForFieldName("left");
10217
+ if (leftNode) addPatternDefs(leftNode, line);
10218
+ return;
10219
+ }
10220
+ if (patternNode.type === "object_pattern" || patternNode.type === "array_pattern") {
10221
+ const destructDefs = extractDestructuringDefs(patternNode, id);
10222
+ for (const def of destructDefs) {
10223
+ def.kind = "param";
10224
+ defs.push(def);
10225
+ id++;
10226
+ }
10227
+ }
10228
+ };
10199
10229
  for (let i2 = 0; i2 < params.childCount; i2++) {
10200
10230
  const param = params.child(i2);
10201
10231
  if (!param) continue;
@@ -10243,14 +10273,7 @@ function extractJSParameterDefs(params, startId) {
10243
10273
  }
10244
10274
  } else if (param.type === "required_parameter" || param.type === "optional_parameter") {
10245
10275
  const patternNode = param.childForFieldName("pattern");
10246
- if (patternNode && patternNode.type === "identifier") {
10247
- defs.push({
10248
- id: id++,
10249
- variable: getNodeText(patternNode),
10250
- line: param.startPosition.row + 1,
10251
- kind: "param"
10252
- });
10253
- }
10276
+ if (patternNode) addPatternDefs(patternNode, param.startPosition.row + 1);
10254
10277
  }
10255
10278
  }
10256
10279
  return defs;
@@ -15766,7 +15789,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
15766
15789
  if (skipMethods.includes(method.name)) continue;
15767
15790
  for (const param of method.parameters) {
15768
15791
  const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
15769
- const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
15792
+ const isDestructureBinding = param.name.startsWith("{") || param.name.startsWith("[");
15793
+ const isTaintable = hasCSharpBindingAttr ? true : isDestructureBinding ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
15770
15794
  if (isTaintable) {
15771
15795
  const paramLine = param.line ?? method.start_line;
15772
15796
  sources.push({
@@ -19255,6 +19279,9 @@ function buildSanitizersByLine(sanitizers) {
19255
19279
  }
19256
19280
  return out2;
19257
19281
  }
19282
+ function concatOutsideStrings(expression) {
19283
+ return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
19284
+ }
19258
19285
  function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
19259
19286
  let graph;
19260
19287
  let sources;
@@ -19286,6 +19313,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
19286
19313
  }
19287
19314
  const taintedVars = [];
19288
19315
  const flows = [];
19316
+ const javaConcat = graph.ir.meta.language === "java";
19289
19317
  const reachableSinks = /* @__PURE__ */ new Map();
19290
19318
  const defsByLine = graph.defsByLine;
19291
19319
  const usesByLine = graph.usesByLine;
@@ -19322,12 +19350,14 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
19322
19350
  continue;
19323
19351
  }
19324
19352
  }
19325
- const candidateUses = arg.variable ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
19353
+ const expr = typeof arg.expression === "string" ? arg.expression : "";
19354
+ const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
19355
+ const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
19326
19356
  {
19327
19357
  for (const use of candidateUses) {
19328
19358
  if (use.def_id !== null) {
19329
19359
  if (allTaintedDefIds.has(use.def_id)) {
19330
- if (!arg.variable) {
19360
+ if (!arg.variable || compoundConcat) {
19331
19361
  if (typeof arg.expression !== "string" || arg.expression.length === 0) continue;
19332
19362
  const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
19333
19363
  if (!re.test(arg.expression)) continue;
@@ -37344,6 +37374,20 @@ function evaluateSimpleExpression(expr, symbols) {
37344
37374
  if (result !== null && !isNaN(result)) return String(Math.floor(result));
37345
37375
  return expr;
37346
37376
  }
37377
+ var ARGUMENT_IDENT_SKIP = /* @__PURE__ */ new Set(["new", "true", "false", "null", "this", "super"]);
37378
+ function isStringConcat(expression) {
37379
+ return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
37380
+ }
37381
+ function argumentIdentifiers(expression) {
37382
+ const stripped = expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ");
37383
+ const found = stripped.match(/[A-Za-z_$][A-Za-z0-9_$]*/g) ?? [];
37384
+ const out2 = [];
37385
+ for (const id of found) {
37386
+ if (ARGUMENT_IDENT_SKIP.has(id)) continue;
37387
+ if (!out2.includes(id)) out2.push(id);
37388
+ }
37389
+ return out2;
37390
+ }
37347
37391
  function isStringLiteralExpression(expr) {
37348
37392
  const trimmed = expr.trim();
37349
37393
  if (trimmed.length < 2) return false;
@@ -37415,20 +37459,27 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
37415
37459
  dangerousArgCount++;
37416
37460
  if (language === "bash" && arg.expression === call.method_name && !arg.variable && arg.literal == null) continue;
37417
37461
  if (arg.variable && !arg.expression?.includes("[")) {
37418
- const varName = arg.variable;
37419
- const scopedName = methodName ? `${methodName}:${varName}` : varName;
37420
- if (fieldNames.has(varName) && !isInSynchronizedBlock) {
37421
- allArgsAreClean = false;
37422
- continue;
37423
- }
37424
- if (sanitizedVars?.has(scopedName) || sanitizedVars?.has(varName)) continue;
37425
- if (taintedVars.has(scopedName) || taintedVars.has(varName)) {
37426
- allArgsAreClean = false;
37427
- continue;
37462
+ const expr = arg.expression ?? "";
37463
+ const concatNames = language === "java" && isStringConcat(expr) ? argumentIdentifiers(expr) : [];
37464
+ const names = concatNames.length > 0 ? concatNames : [arg.variable];
37465
+ let argClean = true;
37466
+ for (const varName of names) {
37467
+ const scopedName = methodName ? `${methodName}:${varName}` : varName;
37468
+ if (fieldNames.has(varName) && !isInSynchronizedBlock) {
37469
+ argClean = false;
37470
+ break;
37471
+ }
37472
+ if (sanitizedVars?.has(scopedName) || sanitizedVars?.has(varName)) continue;
37473
+ if (taintedVars.has(scopedName) || taintedVars.has(varName)) {
37474
+ argClean = false;
37475
+ break;
37476
+ }
37477
+ const symbolValue = symbols.get(scopedName) ?? symbols.get(varName);
37478
+ if (symbolValue && symbolValue.type !== "unknown") continue;
37479
+ argClean = false;
37480
+ break;
37428
37481
  }
37429
- const symbolValue = symbols.get(scopedName) ?? symbols.get(varName);
37430
- if (symbolValue && symbolValue.type !== "unknown") continue;
37431
- allArgsAreClean = false;
37482
+ if (!argClean) allArgsAreClean = false;
37432
37483
  } else {
37433
37484
  if (arg.literal != null) continue;
37434
37485
  if (arg.expression && !arg.variable && isStringLiteralExpression(arg.expression)) continue;
@@ -10242,6 +10242,36 @@ function extractFieldDefs(body2, startId) {
10242
10242
  function extractJSParameterDefs(params, startId) {
10243
10243
  const defs = [];
10244
10244
  let id = startId;
10245
+ const addPatternDefs = (patternNode, line) => {
10246
+ if (patternNode.type === "identifier" || patternNode.type === "shorthand_property_identifier_pattern") {
10247
+ defs.push({
10248
+ id: id++,
10249
+ variable: getNodeText(patternNode),
10250
+ line,
10251
+ kind: "param"
10252
+ });
10253
+ return;
10254
+ }
10255
+ if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
10256
+ const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
10257
+ if (!nameNode) return;
10258
+ addPatternDefs(nameNode, line);
10259
+ return;
10260
+ }
10261
+ if (patternNode.type === "assignment_pattern") {
10262
+ const leftNode = patternNode.childForFieldName("left");
10263
+ if (leftNode) addPatternDefs(leftNode, line);
10264
+ return;
10265
+ }
10266
+ if (patternNode.type === "object_pattern" || patternNode.type === "array_pattern") {
10267
+ const destructDefs = extractDestructuringDefs(patternNode, id);
10268
+ for (const def of destructDefs) {
10269
+ def.kind = "param";
10270
+ defs.push(def);
10271
+ id++;
10272
+ }
10273
+ }
10274
+ };
10245
10275
  for (let i2 = 0; i2 < params.childCount; i2++) {
10246
10276
  const param = params.child(i2);
10247
10277
  if (!param) continue;
@@ -10289,14 +10319,7 @@ function extractJSParameterDefs(params, startId) {
10289
10319
  }
10290
10320
  } else if (param.type === "required_parameter" || param.type === "optional_parameter") {
10291
10321
  const patternNode = param.childForFieldName("pattern");
10292
- if (patternNode && patternNode.type === "identifier") {
10293
- defs.push({
10294
- id: id++,
10295
- variable: getNodeText(patternNode),
10296
- line: param.startPosition.row + 1,
10297
- kind: "param"
10298
- });
10299
- }
10322
+ if (patternNode) addPatternDefs(patternNode, param.startPosition.row + 1);
10300
10323
  }
10301
10324
  }
10302
10325
  return defs;
@@ -15066,7 +15089,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
15066
15089
  if (skipMethods.includes(method.name)) continue;
15067
15090
  for (const param of method.parameters) {
15068
15091
  const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
15069
- const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
15092
+ const isDestructureBinding = param.name.startsWith("{") || param.name.startsWith("[");
15093
+ const isTaintable = hasCSharpBindingAttr ? true : isDestructureBinding ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
15070
15094
  if (isTaintable) {
15071
15095
  const paramLine = param.line ?? method.start_line;
15072
15096
  sources.push({
@@ -17220,6 +17244,9 @@ function buildSanitizersByLine(sanitizers) {
17220
17244
  }
17221
17245
  return out2;
17222
17246
  }
17247
+ function concatOutsideStrings(expression) {
17248
+ return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
17249
+ }
17223
17250
  function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
17224
17251
  let graph;
17225
17252
  let sources;
@@ -17251,6 +17278,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17251
17278
  }
17252
17279
  const taintedVars = [];
17253
17280
  const flows = [];
17281
+ const javaConcat = graph.ir.meta.language === "java";
17254
17282
  const reachableSinks = /* @__PURE__ */ new Map();
17255
17283
  const defsByLine = graph.defsByLine;
17256
17284
  const usesByLine = graph.usesByLine;
@@ -17287,12 +17315,14 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17287
17315
  continue;
17288
17316
  }
17289
17317
  }
17290
- const candidateUses = arg.variable ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17318
+ const expr = typeof arg.expression === "string" ? arg.expression : "";
17319
+ const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
17320
+ const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17291
17321
  {
17292
17322
  for (const use of candidateUses) {
17293
17323
  if (use.def_id !== null) {
17294
17324
  if (allTaintedDefIds.has(use.def_id)) {
17295
- if (!arg.variable) {
17325
+ if (!arg.variable || compoundConcat) {
17296
17326
  if (typeof arg.expression !== "string" || arg.expression.length === 0) continue;
17297
17327
  const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
17298
17328
  if (!re.test(arg.expression)) continue;
@@ -10176,6 +10176,36 @@ function extractFieldDefs(body2, startId) {
10176
10176
  function extractJSParameterDefs(params, startId) {
10177
10177
  const defs = [];
10178
10178
  let id = startId;
10179
+ const addPatternDefs = (patternNode, line) => {
10180
+ if (patternNode.type === "identifier" || patternNode.type === "shorthand_property_identifier_pattern") {
10181
+ defs.push({
10182
+ id: id++,
10183
+ variable: getNodeText(patternNode),
10184
+ line,
10185
+ kind: "param"
10186
+ });
10187
+ return;
10188
+ }
10189
+ if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
10190
+ const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
10191
+ if (!nameNode) return;
10192
+ addPatternDefs(nameNode, line);
10193
+ return;
10194
+ }
10195
+ if (patternNode.type === "assignment_pattern") {
10196
+ const leftNode = patternNode.childForFieldName("left");
10197
+ if (leftNode) addPatternDefs(leftNode, line);
10198
+ return;
10199
+ }
10200
+ if (patternNode.type === "object_pattern" || patternNode.type === "array_pattern") {
10201
+ const destructDefs = extractDestructuringDefs(patternNode, id);
10202
+ for (const def of destructDefs) {
10203
+ def.kind = "param";
10204
+ defs.push(def);
10205
+ id++;
10206
+ }
10207
+ }
10208
+ };
10179
10209
  for (let i2 = 0; i2 < params.childCount; i2++) {
10180
10210
  const param = params.child(i2);
10181
10211
  if (!param) continue;
@@ -10223,14 +10253,7 @@ function extractJSParameterDefs(params, startId) {
10223
10253
  }
10224
10254
  } else if (param.type === "required_parameter" || param.type === "optional_parameter") {
10225
10255
  const patternNode = param.childForFieldName("pattern");
10226
- if (patternNode && patternNode.type === "identifier") {
10227
- defs.push({
10228
- id: id++,
10229
- variable: getNodeText(patternNode),
10230
- line: param.startPosition.row + 1,
10231
- kind: "param"
10232
- });
10233
- }
10256
+ if (patternNode) addPatternDefs(patternNode, param.startPosition.row + 1);
10234
10257
  }
10235
10258
  }
10236
10259
  return defs;
@@ -15000,7 +15023,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
15000
15023
  if (skipMethods.includes(method.name)) continue;
15001
15024
  for (const param of method.parameters) {
15002
15025
  const hasCSharpBindingAttr = language === "csharp" && param.annotations.some((a) => CSHARP_BINDING_ATTRS.has(a));
15003
- const isTaintable = hasCSharpBindingAttr ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
15026
+ const isDestructureBinding = param.name.startsWith("{") || param.name.startsWith("[");
15027
+ const isTaintable = hasCSharpBindingAttr ? true : isDestructureBinding ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
15004
15028
  if (isTaintable) {
15005
15029
  const paramLine = param.line ?? method.start_line;
15006
15030
  sources.push({
@@ -17154,6 +17178,9 @@ function buildSanitizersByLine(sanitizers) {
17154
17178
  }
17155
17179
  return out2;
17156
17180
  }
17181
+ function concatOutsideStrings(expression) {
17182
+ return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
17183
+ }
17157
17184
  function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
17158
17185
  let graph;
17159
17186
  let sources;
@@ -17185,6 +17212,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17185
17212
  }
17186
17213
  const taintedVars = [];
17187
17214
  const flows = [];
17215
+ const javaConcat = graph.ir.meta.language === "java";
17188
17216
  const reachableSinks = /* @__PURE__ */ new Map();
17189
17217
  const defsByLine = graph.defsByLine;
17190
17218
  const usesByLine = graph.usesByLine;
@@ -17221,12 +17249,14 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17221
17249
  continue;
17222
17250
  }
17223
17251
  }
17224
- const candidateUses = arg.variable ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17252
+ const expr = typeof arg.expression === "string" ? arg.expression : "";
17253
+ const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
17254
+ const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17225
17255
  {
17226
17256
  for (const use of candidateUses) {
17227
17257
  if (use.def_id !== null) {
17228
17258
  if (allTaintedDefIds.has(use.def_id)) {
17229
- if (!arg.variable) {
17259
+ if (!arg.variable || compoundConcat) {
17230
17260
  if (typeof arg.expression !== "string" || arg.expression.length === 0) continue;
17231
17261
  const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
17232
17262
  if (!re.test(arg.expression)) continue;
@@ -804,6 +804,42 @@ function extractFieldDefs(body, startId) {
804
804
  function extractJSParameterDefs(params, startId) {
805
805
  const defs = [];
806
806
  let id = startId;
807
+ // TypeScript wraps every parameter in required_parameter / optional_parameter.
808
+ // The pattern may be an identifier or a destructuring pattern (`{ id }`,
809
+ // `{ a: { b } }`, `{ id } = default`). Bound names are param defs, matching
810
+ // the JavaScript object_pattern / array_pattern branches. (cognium-dev#664)
811
+ const addPatternDefs = (patternNode, line) => {
812
+ if (patternNode.type === 'identifier' || patternNode.type === 'shorthand_property_identifier_pattern') {
813
+ defs.push({
814
+ id: id++,
815
+ variable: getNodeText(patternNode),
816
+ line,
817
+ kind: 'param',
818
+ });
819
+ return;
820
+ }
821
+ if (patternNode.type === 'rest_pattern' || patternNode.type === 'rest_element') {
822
+ const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
823
+ if (!nameNode)
824
+ return;
825
+ addPatternDefs(nameNode, line);
826
+ return;
827
+ }
828
+ if (patternNode.type === 'assignment_pattern') {
829
+ const leftNode = patternNode.childForFieldName('left');
830
+ if (leftNode)
831
+ addPatternDefs(leftNode, line);
832
+ return;
833
+ }
834
+ if (patternNode.type === 'object_pattern' || patternNode.type === 'array_pattern') {
835
+ const destructDefs = extractDestructuringDefs(patternNode, id);
836
+ for (const def of destructDefs) {
837
+ def.kind = 'param';
838
+ defs.push(def);
839
+ id++;
840
+ }
841
+ }
842
+ };
807
843
  for (let i = 0; i < params.childCount; i++) {
808
844
  const param = params.child(i);
809
845
  if (!param)
@@ -865,14 +901,8 @@ function extractJSParameterDefs(params, startId) {
865
901
  // TypeScript formal parameters
866
902
  else if (param.type === 'required_parameter' || param.type === 'optional_parameter') {
867
903
  const patternNode = param.childForFieldName('pattern');
868
- if (patternNode && patternNode.type === 'identifier') {
869
- defs.push({
870
- id: id++,
871
- variable: getNodeText(patternNode),
872
- line: param.startPosition.row + 1,
873
- kind: 'param',
874
- });
875
- }
904
+ if (patternNode)
905
+ addPatternDefs(patternNode, param.startPosition.row + 1);
876
906
  }
877
907
  }
878
908
  return defs;