cognium-dev 3.59.0 → 3.64.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 (2) hide show
  1. package/dist/cli.js +542 -64
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -3612,7 +3612,7 @@ function detectLanguage(tree) {
3612
3612
  }
3613
3613
  function extractTypes(tree, cache, language) {
3614
3614
  const effectiveLanguage = language ?? detectLanguage(tree);
3615
- const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
3615
+ const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
3616
3616
  const isPython = effectiveLanguage === "python";
3617
3617
  const isRust = effectiveLanguage === "rust";
3618
3618
  if (effectiveLanguage === "go") {
@@ -5041,7 +5041,7 @@ function detectLanguageFromTree(tree, cache) {
5041
5041
  function extractCalls(tree, cache, language) {
5042
5042
  const calls = [];
5043
5043
  const detectedLanguage = language ?? detectLanguageFromTree(tree, cache);
5044
- const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
5044
+ const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript" || detectedLanguage === "tsx";
5045
5045
  const isPython = detectedLanguage === "python";
5046
5046
  const isRust = detectedLanguage === "rust";
5047
5047
  if (detectedLanguage === "go") {
@@ -5090,8 +5090,149 @@ function extractJavaScriptCalls(tree, cache) {
5090
5090
  calls.push(callInfo);
5091
5091
  }
5092
5092
  }
5093
+ const jsxAttributes = getNodesFromCache(tree.rootNode, "jsx_attribute", cache);
5094
+ for (const attr of jsxAttributes) {
5095
+ const callInfo = extractJSXAttributeSink(attr);
5096
+ if (callInfo) {
5097
+ calls.push(callInfo);
5098
+ }
5099
+ }
5100
+ const assignments = getNodesFromCache(tree.rootNode, "assignment_expression", cache);
5101
+ for (const assign of assignments) {
5102
+ const callInfo = extractDomPropertyAssignmentSink(assign);
5103
+ if (callInfo) {
5104
+ calls.push(callInfo);
5105
+ }
5106
+ }
5093
5107
  return calls;
5094
5108
  }
5109
+ var DOM_XSS_ASSIGNMENT_PROPERTIES = new Set([
5110
+ "innerHTML",
5111
+ "outerHTML"
5112
+ ]);
5113
+ function extractDomPropertyAssignmentSink(node) {
5114
+ const leftNode = node.childForFieldName("left");
5115
+ const rightNode = node.childForFieldName("right");
5116
+ if (!leftNode || !rightNode)
5117
+ return null;
5118
+ if (leftNode.type !== "member_expression")
5119
+ return null;
5120
+ const propertyNode = leftNode.childForFieldName("property");
5121
+ const objectNode = leftNode.childForFieldName("object");
5122
+ if (!propertyNode)
5123
+ return null;
5124
+ const propertyName = getNodeText(propertyNode);
5125
+ if (!DOM_XSS_ASSIGNMENT_PROPERTIES.has(propertyName))
5126
+ return null;
5127
+ const receiver = objectNode ? getNodeText(objectNode) : null;
5128
+ const expression = getNodeText(rightNode);
5129
+ const { variable, literal } = analyzeJSArgument(rightNode);
5130
+ const enclosingFunc = findJSEnclosingFunction(node);
5131
+ return {
5132
+ method_name: propertyName,
5133
+ receiver,
5134
+ arguments: [
5135
+ {
5136
+ position: 0,
5137
+ expression,
5138
+ variable,
5139
+ literal
5140
+ }
5141
+ ],
5142
+ location: {
5143
+ line: node.startPosition.row + 1,
5144
+ column: node.startPosition.column
5145
+ },
5146
+ in_method: enclosingFunc,
5147
+ resolved: true,
5148
+ resolution: {
5149
+ status: "resolved",
5150
+ target: `DOM.${propertyName}`
5151
+ }
5152
+ };
5153
+ }
5154
+ function extractJSXAttributeSink(attr) {
5155
+ let nameNode = null;
5156
+ for (let i2 = 0;i2 < attr.childCount; i2++) {
5157
+ const child = attr.child(i2);
5158
+ if (child && child.type === "property_identifier") {
5159
+ nameNode = child;
5160
+ break;
5161
+ }
5162
+ }
5163
+ if (!nameNode)
5164
+ return null;
5165
+ const attrName = getNodeText(nameNode);
5166
+ if (attrName !== "dangerouslySetInnerHTML")
5167
+ return null;
5168
+ let valueExpr = null;
5169
+ for (let i2 = 0;i2 < attr.childCount; i2++) {
5170
+ const child = attr.child(i2);
5171
+ if (child && child.type === "jsx_expression") {
5172
+ valueExpr = child;
5173
+ break;
5174
+ }
5175
+ }
5176
+ if (!valueExpr)
5177
+ return null;
5178
+ let htmlValue = null;
5179
+ for (let i2 = 0;i2 < valueExpr.childCount; i2++) {
5180
+ const inner = valueExpr.child(i2);
5181
+ if (!inner || inner.type !== "object")
5182
+ continue;
5183
+ for (let j = 0;j < inner.childCount; j++) {
5184
+ const pair = inner.child(j);
5185
+ if (!pair || pair.type !== "pair")
5186
+ continue;
5187
+ const keyNode = pair.childForFieldName("key");
5188
+ if (!keyNode)
5189
+ continue;
5190
+ const keyText = getNodeText(keyNode).replace(/^["']|["']$/g, "");
5191
+ if (keyText === "__html") {
5192
+ htmlValue = pair.childForFieldName("value");
5193
+ break;
5194
+ }
5195
+ }
5196
+ if (htmlValue)
5197
+ break;
5198
+ }
5199
+ if (!htmlValue) {
5200
+ for (let i2 = 0;i2 < valueExpr.childCount; i2++) {
5201
+ const inner = valueExpr.child(i2);
5202
+ if (inner && inner.type !== "{" && inner.type !== "}") {
5203
+ htmlValue = inner;
5204
+ break;
5205
+ }
5206
+ }
5207
+ }
5208
+ if (!htmlValue)
5209
+ return null;
5210
+ const expression = getNodeText(htmlValue);
5211
+ const { variable, literal } = analyzeJSArgument(htmlValue);
5212
+ const enclosingFunc = findJSEnclosingFunction(attr);
5213
+ return {
5214
+ method_name: "dangerouslySetInnerHTML",
5215
+ receiver: null,
5216
+ arguments: [
5217
+ {
5218
+ position: 0,
5219
+ expression,
5220
+ variable,
5221
+ literal
5222
+ }
5223
+ ],
5224
+ location: {
5225
+ line: attr.startPosition.row + 1,
5226
+ column: attr.startPosition.column
5227
+ },
5228
+ in_method: enclosingFunc,
5229
+ resolved: true,
5230
+ resolution: {
5231
+ status: "resolved",
5232
+ target: "react.dangerouslySetInnerHTML"
5233
+ }
5234
+ };
5235
+ }
5095
5236
  function buildJSResolutionContext(tree, cache) {
5096
5237
  const context = {
5097
5238
  functionNames: new Set,
@@ -6632,7 +6773,7 @@ function detectLanguage2(tree) {
6632
6773
  }
6633
6774
  function extractImports(tree, language) {
6634
6775
  const effectiveLanguage = language ?? detectLanguage2(tree);
6635
- const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
6776
+ const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
6636
6777
  const isPython = effectiveLanguage === "python";
6637
6778
  const isRust = effectiveLanguage === "rust";
6638
6779
  if (effectiveLanguage === "go") {
@@ -7347,7 +7488,7 @@ function detectLanguage3(tree) {
7347
7488
  }
7348
7489
  function buildCFG(tree, language) {
7349
7490
  const effectiveLanguage = language ?? detectLanguage3(tree);
7350
- const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
7491
+ const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
7351
7492
  const allBlocks = [];
7352
7493
  const allEdges = [];
7353
7494
  let blockIdCounter = 0;
@@ -7928,7 +8069,7 @@ function detectLanguage4(tree) {
7928
8069
  }
7929
8070
  function buildDFG(tree, cache, language) {
7930
8071
  const effectiveLanguage = language ?? detectLanguage4(tree);
7931
- const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript";
8072
+ const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
7932
8073
  if (isJavaScript) {
7933
8074
  return buildJavaScriptDFG(tree, cache);
7934
8075
  }
@@ -8749,7 +8890,18 @@ function buildBashDFG(tree) {
8749
8890
  if (varNameNode) {
8750
8891
  const varName = getNodeText(varNameNode);
8751
8892
  if (varName && !varName.startsWith("?") && !varName.startsWith("#")) {
8752
- const reachingDef = findReachingDef(varName, scopeStack);
8893
+ let reachingDef = findReachingDef(varName, scopeStack);
8894
+ if (reachingDef === null && !positionalParams.includes(varName)) {
8895
+ const def = {
8896
+ id: defIdCounter++,
8897
+ variable: varName,
8898
+ line: 0,
8899
+ kind: "param"
8900
+ };
8901
+ defs.push(def);
8902
+ scopeStack[0].set(varName, def.id);
8903
+ reachingDef = def.id;
8904
+ }
8753
8905
  uses.push({
8754
8906
  id: useIdCounter++,
8755
8907
  variable: varName,
@@ -8762,7 +8914,18 @@ function buildBashDFG(tree) {
8762
8914
  const varNameNode = node.namedChildCount > 0 ? node.namedChild(0) : null;
8763
8915
  if (varNameNode && varNameNode.type === "variable_name") {
8764
8916
  const varName = getNodeText(varNameNode);
8765
- const reachingDef = findReachingDef(varName, scopeStack);
8917
+ let reachingDef = findReachingDef(varName, scopeStack);
8918
+ if (reachingDef === null && !positionalParams.includes(varName)) {
8919
+ const def = {
8920
+ id: defIdCounter++,
8921
+ variable: varName,
8922
+ line: 0,
8923
+ kind: "param"
8924
+ };
8925
+ defs.push(def);
8926
+ scopeStack[0].set(varName, def.id);
8927
+ reachingDef = def.id;
8928
+ }
8766
8929
  uses.push({
8767
8930
  id: useIdCounter++,
8768
8931
  variable: varName,
@@ -9234,7 +9397,7 @@ var FRAMEWORK_MODULE_PATTERNS = [
9234
9397
  /^@nestjs\/core$/
9235
9398
  ];
9236
9399
  function extractRuntimeRegistrations(tree, cache, language, imports) {
9237
- if (language === "javascript" || language === "typescript") {
9400
+ if (language === "javascript" || language === "typescript" || language === "tsx") {
9238
9401
  return extractJSRuntimeRegistrations(tree, cache, imports);
9239
9402
  }
9240
9403
  if (language === "python") {
@@ -10171,8 +10334,11 @@ var DEFAULT_SOURCES = [
10171
10334
  { method: "get", class: "cookies", type: "http_cookie", severity: "high", return_tainted: true },
10172
10335
  { property: "json", object: "request", type: "http_body", severity: "high", property_tainted: true },
10173
10336
  { property: "data", object: "request", type: "http_body", severity: "high", property_tainted: true },
10337
+ { property: "stream", object: "request", type: "http_body", severity: "high", property_tainted: true },
10174
10338
  { property: "path", object: "request", type: "http_path", severity: "medium", property_tainted: true },
10175
10339
  { property: "query_string", object: "request", type: "http_query", severity: "high", property_tainted: true },
10340
+ { method: "get_data", class: "request", type: "http_body", severity: "high", return_tainted: true },
10341
+ { method: "get_json", class: "request", type: "http_body", severity: "high", return_tainted: true },
10176
10342
  { method: "get", class: "GET", type: "http_param", severity: "high", return_tainted: true },
10177
10343
  { method: "get", class: "POST", type: "http_param", severity: "high", return_tainted: true },
10178
10344
  { method: "get", class: "META", type: "http_header", severity: "high", return_tainted: true },
@@ -10335,13 +10501,13 @@ var DEFAULT_SINKS = [
10335
10501
  { method: "setExecutable", class: "ExecTask", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10336
10502
  { method: "setCommand", class: "ExecTask", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10337
10503
  { method: "execute", class: "Java", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10338
- { method: "bash", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10339
- { method: "shell", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10340
- { method: "sh", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10341
- { method: "spawn", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10342
- { method: "fork", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10343
- { method: "popen", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10344
- { method: "system", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10504
+ { method: "bash", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10505
+ { method: "shell", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10506
+ { method: "sh", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10507
+ { method: "spawn", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10508
+ { method: "fork", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10509
+ { method: "popen", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10510
+ { method: "system", languages: ["java", "javascript", "typescript", "python", "go", "rust"], type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10345
10511
  { method: "setCommandline", class: "DefaultExecutor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10346
10512
  { method: "parse", class: "CommandLine", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10347
10513
  { method: "addArgument", class: "CommandLine", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -10404,6 +10570,9 @@ var DEFAULT_SINKS = [
10404
10570
  { method: "unzip", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
10405
10571
  { method: "extract", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
10406
10572
  { method: "extractAll", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
10573
+ { method: "extractall", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0], languages: ["python"] },
10574
+ { method: "ZipFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
10575
+ { method: "send_from_directory", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [1], languages: ["python"] },
10407
10576
  { method: "unjar", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
10408
10577
  { method: "PrintWriter", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10409
10578
  { method: "Scanner", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -11171,10 +11340,22 @@ var DEFAULT_SINKS = [
11171
11340
  { method: "redirect", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
11172
11341
  { method: "Set", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
11173
11342
  { method: "Add", class: "Header", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["go"] },
11174
- { method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11175
- { method: "merge", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11176
- { method: "extend", class: "_", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11177
- { method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-915", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] }
11343
+ { method: "assign", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11344
+ { method: "defineProperty", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2], languages: ["javascript", "typescript"] },
11345
+ { method: "defineProperties", class: "Object", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1], languages: ["javascript", "typescript"] },
11346
+ { method: "merge", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11347
+ { method: "merge", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11348
+ { method: "extend", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11349
+ { method: "extend", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11350
+ { method: "defaultsDeep", class: "_", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11351
+ { method: "defaultsDeep", class: "lodash", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11352
+ { method: "extend", class: "$", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11353
+ { method: "extend", class: "jQuery", type: "mass_assignment", cwe: "CWE-1321", severity: "high", arg_positions: [1, 2, 3], languages: ["javascript", "typescript"] },
11354
+ { method: "innerHTML", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11355
+ { method: "outerHTML", type: "xss", cwe: "CWE-79", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11356
+ { method: "unserialize", class: "serialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11357
+ { method: "unserialize", class: "node-serialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] },
11358
+ { method: "unserialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
11178
11359
  ];
11179
11360
  var DEFAULT_SANITIZERS = [
11180
11361
  { method: "setString", class: "PreparedStatement", removes: ["sql_injection"] },
@@ -11452,7 +11633,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11452
11633
  location: formatCallLocation(call),
11453
11634
  severity: pattern.severity,
11454
11635
  line: call.location.line,
11455
- confidence: 1
11636
+ confidence: 1,
11637
+ in_method: call.in_method ?? undefined
11456
11638
  });
11457
11639
  }
11458
11640
  }
@@ -11469,7 +11651,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11469
11651
  location: `@${pattern.annotation} ${param.name} in ${method.name}`,
11470
11652
  severity: pattern.severity,
11471
11653
  line: paramLine,
11472
- confidence: 1
11654
+ confidence: 1,
11655
+ in_method: method.name
11473
11656
  });
11474
11657
  }
11475
11658
  }
@@ -11491,7 +11674,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11491
11674
  location: `@${pattern.method_annotation} ${param.name} in ${method.name}`,
11492
11675
  severity: pattern.severity,
11493
11676
  line: paramLine,
11494
- confidence: 1
11677
+ confidence: 1,
11678
+ in_method: method.name
11495
11679
  });
11496
11680
  }
11497
11681
  }
@@ -11520,7 +11704,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11520
11704
  severity: "high",
11521
11705
  line: paramLine,
11522
11706
  confidence: 1,
11523
- variable: param.name
11707
+ variable: param.name,
11708
+ in_method: method.name
11524
11709
  });
11525
11710
  }
11526
11711
  }
@@ -11541,7 +11726,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11541
11726
  location: `${param.type || "any"} ${param.name} in ${method.name}`,
11542
11727
  severity: "medium",
11543
11728
  line: paramLine,
11544
- confidence: param.type ? 0.7 : 0.5
11729
+ confidence: param.type ? 0.7 : 0.5,
11730
+ in_method: method.name
11545
11731
  });
11546
11732
  }
11547
11733
  }
@@ -11561,7 +11747,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11561
11747
  location: `${arg.expression} in ${call.in_method || "anonymous"}`,
11562
11748
  severity: "high",
11563
11749
  line: call.location.line,
11564
- confidence: 1
11750
+ confidence: 1,
11751
+ in_method: call.in_method ?? undefined
11565
11752
  });
11566
11753
  }
11567
11754
  }
@@ -11581,7 +11768,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
11581
11768
  location: `${arg.expression} in ${call.in_method || "anonymous"}`,
11582
11769
  severity: "high",
11583
11770
  line: call.location.line,
11584
- confidence: 1
11771
+ confidence: 1,
11772
+ in_method: call.in_method ?? undefined
11585
11773
  });
11586
11774
  }
11587
11775
  break;
@@ -11614,6 +11802,21 @@ function findSources(calls, types, patterns, sourceLines, language) {
11614
11802
  s.variable = m[1];
11615
11803
  }
11616
11804
  }
11805
+ if (language === "java" && sourceLines) {
11806
+ const JAVA_ASSIGN_LHS = /^\s*(?:(?:final|public|private|protected|static|synchronized|volatile|transient)\s+)*(?:[A-Za-z_][\w.]*(?:\s*<[^=]*>)?(?:\s*\[\s*\])*\s+)?([A-Za-z_]\w*)\s*=(?!=)/;
11807
+ for (const s of result) {
11808
+ if (s.variable && s.variable.length > 0)
11809
+ continue;
11810
+ const lineText = sourceLines[s.line - 1] ?? "";
11811
+ const m = JAVA_ASSIGN_LHS.exec(lineText);
11812
+ if (!m)
11813
+ continue;
11814
+ const rhs = lineText.slice(m[0].length).trimStart();
11815
+ if (/^new\b/.test(rhs))
11816
+ continue;
11817
+ s.variable = m[1];
11818
+ }
11819
+ }
11617
11820
  return result;
11618
11821
  }
11619
11822
  function isInterproceduralTaintableType(typeName) {
@@ -11787,7 +11990,11 @@ function matchesSourcePattern(call, pattern) {
11787
11990
  }
11788
11991
  if (pattern.class && pattern.class !== "constructor") {
11789
11992
  if (call.receiver_type && call.receiver_type === pattern.class) {} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {} else if (!call.receiver) {
11790
- return false;
11993
+ const target = call.resolution?.target;
11994
+ const expectedTail = `${pattern.class}.${pattern.method}`;
11995
+ if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {} else {
11996
+ return false;
11997
+ }
11791
11998
  } else if (!receiverMightBeClass(call.receiver, pattern.class)) {
11792
11999
  return false;
11793
12000
  }
@@ -12044,7 +12251,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12044
12251
  }
12045
12252
  return false;
12046
12253
  } else if (!call.receiver && !call.receiver_type) {
12047
- return false;
12254
+ const target = call.resolution?.target;
12255
+ const expectedTail = `${pattern.class}.${pattern.method}`;
12256
+ if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {} else {
12257
+ return false;
12258
+ }
12048
12259
  }
12049
12260
  }
12050
12261
  if (!pattern.class && call.receiver) {
@@ -15051,6 +15262,9 @@ class DefaultLanguageRegistry {
15051
15262
  }
15052
15263
  }
15053
15264
  get(language) {
15265
+ if (language === "tsx") {
15266
+ return this.plugins.get("javascript");
15267
+ }
15054
15268
  return this.plugins.get(language);
15055
15269
  }
15056
15270
  getForFile(filePath) {
@@ -17244,6 +17458,20 @@ class BashPlugin extends BaseLanguagePlugin {
17244
17458
  cwe: "CWE-918",
17245
17459
  severity: "high",
17246
17460
  argPositions: [0]
17461
+ },
17462
+ {
17463
+ method: "source",
17464
+ type: "path_traversal",
17465
+ cwe: "CWE-98",
17466
+ severity: "critical",
17467
+ argPositions: [0]
17468
+ },
17469
+ {
17470
+ method: ".",
17471
+ type: "path_traversal",
17472
+ cwe: "CWE-98",
17473
+ severity: "critical",
17474
+ argPositions: [0]
17247
17475
  }
17248
17476
  ];
17249
17477
  }
@@ -20337,6 +20565,7 @@ function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
20337
20565
  const allSources = [];
20338
20566
  const allSinks = [];
20339
20567
  const allSanitizers = [];
20568
+ const allFlows = [];
20340
20569
  const allImports = [];
20341
20570
  const allExports = [];
20342
20571
  const allFindings = [];
@@ -20422,6 +20651,14 @@ function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
20422
20651
  line: sanitizer.line + lineShift
20423
20652
  });
20424
20653
  }
20654
+ for (const flow of ir.taint.flows ?? []) {
20655
+ allFlows.push({
20656
+ ...flow,
20657
+ source_line: flow.source_line + lineShift,
20658
+ sink_line: flow.sink_line + lineShift,
20659
+ path: flow.path.map((step) => ({ ...step, line: step.line + lineShift }))
20660
+ });
20661
+ }
20425
20662
  for (const imp of ir.imports) {
20426
20663
  allImports.push({
20427
20664
  ...imp,
@@ -20441,7 +20678,8 @@ function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
20441
20678
  const taint = {
20442
20679
  sources: allSources,
20443
20680
  sinks: allSinks,
20444
- sanitizers: allSanitizers.length > 0 ? allSanitizers : undefined
20681
+ sanitizers: allSanitizers.length > 0 ? allSanitizers : undefined,
20682
+ flows: allFlows.length > 0 ? allFlows : undefined
20445
20683
  };
20446
20684
  const cfg = {
20447
20685
  blocks: allCfgBlocks,
@@ -20637,6 +20875,7 @@ class LanguageSourcesPass {
20637
20875
  const constProp = ctx.getResult("constant-propagation");
20638
20876
  const additionalSources = [];
20639
20877
  const additionalSinks = [];
20878
+ const additionalSanitizers = [];
20640
20879
  additionalSources.push(...findGetterSources(types, constProp.instanceFieldTaint, code));
20641
20880
  additionalSources.push(...findOopFieldReadSources(types, code, language));
20642
20881
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
@@ -20690,9 +20929,10 @@ class LanguageSourcesPass {
20690
20929
  for (const finding of bashFindings) {
20691
20930
  ctx.addFinding(finding);
20692
20931
  }
20932
+ additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
20693
20933
  }
20694
20934
  attachSourceLineCode(additionalSources, additionalSinks, code);
20695
- return { additionalSources, additionalSinks, pyTaintedVars, pySanitizedVars, jsTaintedVars };
20935
+ return { additionalSources, additionalSinks, additionalSanitizers, pyTaintedVars, pySanitizedVars, jsTaintedVars };
20696
20936
  }
20697
20937
  }
20698
20938
  function findGetterSources(types, instanceFieldTaint, _sourceCode) {
@@ -20943,62 +21183,62 @@ function buildPythonTaintedVars(sourceCode) {
20943
21183
  const line = lines[i2];
20944
21184
  if (line.trimStart().startsWith("#"))
20945
21185
  continue;
20946
- const subscriptAssign = line.match(/^\s*(\w+)\[(['"])([^'"]+)\2\]\s*=\s*(.+)$/);
21186
+ const subscriptAssign = line.match(/^\s*([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]\s*=\s*(.+)$/u);
20947
21187
  if (subscriptAssign) {
20948
21188
  const [, container, , key, rhs2] = subscriptAssign;
20949
- const isTaintedRhs = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(rhs2));
21189
+ const isTaintedRhs = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(rhs2));
20950
21190
  if (isTaintedRhs)
20951
21191
  containerTainted.set(`${container}['${key}']`, i2 + 1);
20952
21192
  continue;
20953
21193
  }
20954
- const setCallMatch = line.match(/^\s*(\w+)\.set\s*\(\s*(['"])([^'"]+)\2\s*,\s*(['"])([^'"]+)\4\s*,\s*(.+?)\s*\)$/);
21194
+ const setCallMatch = line.match(/^\s*([\p{L}\p{N}_]+)\.set\s*\(\s*(['"])([^'"]+)\2\s*,\s*(['"])([^'"]+)\4\s*,\s*(.+?)\s*\)$/u);
20955
21195
  if (setCallMatch) {
20956
21196
  const [, obj, , section, , key, rhs2] = setCallMatch;
20957
- const isTaintedRhs = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(rhs2));
21197
+ const isTaintedRhs = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(rhs2));
20958
21198
  if (isTaintedRhs)
20959
21199
  containerTainted.set(`${obj}['${section}']['${key}']`, i2 + 1);
20960
21200
  continue;
20961
21201
  }
20962
- const containerAppendMatch = line.match(/^\s*(\w+)\.(append|extend|insert|add|push|put|appendleft)\s*\(\s*(.+?)\s*\)\s*$/);
21202
+ const containerAppendMatch = line.match(/^\s*([\p{L}\p{N}_]+)\.(append|extend|insert|add|push|put|appendleft)\s*\(\s*(.+?)\s*\)\s*$/u);
20963
21203
  if (containerAppendMatch) {
20964
21204
  const [, receiver, , argExpr] = containerAppendMatch;
20965
- const argIsTainted = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(argExpr));
21205
+ const argIsTainted = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(argExpr));
20966
21206
  const argIsDirectSource = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(argExpr));
20967
21207
  if (argIsTainted || argIsDirectSource)
20968
21208
  tainted.set(receiver, tainted.get(receiver) ?? i2 + 1);
20969
21209
  continue;
20970
21210
  }
20971
- const augAssign = line.match(/^\s*(\w+)\s*\+=\s*(.+)$/);
21211
+ const augAssign = line.match(/^\s*([\p{L}\p{N}_]+)\s*\+=\s*(.+)$/u);
20972
21212
  if (augAssign) {
20973
21213
  const [, augLhs, augRhs] = augAssign;
20974
- const rhsTainted = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(augRhs));
21214
+ const rhsTainted = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(augRhs));
20975
21215
  if (rhsTainted || tainted.has(augLhs))
20976
21216
  tainted.set(augLhs, tainted.get(augLhs) ?? i2 + 1);
20977
21217
  continue;
20978
21218
  }
20979
- const forLoopMatch = line.match(/^\s*for\s+(\w+)\s+in\s+(.+?)(?:\s*:\s*)?$/);
21219
+ const forLoopMatch = line.match(/^\s*for\s+([\p{L}\p{N}_]+)\s+in\s+(.+?)(?:\s*:\s*)?$/u);
20980
21220
  if (forLoopMatch) {
20981
21221
  const [, iterVar, iterExpr] = forLoopMatch;
20982
21222
  const isDirectSource2 = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(iterExpr));
20983
- const isPropagated = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(iterExpr));
21223
+ const isPropagated = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(iterExpr));
20984
21224
  if (isDirectSource2 || isPropagated)
20985
21225
  tainted.set(iterVar, i2 + 1);
20986
21226
  continue;
20987
21227
  }
20988
- const assignMatch = line.match(/^\s*(\w+)\s*=\s*(.+)$/);
21228
+ const assignMatch = line.match(/^\s*([\p{L}\p{N}_]+)\s*=\s*(.+)$/u);
20989
21229
  if (!assignMatch)
20990
21230
  continue;
20991
21231
  const [, lhs, rhs] = assignMatch;
20992
21232
  const isDirectSource = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(rhs));
20993
21233
  let propagatedFrom;
20994
- const dictAccessMatch = rhs.trim().match(/^(\w+)\[(['"])([^'"]+)\2\]$/);
21234
+ const dictAccessMatch = rhs.trim().match(/^([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]$/u);
20995
21235
  if (dictAccessMatch) {
20996
21236
  const [, container, , key] = dictAccessMatch;
20997
21237
  if (containerTainted.has(`${container}['${key}']`))
20998
21238
  propagatedFrom = `${container}['${key}']`;
20999
21239
  }
21000
21240
  if (!propagatedFrom) {
21001
- const confGetMatch = rhs.trim().match(/^(\w+)\.get\s*\(\s*(['"])([^'"]+)\2\s*,\s*(['"])([^'"]+)\4\s*\)$/);
21241
+ const confGetMatch = rhs.trim().match(/^([\p{L}\p{N}_]+)\.get\s*\(\s*(['"])([^'"]+)\2\s*,\s*(['"])([^'"]+)\4\s*\)$/u);
21002
21242
  if (confGetMatch) {
21003
21243
  const [, obj, , section, , key] = confGetMatch;
21004
21244
  if (containerTainted.has(`${obj}['${section}']['${key}']`))
@@ -21008,7 +21248,7 @@ function buildPythonTaintedVars(sourceCode) {
21008
21248
  if (!propagatedFrom) {
21009
21249
  const isSafeEnvRead = /\bos\.environ\.get\s*\(/.test(rhs) || /\bos\.getenv\s*\(/.test(rhs);
21010
21250
  if (!isSafeEnvRead)
21011
- propagatedFrom = [...tainted.keys()].find((v) => new RegExp(`\\b${v}\\b`).test(rhs));
21251
+ propagatedFrom = [...tainted.keys()].find((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(rhs));
21012
21252
  }
21013
21253
  if (isDirectSource) {
21014
21254
  tainted.set(lhs, i2 + 1);
@@ -21413,6 +21653,61 @@ function findBashPatternFindings(sourceCode, file) {
21413
21653
  }
21414
21654
  return findings;
21415
21655
  }
21656
+ function findBashRegexAllowlistSanitizers(code) {
21657
+ const sanitizers = [];
21658
+ const lines = code.split(`
21659
+ `);
21660
+ const guardRe = /^\s*if\s+\[\[\s*!\s*"?\$\{?(\w+)\}?"?\s*=~\s*(\S+)\s*\]\]\s*;\s*then\s+(exit|return|die)\b/;
21661
+ for (let i2 = 0;i2 < lines.length; i2++) {
21662
+ const m = guardRe.exec(lines[i2]);
21663
+ if (!m)
21664
+ continue;
21665
+ const regexLiteral = m[2];
21666
+ if (!isSafeBashAllowlistRegex(regexLiteral))
21667
+ continue;
21668
+ const ifLine1Indexed = i2 + 1;
21669
+ for (let l = ifLine1Indexed + 1;l <= lines.length; l++) {
21670
+ sanitizers.push({
21671
+ type: "regex_allowlist",
21672
+ method: "=~",
21673
+ line: l,
21674
+ sanitizes: [
21675
+ "command_injection",
21676
+ "path_traversal",
21677
+ "sql_injection",
21678
+ "code_injection",
21679
+ "ssrf",
21680
+ "xss",
21681
+ "open_redirect",
21682
+ "log_injection"
21683
+ ]
21684
+ });
21685
+ }
21686
+ }
21687
+ return sanitizers;
21688
+ }
21689
+ function isSafeBashAllowlistRegex(literal) {
21690
+ if (!literal.startsWith("^") || !literal.endsWith("$"))
21691
+ return false;
21692
+ const body2 = literal.slice(1, -1);
21693
+ if (body2.length === 0)
21694
+ return false;
21695
+ if (body2.includes(".*") || body2.includes(".+"))
21696
+ return false;
21697
+ if (body2.includes("|"))
21698
+ return false;
21699
+ if (/\\\d/.test(body2))
21700
+ return false;
21701
+ const safeToken = /\[[^\]]+\][+*?]?|\\.|[A-Za-z0-9_\-./]|[+*?]/g;
21702
+ let consumed = 0;
21703
+ let match;
21704
+ while ((match = safeToken.exec(body2)) !== null) {
21705
+ if (match.index !== consumed)
21706
+ return false;
21707
+ consumed += match[0].length;
21708
+ }
21709
+ return consumed === body2.length;
21710
+ }
21416
21711
 
21417
21712
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
21418
21713
  var JS_XSS_SANITIZERS = [
@@ -21449,7 +21744,10 @@ class SinkFilterPass {
21449
21744
  sinks.push(s);
21450
21745
  }
21451
21746
  }
21452
- const sanitizers = taintMatcher.sanitizers;
21747
+ const sanitizers = [
21748
+ ...taintMatcher.sanitizers,
21749
+ ...langSources.additionalSanitizers ?? []
21750
+ ];
21453
21751
  let filtered = sinks.filter((sink) => !constProp.unreachableLines.has(sink.line));
21454
21752
  filtered = filterCleanArraySinks(filtered, calls, constProp.taintedArrayElements, constProp.symbols);
21455
21753
  filtered = filterCleanVariableSinks(filtered, calls, constProp.tainted, constProp.symbols, dfg, constProp.sanitizedVars, constProp.synchronizedLines, language);
@@ -21924,6 +22222,8 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
21924
22222
  for (const def of defsNextLine) {
21925
22223
  const callsOnSourceLine = callsByLine.get(source.line) ?? [];
21926
22224
  if (callsOnSourceLine.length > 0) {
22225
+ if (source.variable && def.variable !== source.variable)
22226
+ continue;
21927
22227
  tainted.push({
21928
22228
  variable: def.variable,
21929
22229
  defId: def.id,
@@ -21934,6 +22234,21 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
21934
22234
  });
21935
22235
  }
21936
22236
  }
22237
+ if (source.variable) {
22238
+ const paramDefs = defsByLine.get(0) ?? [];
22239
+ for (const def of paramDefs) {
22240
+ if (def.kind === "param" && def.variable === source.variable) {
22241
+ tainted.push({
22242
+ variable: def.variable,
22243
+ defId: def.id,
22244
+ line: def.line,
22245
+ sourceType: source.type,
22246
+ sourceLine: source.line,
22247
+ confidence: source.confidence
22248
+ });
22249
+ }
22250
+ }
22251
+ }
21937
22252
  }
21938
22253
  return tainted;
21939
22254
  }
@@ -22111,13 +22426,13 @@ class TaintPropagationPass {
22111
22426
  confidence: flow.confidence,
22112
22427
  sanitized: flow.sanitized
22113
22428
  }));
22114
- const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines) ?? [];
22429
+ const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines, types) ?? [];
22115
22430
  for (const f of arrayFlows) {
22116
22431
  if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
22117
22432
  flows.push(f);
22118
22433
  }
22119
22434
  }
22120
- const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code) ?? [];
22435
+ const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
22121
22436
  for (const f of collectionFlows) {
22122
22437
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line))
22123
22438
  continue;
@@ -22168,7 +22483,7 @@ class TaintPropagationPass {
22168
22483
  flows.push(f);
22169
22484
  }
22170
22485
  const sanitizedNames = constProp.sanitizedVars;
22171
- const finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
22486
+ let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
22172
22487
  if (f.path.length === 0)
22173
22488
  return true;
22174
22489
  const sourceVar = f.path[0].variable;
@@ -22182,10 +22497,60 @@ class TaintPropagationPass {
22182
22497
  }
22183
22498
  return true;
22184
22499
  });
22500
+ if (ctx.language === "java" && typeof ctx.code === "string") {
22501
+ finalFlows = finalFlows.filter((f) => {
22502
+ if (f.sink_type !== "path_traversal" && f.sink_type !== "xxe")
22503
+ return true;
22504
+ if (!isInJavaSanitizedMethod(ctx.code, types, f.sink_line, f.sink_type))
22505
+ return true;
22506
+ return false;
22507
+ });
22508
+ }
22185
22509
  return { flows: finalFlows };
22186
22510
  }
22187
22511
  }
22188
- function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code) {
22512
+ function isInJavaSanitizedMethod(code, types, sinkLine, sinkType) {
22513
+ if (!types || types.length === 0)
22514
+ return false;
22515
+ let methodStart = -1;
22516
+ let methodEnd = -1;
22517
+ for (const t of types) {
22518
+ for (const m of t.methods) {
22519
+ if (sinkLine >= m.start_line && sinkLine <= m.end_line) {
22520
+ methodStart = m.start_line;
22521
+ methodEnd = m.end_line;
22522
+ break;
22523
+ }
22524
+ }
22525
+ if (methodStart > 0)
22526
+ break;
22527
+ }
22528
+ if (methodStart < 0)
22529
+ return false;
22530
+ const lines = code.split(`
22531
+ `);
22532
+ const body2 = lines.slice(methodStart - 1, methodEnd).join(`
22533
+ `);
22534
+ if (sinkType === "path_traversal") {
22535
+ if (!/\.getCanonicalPath\s*\(/.test(body2))
22536
+ return false;
22537
+ if (!/\.startsWith\s*\([^)]*getCanonicalPath/.test(body2))
22538
+ return false;
22539
+ if (!/\bthrow\s+new\b/.test(body2))
22540
+ return false;
22541
+ return true;
22542
+ }
22543
+ if (sinkType === "xxe") {
22544
+ const setFeatureRe = /\.setFeature\s*\(\s*"(?:[^"]*disallow-doctype-decl|[^"]*external-general-entities|[^"]*external-parameter-entities|[^"]*load-external-dtd)"/;
22545
+ if (setFeatureRe.test(body2))
22546
+ return true;
22547
+ if (/\.setProperty\s*\([^,]*SUPPORT_DTD[^,]*,\s*false\s*\)/.test(body2))
22548
+ return true;
22549
+ return false;
22550
+ }
22551
+ return false;
22552
+ }
22553
+ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code, types) {
22189
22554
  const flows = [];
22190
22555
  const callsByLine = new Map;
22191
22556
  for (const call of calls) {
@@ -22206,8 +22571,11 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
22206
22571
  const varName = arg.variable;
22207
22572
  const scopedName = call.in_method ? `${call.in_method}:${varName}` : varName;
22208
22573
  if (taintedVars.has(varName) || taintedVars.has(scopedName)) {
22209
- const source = sources[0];
22574
+ const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, varName);
22210
22575
  if (source) {
22576
+ if (source.variable && source.variable !== varName && source.in_method && call.in_method && source.in_method !== call.in_method) {
22577
+ continue;
22578
+ }
22211
22579
  if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, source.line, sink.line)) {
22212
22580
  continue;
22213
22581
  }
@@ -22243,8 +22611,11 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
22243
22611
  const collectionVar = match[1];
22244
22612
  const scopedCollection = call.in_method ? `${call.in_method}:${collectionVar}` : collectionVar;
22245
22613
  if (taintedVars.has(collectionVar) || taintedVars.has(scopedCollection)) {
22246
- const source = sources[0];
22614
+ const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, collectionVar);
22247
22615
  if (source) {
22616
+ if (source.variable && source.variable !== collectionVar && source.in_method && call.in_method && source.in_method !== call.in_method) {
22617
+ continue;
22618
+ }
22248
22619
  if (typeof code === "string" && isReassignedToLiteralBetween(code, collectionVar, source.line, sink.line)) {
22249
22620
  continue;
22250
22621
  }
@@ -22270,7 +22641,7 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
22270
22641
  }
22271
22642
  return flows;
22272
22643
  }
22273
- function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, unreachableLines) {
22644
+ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, unreachableLines, types) {
22274
22645
  const flows = [];
22275
22646
  const callsByLine = new Map;
22276
22647
  for (const call of calls) {
@@ -22295,7 +22666,7 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
22295
22666
  if (taintedIndices) {
22296
22667
  const isTainted = taintedIndices.has(indexStr) || taintedIndices.has("*");
22297
22668
  if (isTainted) {
22298
- const source = sources[0];
22669
+ const source = pickScopedSource(sources, sink.line, call.in_method ?? null, types, arrayName);
22299
22670
  if (source) {
22300
22671
  flows.push({
22301
22672
  source_line: source.line,
@@ -22399,11 +22770,12 @@ function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
22399
22770
  const strLit = `(?:"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|\`[^\`\\\\]*(?:\\\\.[^\`\\\\]*)*\`)`;
22400
22771
  const reNaked = new RegExp(`^\\s*${variable}\\s*(?::?=)\\s*${strLit}\\s*;?\\s*$`);
22401
22772
  const reGuarded = new RegExp(`^\\s*if\\b.*\\b${variable}\\s*=\\s*${strLit}\\s*;?\\s*$`);
22773
+ const reSwitchCase = new RegExp(`^\\s*(?:case\\b.*?|default\\s*):\\s*${variable}\\s*=\\s*${strLit}\\s*;?\\s*(?:break\\s*;?)?\\s*$`);
22402
22774
  for (let i2 = lo;i2 < hi; i2++) {
22403
22775
  const line = lines[i2];
22404
22776
  if (!line)
22405
22777
  continue;
22406
- if (reNaked.test(line) || reGuarded.test(line))
22778
+ if (reNaked.test(line) || reGuarded.test(line) || reSwitchCase.test(line))
22407
22779
  return true;
22408
22780
  }
22409
22781
  return false;
@@ -22507,7 +22879,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22507
22879
  if (reCache.has(s.variable))
22508
22880
  continue;
22509
22881
  const escaped = s.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22510
- reCache.set(s.variable, new RegExp(`\\b${escaped}\\b`));
22882
+ reCache.set(s.variable, new RegExp(`(?<![\\p{L}\\p{N}_])${escaped}(?![\\p{L}\\p{N}_])`, "u"));
22511
22883
  }
22512
22884
  const callsByLine = new Map;
22513
22885
  for (const call of calls) {
@@ -22530,6 +22902,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22530
22902
  for (const source of sourcesWithVar) {
22531
22903
  if (source.line >= sink.line)
22532
22904
  continue;
22905
+ if (source.in_method && call.in_method && source.in_method !== call.in_method) {
22906
+ continue;
22907
+ }
22533
22908
  const re = reCache.get(source.variable);
22534
22909
  if (!re || !re.test(expr))
22535
22910
  continue;
@@ -22596,6 +22971,51 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22596
22971
  }
22597
22972
  return flows;
22598
22973
  }
22974
+ function pickScopedSource(sources, sinkLine, methodName, types, taintedVar) {
22975
+ if (sources.length === 0)
22976
+ return;
22977
+ const closestPreceding = (cands) => {
22978
+ let best;
22979
+ for (const s of cands) {
22980
+ if (s.line >= sinkLine)
22981
+ continue;
22982
+ if (!best || s.line > best.line)
22983
+ best = s;
22984
+ }
22985
+ return best;
22986
+ };
22987
+ if (taintedVar) {
22988
+ const byVar = sources.filter((s) => s.variable === taintedVar);
22989
+ const pick = closestPreceding(byVar);
22990
+ if (pick)
22991
+ return pick;
22992
+ }
22993
+ if (methodName && types && types.length > 0) {
22994
+ let methodStart = -1;
22995
+ let methodEnd = -1;
22996
+ for (const t of types) {
22997
+ for (const m of t.methods) {
22998
+ if (m.name === methodName) {
22999
+ methodStart = m.start_line;
23000
+ methodEnd = m.end_line;
23001
+ break;
23002
+ }
23003
+ }
23004
+ if (methodStart > 0)
23005
+ break;
23006
+ }
23007
+ if (methodStart > 0 && methodEnd >= methodStart) {
23008
+ const inScope = sources.filter((s) => s.line >= methodStart && s.line <= methodEnd);
23009
+ const pick = closestPreceding(inScope);
23010
+ if (pick)
23011
+ return pick;
23012
+ }
23013
+ }
23014
+ const globalPick = closestPreceding(sources);
23015
+ if (globalPick)
23016
+ return globalPick;
23017
+ return sources[0];
23018
+ }
22599
23019
 
22600
23020
  // ../circle-ir/dist/analysis/interprocedural.js
22601
23021
  function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourcesOrSanitizers, sinksOrOptions, sanitizersArg, optionsArg = {}) {
@@ -22649,6 +23069,13 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
22649
23069
  for (const def of graph.defsAtLine(source.line)) {
22650
23070
  seedIds.add(def.id);
22651
23071
  }
23072
+ if (source.variable) {
23073
+ for (const def of graph.defsAtLine(0)) {
23074
+ if (def.kind === "param" && def.variable === source.variable) {
23075
+ seedIds.add(def.id);
23076
+ }
23077
+ }
23078
+ }
22652
23079
  }
22653
23080
  const taintedDefIds = graph.propagateTaintedDefIds(seedIds);
22654
23081
  const taintedVarsFromCP = options.taintedVariables ?? new Set;
@@ -22789,7 +23216,34 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
22789
23216
  const targetMethod = getMethodNode(methodNodes, call.method_name);
22790
23217
  if (!targetMethod) {
22791
23218
  if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name)) {
22792
- const sink = {
23219
+ const isBash = graph.ir.meta.language === "bash";
23220
+ const bashSafeBuiltins = new Set([
23221
+ "echo",
23222
+ "printf",
23223
+ "test",
23224
+ "[",
23225
+ "[[",
23226
+ "true",
23227
+ "false",
23228
+ ":",
23229
+ "declare",
23230
+ "local",
23231
+ "export",
23232
+ "readonly",
23233
+ "typeset"
23234
+ ]);
23235
+ if (isBash && bashSafeBuiltins.has(call.method_name)) {
23236
+ continue;
23237
+ }
23238
+ const sink = isBash ? {
23239
+ type: "command_injection",
23240
+ cwe: "CWE-78",
23241
+ location: `Tainted data (${taintedArgVars.join(", ")}) passed unquoted to shell utility ${call.method_name}`,
23242
+ line: call.location.line,
23243
+ confidence: 0.6,
23244
+ method: call.method_name,
23245
+ argPositions: taintedArgPositions
23246
+ } : {
22793
23247
  type: "external_taint_escape",
22794
23248
  cwe: "CWE-668",
22795
23249
  location: `Tainted data (${taintedArgVars.join(", ")}) passed to external method ${call.receiver ? call.receiver + "." : ""}${call.method_name}()`,
@@ -23469,6 +23923,7 @@ function isPublicMethod(method, language) {
23469
23923
  return method.modifiers.includes("public");
23470
23924
  case "javascript":
23471
23925
  case "typescript":
23926
+ case "tsx":
23472
23927
  return !method.modifiers.includes("private") && !method.modifiers.includes("protected");
23473
23928
  case "python":
23474
23929
  return !method.name.startsWith("_");
@@ -23488,7 +23943,7 @@ class MissingPublicDocPass {
23488
23943
  if (UTIL_DIR_RE.test(graph.ir.meta.file)) {
23489
23944
  return { missingDocMethods: [], missingDocTypes: [] };
23490
23945
  }
23491
- if (!["java", "javascript", "typescript", "python"].includes(language)) {
23946
+ if (!["java", "javascript", "typescript", "tsx", "python"].includes(language)) {
23492
23947
  return { missingDocMethods: [], missingDocTypes: [] };
23493
23948
  }
23494
23949
  const lines = code.split(`
@@ -24012,6 +24467,7 @@ function hasDeclKeyword(lineText, language) {
24012
24467
  return /\b(?:int|long|float|double|boolean|byte|char|short|var|final)\b/.test(lineText) || /\b[A-Z]\w*(?:<[^>]*>)?\s+\w/.test(lineText);
24013
24468
  case "javascript":
24014
24469
  case "typescript":
24470
+ case "tsx":
24015
24471
  return /\b(?:let|const|var)\s+[\w{[]/.test(lineText);
24016
24472
  case "rust":
24017
24473
  return /\blet\s+(?:mut\s+)?\w/.test(lineText);
@@ -28518,6 +28974,7 @@ class WeakRandomPass {
28518
28974
  return "Use the `secrets` module (`secrets.token_bytes`, " + "`secrets.token_hex`, `secrets.choice`, `secrets.randbelow`).";
28519
28975
  case "javascript":
28520
28976
  case "typescript":
28977
+ case "tsx":
28521
28978
  return "Use `crypto.randomBytes(n)` (Node.js) or " + "`crypto.getRandomValues(typedArray)` (browser).";
28522
28979
  case "go":
28523
28980
  return "Use `crypto/rand` instead of `math/rand`. Example: " + "`b := make([]byte, 32); _, _ = rand.Read(b)` (where `rand` is `crypto/rand`).";
@@ -28556,7 +29013,7 @@ class WeakRandomPass {
28556
29013
  }
28557
29014
  return null;
28558
29015
  }
28559
- if (language === "javascript" || language === "typescript") {
29016
+ if (language === "javascript" || language === "typescript" || language === "tsx") {
28560
29017
  if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
28561
29018
  return "Math.random";
28562
29019
  }
@@ -30158,6 +30615,7 @@ function getNodeTypesForLanguage(language) {
30158
30615
  ]);
30159
30616
  case "javascript":
30160
30617
  case "typescript":
30618
+ case "tsx":
30161
30619
  return new Set([
30162
30620
  "call_expression",
30163
30621
  "new_expression",
@@ -30170,7 +30628,12 @@ function getNodeTypesForLanguage(language) {
30170
30628
  "import_statement",
30171
30629
  "export_statement",
30172
30630
  "member_expression",
30173
- "assignment_expression"
30631
+ "assignment_expression",
30632
+ "jsx_element",
30633
+ "jsx_self_closing_element",
30634
+ "jsx_opening_element",
30635
+ "jsx_attribute",
30636
+ "jsx_expression"
30174
30637
  ]);
30175
30638
  case "bash":
30176
30639
  return new Set([
@@ -30234,8 +30697,15 @@ async function analyze(code, filePath, language, options = {}) {
30234
30697
  if (language === "html") {
30235
30698
  return analyzeHtmlFile(code, filePath, options);
30236
30699
  }
30237
- logger.debug("Analyzing file", { filePath, language, codeLength: code.length });
30238
- const tree = await parse(code, language);
30700
+ let parseGrammar = language;
30701
+ if (language === "javascript" || language === "typescript") {
30702
+ const lower = filePath.toLowerCase();
30703
+ if (lower.endsWith(".tsx") || lower.endsWith(".jsx")) {
30704
+ parseGrammar = "tsx";
30705
+ }
30706
+ }
30707
+ logger.debug("Analyzing file", { filePath, language, parseGrammar, codeLength: code.length });
30708
+ const tree = await parse(code, parseGrammar);
30239
30709
  try {
30240
30710
  logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
30241
30711
  const parseStatus = extractParseStatus(tree);
@@ -30563,7 +31033,7 @@ var colors = {
30563
31033
  };
30564
31034
 
30565
31035
  // src/version.ts
30566
- var version = "3.59.0";
31036
+ var version = "3.64.0";
30567
31037
 
30568
31038
  // src/formatters.ts
30569
31039
  var SINK_SEVERITY = {
@@ -30586,7 +31056,11 @@ var SINK_SEVERITY = {
30586
31056
  insecure_cookie: "low",
30587
31057
  trust_boundary: "medium",
30588
31058
  external_taint_escape: "medium",
30589
- mybatis_mapper_call: "medium"
31059
+ mybatis_mapper_call: "medium",
31060
+ redos: "medium",
31061
+ format_string: "high",
31062
+ crlf: "medium",
31063
+ mass_assignment: "high"
30590
31064
  };
30591
31065
  var SINK_CWE = {
30592
31066
  sql_injection: "CWE-89",
@@ -30608,7 +31082,11 @@ var SINK_CWE = {
30608
31082
  insecure_cookie: "CWE-614",
30609
31083
  trust_boundary: "CWE-501",
30610
31084
  external_taint_escape: "CWE-20",
30611
- mybatis_mapper_call: "CWE-89"
31085
+ mybatis_mapper_call: "CWE-89",
31086
+ redos: "CWE-1333",
31087
+ format_string: "CWE-134",
31088
+ crlf: "CWE-113",
31089
+ mass_assignment: "CWE-915"
30612
31090
  };
30613
31091
  var VULNERABILITY_HELP = {
30614
31092
  sql_injection: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.59.0",
3
+ "version": "3.64.0",
4
4
  "description": "Static Application Security Testing CLI for detecting security vulnerabilities via taint tracking",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -65,7 +65,7 @@
65
65
  "registry": "https://registry.npmjs.org/"
66
66
  },
67
67
  "dependencies": {
68
- "circle-ir": "^3.59.0"
68
+ "circle-ir": "^3.64.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",