circle-ir 4.14.0 → 4.14.2

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 (43) hide show
  1. package/dist/analysis/config-loader.d.ts.map +1 -1
  2. package/dist/analysis/config-loader.js +9 -2
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/passes/insecure-cookie-pass.d.ts +11 -4
  5. package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
  6. package/dist/analysis/passes/insecure-cookie-pass.js +30 -15
  7. package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
  8. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  9. package/dist/analysis/passes/interprocedural-pass.js +11 -2
  10. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  11. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  12. package/dist/analysis/passes/language-sources-pass.js +52 -0
  13. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  14. package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
  15. package/dist/analysis/passes/scan-secrets-pass.js +24 -3
  16. package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
  17. package/dist/analysis/passes/sink-filter-pass.d.ts +1 -1
  18. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  19. package/dist/analysis/passes/sink-filter-pass.js +95 -38
  20. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  21. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  22. package/dist/analysis/passes/taint-propagation-pass.js +11 -3
  23. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  24. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  25. package/dist/analysis/taint-matcher.js +14 -5
  26. package/dist/analysis/taint-matcher.js.map +1 -1
  27. package/dist/analysis/taint-propagation.d.ts +7 -4
  28. package/dist/analysis/taint-propagation.d.ts.map +1 -1
  29. package/dist/analysis/taint-propagation.js +132 -3
  30. package/dist/analysis/taint-propagation.js.map +1 -1
  31. package/dist/analyzer.js +1 -1
  32. package/dist/analyzer.js.map +1 -1
  33. package/dist/browser/circle-ir.js +339 -64
  34. package/dist/core/circle-ir-core.cjs +159 -15
  35. package/dist/core/circle-ir-core.js +159 -15
  36. package/dist/core/extractors/calls.js +19 -0
  37. package/dist/core/extractors/calls.js.map +1 -1
  38. package/dist/core/extractors/dfg.js +38 -8
  39. package/dist/core/extractors/dfg.js.map +1 -1
  40. package/dist/languages/plugins/go.d.ts.map +1 -1
  41. package/dist/languages/plugins/go.js +19 -0
  42. package/dist/languages/plugins/go.js.map +1 -1
  43. package/package.json +1 -1
@@ -8080,6 +8080,17 @@ function extractGoCallInfo(node) {
8080
8080
  if (operand.type === "identifier") {
8081
8081
  const resolved = resolveGoLocalReceiverType(opText, node);
8082
8082
  receiver = resolved !== null ? resolved : opText;
8083
+ } else if (operand.type === "selector_expression" && methodName === "Get") {
8084
+ const fieldNode = operand.childForFieldName("field");
8085
+ const inner = operand.childForFieldName("operand");
8086
+ const fieldName = fieldNode ? getNodeText(fieldNode) : "";
8087
+ const innerName = inner && inner.type === "identifier" ? getNodeText(inner) : null;
8088
+ const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
8089
+ if (innerType === "Request" && (fieldName === "PostForm" || fieldName === "Form")) {
8090
+ receiver = fieldName === "PostForm" ? "RequestPostForm" : "RequestForm";
8091
+ } else {
8092
+ receiver = opText;
8093
+ }
8083
8094
  } else {
8084
8095
  receiver = opText;
8085
8096
  }
@@ -10242,6 +10253,36 @@ function extractFieldDefs(body2, startId) {
10242
10253
  function extractJSParameterDefs(params, startId) {
10243
10254
  const defs = [];
10244
10255
  let id = startId;
10256
+ const addPatternDefs = (patternNode, line) => {
10257
+ if (patternNode.type === "identifier" || patternNode.type === "shorthand_property_identifier_pattern") {
10258
+ defs.push({
10259
+ id: id++,
10260
+ variable: getNodeText(patternNode),
10261
+ line,
10262
+ kind: "param"
10263
+ });
10264
+ return;
10265
+ }
10266
+ if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
10267
+ const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
10268
+ if (!nameNode) return;
10269
+ addPatternDefs(nameNode, line);
10270
+ return;
10271
+ }
10272
+ if (patternNode.type === "assignment_pattern") {
10273
+ const leftNode = patternNode.childForFieldName("left");
10274
+ if (leftNode) addPatternDefs(leftNode, line);
10275
+ return;
10276
+ }
10277
+ if (patternNode.type === "object_pattern" || patternNode.type === "array_pattern") {
10278
+ const destructDefs = extractDestructuringDefs(patternNode, id);
10279
+ for (const def of destructDefs) {
10280
+ def.kind = "param";
10281
+ defs.push(def);
10282
+ id++;
10283
+ }
10284
+ }
10285
+ };
10245
10286
  for (let i2 = 0; i2 < params.childCount; i2++) {
10246
10287
  const param = params.child(i2);
10247
10288
  if (!param) continue;
@@ -10289,14 +10330,7 @@ function extractJSParameterDefs(params, startId) {
10289
10330
  }
10290
10331
  } else if (param.type === "required_parameter" || param.type === "optional_parameter") {
10291
10332
  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
- }
10333
+ if (patternNode) addPatternDefs(patternNode, param.startPosition.row + 1);
10300
10334
  }
10301
10335
  }
10302
10336
  return defs;
@@ -12425,8 +12459,15 @@ var DEFAULT_SINKS = [
12425
12459
  { method: "setExpireTimeSeconds", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
12426
12460
  { method: "configureJarCase", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12427
12461
  { method: "toString", class: "StringUtils", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
12428
- // Wildfly/Undertow servlet resource manager
12429
- { method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12462
+ // Wildfly/Undertow servlet resource manager.
12463
+ // ResourceManager / PathResourceManager / FileResourceManager /
12464
+ // ClassPathResourceManager are the interface and implementations WildFly
12465
+ // calls through (cognium-dev#622, CVE-2018-1047). Class-qualified only.
12466
+ { method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12467
+ { method: "getResource", class: "ResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12468
+ { method: "getResource", class: "PathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12469
+ { method: "getResource", class: "FileResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12470
+ { method: "getResource", class: "ClassPathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12430
12471
  // Yamcs file system bucket
12431
12472
  { method: "deleteObject", class: "FileSystemBucket", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12432
12473
  // RocketMQ validators
@@ -15066,7 +15107,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
15066
15107
  if (skipMethods.includes(method.name)) continue;
15067
15108
  for (const param of method.parameters) {
15068
15109
  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;
15110
+ const isDestructureBinding = param.name.startsWith("{") || param.name.startsWith("[");
15111
+ const isTaintable = hasCSharpBindingAttr ? true : isDestructureBinding ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
15070
15112
  if (isTaintable) {
15071
15113
  const paramLine = param.line ?? method.start_line;
15072
15114
  sources.push({
@@ -15296,7 +15338,8 @@ function isInterproceduralTaintableType(typeName, language) {
15296
15338
  }
15297
15339
  function isParameterizedQueryCall(call, pattern) {
15298
15340
  if (pattern.type !== "sql_injection") return false;
15299
- const queryPos = pattern.arg_positions.length > 0 ? Math.min(...pattern.arg_positions) : 0;
15341
+ const positions = pattern.arg_positions ?? [];
15342
+ const queryPos = positions.length > 0 ? Math.min(...positions) : 0;
15300
15343
  const queryArg = call.arguments.find((a) => a.position === queryPos);
15301
15344
  if (queryArg) {
15302
15345
  const queryText = queryArg.literal ?? queryArg.expression ?? "";
@@ -17220,6 +17263,9 @@ function buildSanitizersByLine(sanitizers) {
17220
17263
  }
17221
17264
  return out2;
17222
17265
  }
17266
+ function concatOutsideStrings(expression) {
17267
+ return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
17268
+ }
17223
17269
  function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
17224
17270
  let graph;
17225
17271
  let sources;
@@ -17251,6 +17297,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17251
17297
  }
17252
17298
  const taintedVars = [];
17253
17299
  const flows = [];
17300
+ const javaConcat = graph.ir.meta.language === "java";
17254
17301
  const reachableSinks = /* @__PURE__ */ new Map();
17255
17302
  const defsByLine = graph.defsByLine;
17256
17303
  const usesByLine = graph.usesByLine;
@@ -17287,23 +17334,40 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17287
17334
  continue;
17288
17335
  }
17289
17336
  }
17290
- const candidateUses = arg.variable ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17337
+ const expr = typeof arg.expression === "string" ? arg.expression : "";
17338
+ const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
17339
+ const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17291
17340
  {
17292
17341
  for (const use of candidateUses) {
17293
17342
  if (use.def_id !== null) {
17294
17343
  if (allTaintedDefIds.has(use.def_id)) {
17295
- if (!arg.variable) {
17344
+ if (!arg.variable || compoundConcat) {
17296
17345
  if (typeof arg.expression !== "string" || arg.expression.length === 0) continue;
17297
17346
  const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
17298
17347
  if (!re.test(arg.expression)) continue;
17299
17348
  }
17300
17349
  const taintInfo = taintByDefId.get(use.def_id);
17301
17350
  if (taintInfo) {
17351
+ let sanMap = sanitizersByLine;
17352
+ if (identifierOutsideSanitizerCalls(
17353
+ use.variable,
17354
+ callsAtSink,
17355
+ sanitizersByLine.get(sink.line) ?? [],
17356
+ sink.type
17357
+ )) {
17358
+ const stripped = new Map(sanitizersByLine);
17359
+ const kept = (sanitizersByLine.get(sink.line) ?? []).filter(
17360
+ (s) => !sanitizerCoversSink(s, sink.type)
17361
+ );
17362
+ if (kept.length === 0) stripped.delete(sink.line);
17363
+ else stripped.set(sink.line, kept);
17364
+ sanMap = stripped;
17365
+ }
17302
17366
  const isSanitized = checkSanitized(
17303
17367
  taintInfo.line,
17304
17368
  sink.line,
17305
17369
  sink.type,
17306
- sanitizersByLine,
17370
+ sanMap,
17307
17371
  {
17308
17372
  startDefId: use.def_id,
17309
17373
  chainsByToDef: graph.chainsByToDef,
@@ -17457,6 +17521,86 @@ var KNOWN_SINK_TYPES = /* @__PURE__ */ new Set([
17457
17521
  "crlf",
17458
17522
  "mass_assignment"
17459
17523
  ]);
17524
+ function readBalancedArg(text, openParen) {
17525
+ let depth = 0;
17526
+ let quote = null;
17527
+ let escaped = false;
17528
+ for (let i2 = openParen; i2 < text.length; i2++) {
17529
+ const c = text[i2];
17530
+ if (quote) {
17531
+ if (escaped) {
17532
+ escaped = false;
17533
+ continue;
17534
+ }
17535
+ if (c === "\\") {
17536
+ escaped = true;
17537
+ continue;
17538
+ }
17539
+ if (c === quote) quote = null;
17540
+ continue;
17541
+ }
17542
+ if (c === '"' || c === "'") {
17543
+ quote = c;
17544
+ continue;
17545
+ }
17546
+ if (c === "(") depth++;
17547
+ else if (c === ")") {
17548
+ depth--;
17549
+ if (depth === 0) return text.slice(openParen + 1, i2);
17550
+ }
17551
+ }
17552
+ return null;
17553
+ }
17554
+ function sanitizerCallRefs(sanitizers, sinkType) {
17555
+ const refs = [];
17556
+ for (const san of sanitizers) {
17557
+ if (!sanitizerCoversSink(san, sinkType)) continue;
17558
+ const m = /(?:(\w+)\.)?(\w+)\(\)/.exec(san.method);
17559
+ if (!m?.[2]) continue;
17560
+ refs.push({ receiver: m[1], methodName: m[2] });
17561
+ }
17562
+ return refs;
17563
+ }
17564
+ function maskSanitizerCalls(expr, refs) {
17565
+ let out2 = expr;
17566
+ for (const ref of refs) {
17567
+ const needles = ref.receiver ? [`${ref.receiver}.${ref.methodName}`, ref.methodName] : [ref.methodName];
17568
+ for (const needle of needles) {
17569
+ const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
17570
+ for (let guard = 0; guard < 8; guard++) {
17571
+ const m = re.exec(out2);
17572
+ if (!m) break;
17573
+ const open = m.index + m[0].length - 1;
17574
+ const arg = readBalancedArg(out2, open);
17575
+ if (arg === null) break;
17576
+ out2 = out2.slice(0, m.index) + " " + out2.slice(open + arg.length + 2);
17577
+ }
17578
+ }
17579
+ }
17580
+ return out2;
17581
+ }
17582
+ function blankStringLiterals(expr) {
17583
+ return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
17584
+ }
17585
+ function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType) {
17586
+ if (!/^[A-Za-z_$][\w$]*$/.test(variable)) return false;
17587
+ const refs = sanitizerCallRefs(sanitizers, sinkType);
17588
+ if (refs.length === 0) return false;
17589
+ const re = new RegExp(
17590
+ `(?:^|[^A-Za-z0-9_$])${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`
17591
+ );
17592
+ for (const call of calls) {
17593
+ if (refs.some((r) => r.methodName === call.method_name)) continue;
17594
+ for (const arg of call.arguments) {
17595
+ const expr = arg.expression ?? "";
17596
+ if (!expr) continue;
17597
+ const masked = maskSanitizerCalls(expr, refs);
17598
+ if (masked === expr) continue;
17599
+ if (re.test(blankStringLiterals(masked))) return true;
17600
+ }
17601
+ }
17602
+ return false;
17603
+ }
17460
17604
  function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
17461
17605
  const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
17462
17606
  const sanitizersAtTarget = sanitizersByLine.get(toLine);
@@ -8014,6 +8014,17 @@ function extractGoCallInfo(node) {
8014
8014
  if (operand.type === "identifier") {
8015
8015
  const resolved = resolveGoLocalReceiverType(opText, node);
8016
8016
  receiver = resolved !== null ? resolved : opText;
8017
+ } else if (operand.type === "selector_expression" && methodName === "Get") {
8018
+ const fieldNode = operand.childForFieldName("field");
8019
+ const inner = operand.childForFieldName("operand");
8020
+ const fieldName = fieldNode ? getNodeText(fieldNode) : "";
8021
+ const innerName = inner && inner.type === "identifier" ? getNodeText(inner) : null;
8022
+ const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
8023
+ if (innerType === "Request" && (fieldName === "PostForm" || fieldName === "Form")) {
8024
+ receiver = fieldName === "PostForm" ? "RequestPostForm" : "RequestForm";
8025
+ } else {
8026
+ receiver = opText;
8027
+ }
8017
8028
  } else {
8018
8029
  receiver = opText;
8019
8030
  }
@@ -10176,6 +10187,36 @@ function extractFieldDefs(body2, startId) {
10176
10187
  function extractJSParameterDefs(params, startId) {
10177
10188
  const defs = [];
10178
10189
  let id = startId;
10190
+ const addPatternDefs = (patternNode, line) => {
10191
+ if (patternNode.type === "identifier" || patternNode.type === "shorthand_property_identifier_pattern") {
10192
+ defs.push({
10193
+ id: id++,
10194
+ variable: getNodeText(patternNode),
10195
+ line,
10196
+ kind: "param"
10197
+ });
10198
+ return;
10199
+ }
10200
+ if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
10201
+ const nameNode = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
10202
+ if (!nameNode) return;
10203
+ addPatternDefs(nameNode, line);
10204
+ return;
10205
+ }
10206
+ if (patternNode.type === "assignment_pattern") {
10207
+ const leftNode = patternNode.childForFieldName("left");
10208
+ if (leftNode) addPatternDefs(leftNode, line);
10209
+ return;
10210
+ }
10211
+ if (patternNode.type === "object_pattern" || patternNode.type === "array_pattern") {
10212
+ const destructDefs = extractDestructuringDefs(patternNode, id);
10213
+ for (const def of destructDefs) {
10214
+ def.kind = "param";
10215
+ defs.push(def);
10216
+ id++;
10217
+ }
10218
+ }
10219
+ };
10179
10220
  for (let i2 = 0; i2 < params.childCount; i2++) {
10180
10221
  const param = params.child(i2);
10181
10222
  if (!param) continue;
@@ -10223,14 +10264,7 @@ function extractJSParameterDefs(params, startId) {
10223
10264
  }
10224
10265
  } else if (param.type === "required_parameter" || param.type === "optional_parameter") {
10225
10266
  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
- }
10267
+ if (patternNode) addPatternDefs(patternNode, param.startPosition.row + 1);
10234
10268
  }
10235
10269
  }
10236
10270
  return defs;
@@ -12359,8 +12393,15 @@ var DEFAULT_SINKS = [
12359
12393
  { method: "setExpireTimeSeconds", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
12360
12394
  { method: "configureJarCase", class: "StaticFilesConfiguration", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12361
12395
  { method: "toString", class: "StringUtils", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
12362
- // Wildfly/Undertow servlet resource manager
12363
- { method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12396
+ // Wildfly/Undertow servlet resource manager.
12397
+ // ResourceManager / PathResourceManager / FileResourceManager /
12398
+ // ClassPathResourceManager are the interface and implementations WildFly
12399
+ // calls through (cognium-dev#622, CVE-2018-1047). Class-qualified only.
12400
+ { method: "getResource", class: "ServletResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12401
+ { method: "getResource", class: "ResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12402
+ { method: "getResource", class: "PathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12403
+ { method: "getResource", class: "FileResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12404
+ { method: "getResource", class: "ClassPathResourceManager", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
12364
12405
  // Yamcs file system bucket
12365
12406
  { method: "deleteObject", class: "FileSystemBucket", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
12366
12407
  // RocketMQ validators
@@ -15000,7 +15041,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
15000
15041
  if (skipMethods.includes(method.name)) continue;
15001
15042
  for (const param of method.parameters) {
15002
15043
  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;
15044
+ const isDestructureBinding = param.name.startsWith("{") || param.name.startsWith("[");
15045
+ const isTaintable = hasCSharpBindingAttr ? true : isDestructureBinding ? true : param.type ? isInterproceduralTaintableType(param.type, language) : true;
15004
15046
  if (isTaintable) {
15005
15047
  const paramLine = param.line ?? method.start_line;
15006
15048
  sources.push({
@@ -15230,7 +15272,8 @@ function isInterproceduralTaintableType(typeName, language) {
15230
15272
  }
15231
15273
  function isParameterizedQueryCall(call, pattern) {
15232
15274
  if (pattern.type !== "sql_injection") return false;
15233
- const queryPos = pattern.arg_positions.length > 0 ? Math.min(...pattern.arg_positions) : 0;
15275
+ const positions = pattern.arg_positions ?? [];
15276
+ const queryPos = positions.length > 0 ? Math.min(...positions) : 0;
15234
15277
  const queryArg = call.arguments.find((a) => a.position === queryPos);
15235
15278
  if (queryArg) {
15236
15279
  const queryText = queryArg.literal ?? queryArg.expression ?? "";
@@ -17154,6 +17197,9 @@ function buildSanitizersByLine(sanitizers) {
17154
17197
  }
17155
17198
  return out2;
17156
17199
  }
17200
+ function concatOutsideStrings(expression) {
17201
+ return expression.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, " ").includes("+");
17202
+ }
17157
17203
  function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
17158
17204
  let graph;
17159
17205
  let sources;
@@ -17185,6 +17231,7 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17185
17231
  }
17186
17232
  const taintedVars = [];
17187
17233
  const flows = [];
17234
+ const javaConcat = graph.ir.meta.language === "java";
17188
17235
  const reachableSinks = /* @__PURE__ */ new Map();
17189
17236
  const defsByLine = graph.defsByLine;
17190
17237
  const usesByLine = graph.usesByLine;
@@ -17221,23 +17268,40 @@ function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanit
17221
17268
  continue;
17222
17269
  }
17223
17270
  }
17224
- const candidateUses = arg.variable ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17271
+ const expr = typeof arg.expression === "string" ? arg.expression : "";
17272
+ const compoundConcat = javaConcat && !!arg.variable && expr !== arg.variable && concatOutsideStrings(expr);
17273
+ const candidateUses = arg.variable && !compoundConcat ? usesAtSink.filter((u) => u.variable === arg.variable) : usesAtSink;
17225
17274
  {
17226
17275
  for (const use of candidateUses) {
17227
17276
  if (use.def_id !== null) {
17228
17277
  if (allTaintedDefIds.has(use.def_id)) {
17229
- if (!arg.variable) {
17278
+ if (!arg.variable || compoundConcat) {
17230
17279
  if (typeof arg.expression !== "string" || arg.expression.length === 0) continue;
17231
17280
  const re = new RegExp(`(?:^|[^A-Za-z0-9_$])${use.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`);
17232
17281
  if (!re.test(arg.expression)) continue;
17233
17282
  }
17234
17283
  const taintInfo = taintByDefId.get(use.def_id);
17235
17284
  if (taintInfo) {
17285
+ let sanMap = sanitizersByLine;
17286
+ if (identifierOutsideSanitizerCalls(
17287
+ use.variable,
17288
+ callsAtSink,
17289
+ sanitizersByLine.get(sink.line) ?? [],
17290
+ sink.type
17291
+ )) {
17292
+ const stripped = new Map(sanitizersByLine);
17293
+ const kept = (sanitizersByLine.get(sink.line) ?? []).filter(
17294
+ (s) => !sanitizerCoversSink(s, sink.type)
17295
+ );
17296
+ if (kept.length === 0) stripped.delete(sink.line);
17297
+ else stripped.set(sink.line, kept);
17298
+ sanMap = stripped;
17299
+ }
17236
17300
  const isSanitized = checkSanitized(
17237
17301
  taintInfo.line,
17238
17302
  sink.line,
17239
17303
  sink.type,
17240
- sanitizersByLine,
17304
+ sanMap,
17241
17305
  {
17242
17306
  startDefId: use.def_id,
17243
17307
  chainsByToDef: graph.chainsByToDef,
@@ -17391,6 +17455,86 @@ var KNOWN_SINK_TYPES = /* @__PURE__ */ new Set([
17391
17455
  "crlf",
17392
17456
  "mass_assignment"
17393
17457
  ]);
17458
+ function readBalancedArg(text, openParen) {
17459
+ let depth = 0;
17460
+ let quote = null;
17461
+ let escaped = false;
17462
+ for (let i2 = openParen; i2 < text.length; i2++) {
17463
+ const c = text[i2];
17464
+ if (quote) {
17465
+ if (escaped) {
17466
+ escaped = false;
17467
+ continue;
17468
+ }
17469
+ if (c === "\\") {
17470
+ escaped = true;
17471
+ continue;
17472
+ }
17473
+ if (c === quote) quote = null;
17474
+ continue;
17475
+ }
17476
+ if (c === '"' || c === "'") {
17477
+ quote = c;
17478
+ continue;
17479
+ }
17480
+ if (c === "(") depth++;
17481
+ else if (c === ")") {
17482
+ depth--;
17483
+ if (depth === 0) return text.slice(openParen + 1, i2);
17484
+ }
17485
+ }
17486
+ return null;
17487
+ }
17488
+ function sanitizerCallRefs(sanitizers, sinkType) {
17489
+ const refs = [];
17490
+ for (const san of sanitizers) {
17491
+ if (!sanitizerCoversSink(san, sinkType)) continue;
17492
+ const m = /(?:(\w+)\.)?(\w+)\(\)/.exec(san.method);
17493
+ if (!m?.[2]) continue;
17494
+ refs.push({ receiver: m[1], methodName: m[2] });
17495
+ }
17496
+ return refs;
17497
+ }
17498
+ function maskSanitizerCalls(expr, refs) {
17499
+ let out2 = expr;
17500
+ for (const ref of refs) {
17501
+ const needles = ref.receiver ? [`${ref.receiver}.${ref.methodName}`, ref.methodName] : [ref.methodName];
17502
+ for (const needle of needles) {
17503
+ const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\(`);
17504
+ for (let guard = 0; guard < 8; guard++) {
17505
+ const m = re.exec(out2);
17506
+ if (!m) break;
17507
+ const open = m.index + m[0].length - 1;
17508
+ const arg = readBalancedArg(out2, open);
17509
+ if (arg === null) break;
17510
+ out2 = out2.slice(0, m.index) + " " + out2.slice(open + arg.length + 2);
17511
+ }
17512
+ }
17513
+ }
17514
+ return out2;
17515
+ }
17516
+ function blankStringLiterals(expr) {
17517
+ return expr.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
17518
+ }
17519
+ function identifierOutsideSanitizerCalls(variable, calls, sanitizers, sinkType) {
17520
+ if (!/^[A-Za-z_$][\w$]*$/.test(variable)) return false;
17521
+ const refs = sanitizerCallRefs(sanitizers, sinkType);
17522
+ if (refs.length === 0) return false;
17523
+ const re = new RegExp(
17524
+ `(?:^|[^A-Za-z0-9_$])${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[^A-Za-z0-9_$]|$)`
17525
+ );
17526
+ for (const call of calls) {
17527
+ if (refs.some((r) => r.methodName === call.method_name)) continue;
17528
+ for (const arg of call.arguments) {
17529
+ const expr = arg.expression ?? "";
17530
+ if (!expr) continue;
17531
+ const masked = maskSanitizerCalls(expr, refs);
17532
+ if (masked === expr) continue;
17533
+ if (re.test(blankStringLiterals(masked))) return true;
17534
+ }
17535
+ }
17536
+ return false;
17537
+ }
17394
17538
  function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
17395
17539
  const isKnownSinkType = KNOWN_SINK_TYPES.has(sinkType);
17396
17540
  const sanitizersAtTarget = sanitizersByLine.get(toLine);
@@ -2615,6 +2615,25 @@ function extractGoCallInfo(node) {
2615
2615
  const resolved = resolveGoLocalReceiverType(opText, node);
2616
2616
  receiver = resolved !== null ? resolved : opText;
2617
2617
  }
2618
+ else if (operand.type === 'selector_expression' && methodName === 'Get') {
2619
+ // cognium-dev #492 — `r.PostForm.Get` / `r.Form.Get` on a declared
2620
+ // `*http.Request`. The operand is `url.Values`, so a type-resolved
2621
+ // receiver would be `Values` and would also match a plain
2622
+ // `url.Values.Get`. Rewrite only this shape to a synthetic class
2623
+ // the Go plugin registers. Index form `r.Form[k][0]` is not a call;
2624
+ // LanguageSourcesPass covers it.
2625
+ const fieldNode = operand.childForFieldName('field');
2626
+ const inner = operand.childForFieldName('operand');
2627
+ const fieldName = fieldNode ? getNodeText(fieldNode) : '';
2628
+ const innerName = inner && inner.type === 'identifier' ? getNodeText(inner) : null;
2629
+ const innerType = innerName !== null ? resolveGoLocalReceiverType(innerName, node) : null;
2630
+ if (innerType === 'Request' && (fieldName === 'PostForm' || fieldName === 'Form')) {
2631
+ receiver = fieldName === 'PostForm' ? 'RequestPostForm' : 'RequestForm';
2632
+ }
2633
+ else {
2634
+ receiver = opText;
2635
+ }
2636
+ }
2618
2637
  else {
2619
2638
  receiver = opText;
2620
2639
  }