cognium-dev 3.58.0 → 3.62.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 +520 -48
  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"] },
@@ -11787,7 +11968,11 @@ function matchesSourcePattern(call, pattern) {
11787
11968
  }
11788
11969
  if (pattern.class && pattern.class !== "constructor") {
11789
11970
  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;
11971
+ const target = call.resolution?.target;
11972
+ const expectedTail = `${pattern.class}.${pattern.method}`;
11973
+ if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {} else {
11974
+ return false;
11975
+ }
11791
11976
  } else if (!receiverMightBeClass(call.receiver, pattern.class)) {
11792
11977
  return false;
11793
11978
  }
@@ -12044,7 +12229,11 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12044
12229
  }
12045
12230
  return false;
12046
12231
  } else if (!call.receiver && !call.receiver_type) {
12047
- return false;
12232
+ const target = call.resolution?.target;
12233
+ const expectedTail = `${pattern.class}.${pattern.method}`;
12234
+ if (target && (target === expectedTail || target.endsWith("." + expectedTail))) {} else {
12235
+ return false;
12236
+ }
12048
12237
  }
12049
12238
  }
12050
12239
  if (!pattern.class && call.receiver) {
@@ -15051,6 +15240,9 @@ class DefaultLanguageRegistry {
15051
15240
  }
15052
15241
  }
15053
15242
  get(language) {
15243
+ if (language === "tsx") {
15244
+ return this.plugins.get("javascript");
15245
+ }
15054
15246
  return this.plugins.get(language);
15055
15247
  }
15056
15248
  getForFile(filePath) {
@@ -17244,6 +17436,20 @@ class BashPlugin extends BaseLanguagePlugin {
17244
17436
  cwe: "CWE-918",
17245
17437
  severity: "high",
17246
17438
  argPositions: [0]
17439
+ },
17440
+ {
17441
+ method: "source",
17442
+ type: "path_traversal",
17443
+ cwe: "CWE-98",
17444
+ severity: "critical",
17445
+ argPositions: [0]
17446
+ },
17447
+ {
17448
+ method: ".",
17449
+ type: "path_traversal",
17450
+ cwe: "CWE-98",
17451
+ severity: "critical",
17452
+ argPositions: [0]
17247
17453
  }
17248
17454
  ];
17249
17455
  }
@@ -20337,6 +20543,7 @@ function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
20337
20543
  const allSources = [];
20338
20544
  const allSinks = [];
20339
20545
  const allSanitizers = [];
20546
+ const allFlows = [];
20340
20547
  const allImports = [];
20341
20548
  const allExports = [];
20342
20549
  const allFindings = [];
@@ -20422,6 +20629,14 @@ function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
20422
20629
  line: sanitizer.line + lineShift
20423
20630
  });
20424
20631
  }
20632
+ for (const flow of ir.taint.flows ?? []) {
20633
+ allFlows.push({
20634
+ ...flow,
20635
+ source_line: flow.source_line + lineShift,
20636
+ sink_line: flow.sink_line + lineShift,
20637
+ path: flow.path.map((step) => ({ ...step, line: step.line + lineShift }))
20638
+ });
20639
+ }
20425
20640
  for (const imp of ir.imports) {
20426
20641
  allImports.push({
20427
20642
  ...imp,
@@ -20441,7 +20656,8 @@ function mergeHtmlResults(htmlMeta, scriptResults, attributeFindings) {
20441
20656
  const taint = {
20442
20657
  sources: allSources,
20443
20658
  sinks: allSinks,
20444
- sanitizers: allSanitizers.length > 0 ? allSanitizers : undefined
20659
+ sanitizers: allSanitizers.length > 0 ? allSanitizers : undefined,
20660
+ flows: allFlows.length > 0 ? allFlows : undefined
20445
20661
  };
20446
20662
  const cfg = {
20447
20663
  blocks: allCfgBlocks,
@@ -20637,7 +20853,9 @@ class LanguageSourcesPass {
20637
20853
  const constProp = ctx.getResult("constant-propagation");
20638
20854
  const additionalSources = [];
20639
20855
  const additionalSinks = [];
20856
+ const additionalSanitizers = [];
20640
20857
  additionalSources.push(...findGetterSources(types, constProp.instanceFieldTaint, code));
20858
+ additionalSources.push(...findOopFieldReadSources(types, code, language));
20641
20859
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
20642
20860
  const jsDOMSinks = findJavaScriptDOMSinks(code, language);
20643
20861
  for (const s of jsDOMSinks) {
@@ -20689,9 +20907,10 @@ class LanguageSourcesPass {
20689
20907
  for (const finding of bashFindings) {
20690
20908
  ctx.addFinding(finding);
20691
20909
  }
20910
+ additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
20692
20911
  }
20693
20912
  attachSourceLineCode(additionalSources, additionalSinks, code);
20694
- return { additionalSources, additionalSinks, pyTaintedVars, pySanitizedVars, jsTaintedVars };
20913
+ return { additionalSources, additionalSinks, additionalSanitizers, pyTaintedVars, pySanitizedVars, jsTaintedVars };
20695
20914
  }
20696
20915
  }
20697
20916
  function findGetterSources(types, instanceFieldTaint, _sourceCode) {
@@ -20743,6 +20962,128 @@ function findGetterSources(types, instanceFieldTaint, _sourceCode) {
20743
20962
  }
20744
20963
  return sources;
20745
20964
  }
20965
+ function findOopFieldReadSources(types, sourceCode, language) {
20966
+ if (language !== "java" && language !== "python")
20967
+ return [];
20968
+ const sources = [];
20969
+ const lines = sourceCode.split(`
20970
+ `);
20971
+ const isPython = language === "python";
20972
+ const SELF = isPython ? "self" : "this";
20973
+ const javaHttpPattern = /\b(?:req|request|httpRequest|servletRequest|httpServletRequest)\.(?:getParameter|getParameterValues|getParameterMap|getHeader|getHeaders|getCookies|getQueryString|getPathInfo|getRequestURI|getRequestURL|getInputStream|getReader)\b/;
20974
+ const fieldAssignRe = new RegExp(`^\\s*${SELF}\\.([A-Za-z_]\\w*)\\s*=\\s*(.+?)(?:;\\s*)?$`);
20975
+ const commentPrefix = isPython ? "#" : "//";
20976
+ for (const type of types) {
20977
+ if (type.kind !== "class")
20978
+ continue;
20979
+ if (type.name === "<module>")
20980
+ continue;
20981
+ let ctor;
20982
+ for (const m of type.methods) {
20983
+ if (isPython) {
20984
+ if (m.name === "__init__") {
20985
+ ctor = m;
20986
+ break;
20987
+ }
20988
+ } else {
20989
+ if (m.name === type.name) {
20990
+ ctor = m;
20991
+ break;
20992
+ }
20993
+ }
20994
+ }
20995
+ if (!ctor)
20996
+ continue;
20997
+ const paramNames = new Set;
20998
+ for (const p of ctor.parameters) {
20999
+ if (p.name === "self" || p.name === "this")
21000
+ continue;
21001
+ paramNames.add(p.name);
21002
+ }
21003
+ const fieldTaint = new Map;
21004
+ const ctorStart = ctor.start_line;
21005
+ const ctorEnd = ctor.end_line;
21006
+ for (let i2 = ctorStart - 1;i2 < Math.min(ctorEnd, lines.length); i2++) {
21007
+ const line = lines[i2] ?? "";
21008
+ if (line.trim().startsWith(commentPrefix))
21009
+ continue;
21010
+ const m = line.match(fieldAssignRe);
21011
+ if (!m)
21012
+ continue;
21013
+ const fieldName = m[1];
21014
+ const rhs = m[2].trim().replace(/;\s*$/, "");
21015
+ let sourceType = null;
21016
+ if (paramNames.has(rhs)) {
21017
+ sourceType = "interprocedural_param";
21018
+ } else if (!isPython && javaHttpPattern.test(rhs)) {
21019
+ sourceType = "http_param";
21020
+ } else if (isPython) {
21021
+ for (const { pattern, type: type2 } of PYTHON_TAINTED_PATTERNS2) {
21022
+ if (pattern.test(rhs)) {
21023
+ sourceType = type2;
21024
+ break;
21025
+ }
21026
+ }
21027
+ }
21028
+ if (sourceType) {
21029
+ fieldTaint.set(fieldName, { line: i2 + 1, type: sourceType });
21030
+ }
21031
+ }
21032
+ if (fieldTaint.size === 0)
21033
+ continue;
21034
+ for (const [fieldName, info2] of fieldTaint) {
21035
+ sources.push({
21036
+ type: info2.type,
21037
+ location: `${type.name}.${SELF}.${fieldName} (constructor-injected field, #78)`,
21038
+ severity: "high",
21039
+ line: info2.line,
21040
+ confidence: 0.85,
21041
+ variable: `${SELF}.${fieldName}`
21042
+ });
21043
+ }
21044
+ for (const m of type.methods) {
21045
+ if (m === ctor)
21046
+ continue;
21047
+ const nonSelfParams = m.parameters.filter((p) => p.name !== "self" && p.name !== "this");
21048
+ if (nonSelfParams.length !== 0)
21049
+ continue;
21050
+ const mStart = m.start_line;
21051
+ const mEnd = m.end_line;
21052
+ let returnedField = null;
21053
+ let returnStatementCount = 0;
21054
+ const returnRe = new RegExp(`\\breturn\\s+${SELF}\\.([A-Za-z_]\\w*)\\s*[;}]?`);
21055
+ for (let i2 = mStart - 1;i2 < Math.min(mEnd, lines.length); i2++) {
21056
+ const raw = lines[i2] ?? "";
21057
+ const trimmed = raw.trim();
21058
+ if (!trimmed)
21059
+ continue;
21060
+ if (trimmed.startsWith(commentPrefix))
21061
+ continue;
21062
+ const rm = trimmed.match(returnRe);
21063
+ if (rm) {
21064
+ returnedField = rm[1];
21065
+ returnStatementCount++;
21066
+ } else if (/\breturn\b/.test(trimmed)) {
21067
+ returnStatementCount = 99;
21068
+ break;
21069
+ }
21070
+ }
21071
+ if (returnStatementCount === 1 && returnedField && fieldTaint.has(returnedField)) {
21072
+ const fieldInfo = fieldTaint.get(returnedField);
21073
+ const getterVar = isPython ? `${SELF}.${m.name}` : m.name;
21074
+ sources.push({
21075
+ type: fieldInfo.type,
21076
+ location: `${type.name}.${m.name} returns tainted field '${returnedField}' (#78)`,
21077
+ severity: "high",
21078
+ line: m.start_line,
21079
+ confidence: 0.85,
21080
+ variable: getterVar
21081
+ });
21082
+ }
21083
+ }
21084
+ }
21085
+ return sources;
21086
+ }
20746
21087
  function findJavaScriptAssignmentSources(sourceCode, language) {
20747
21088
  if (!["javascript", "typescript"].includes(language))
20748
21089
  return [];
@@ -20820,62 +21161,62 @@ function buildPythonTaintedVars(sourceCode) {
20820
21161
  const line = lines[i2];
20821
21162
  if (line.trimStart().startsWith("#"))
20822
21163
  continue;
20823
- const subscriptAssign = line.match(/^\s*(\w+)\[(['"])([^'"]+)\2\]\s*=\s*(.+)$/);
21164
+ const subscriptAssign = line.match(/^\s*([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]\s*=\s*(.+)$/u);
20824
21165
  if (subscriptAssign) {
20825
21166
  const [, container, , key, rhs2] = subscriptAssign;
20826
- const isTaintedRhs = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(rhs2));
21167
+ const isTaintedRhs = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(rhs2));
20827
21168
  if (isTaintedRhs)
20828
21169
  containerTainted.set(`${container}['${key}']`, i2 + 1);
20829
21170
  continue;
20830
21171
  }
20831
- const setCallMatch = line.match(/^\s*(\w+)\.set\s*\(\s*(['"])([^'"]+)\2\s*,\s*(['"])([^'"]+)\4\s*,\s*(.+?)\s*\)$/);
21172
+ const setCallMatch = line.match(/^\s*([\p{L}\p{N}_]+)\.set\s*\(\s*(['"])([^'"]+)\2\s*,\s*(['"])([^'"]+)\4\s*,\s*(.+?)\s*\)$/u);
20832
21173
  if (setCallMatch) {
20833
21174
  const [, obj, , section, , key, rhs2] = setCallMatch;
20834
- const isTaintedRhs = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(rhs2));
21175
+ const isTaintedRhs = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(rhs2));
20835
21176
  if (isTaintedRhs)
20836
21177
  containerTainted.set(`${obj}['${section}']['${key}']`, i2 + 1);
20837
21178
  continue;
20838
21179
  }
20839
- const containerAppendMatch = line.match(/^\s*(\w+)\.(append|extend|insert|add|push|put|appendleft)\s*\(\s*(.+?)\s*\)\s*$/);
21180
+ const containerAppendMatch = line.match(/^\s*([\p{L}\p{N}_]+)\.(append|extend|insert|add|push|put|appendleft)\s*\(\s*(.+?)\s*\)\s*$/u);
20840
21181
  if (containerAppendMatch) {
20841
21182
  const [, receiver, , argExpr] = containerAppendMatch;
20842
- const argIsTainted = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(argExpr));
21183
+ const argIsTainted = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(argExpr));
20843
21184
  const argIsDirectSource = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(argExpr));
20844
21185
  if (argIsTainted || argIsDirectSource)
20845
21186
  tainted.set(receiver, tainted.get(receiver) ?? i2 + 1);
20846
21187
  continue;
20847
21188
  }
20848
- const augAssign = line.match(/^\s*(\w+)\s*\+=\s*(.+)$/);
21189
+ const augAssign = line.match(/^\s*([\p{L}\p{N}_]+)\s*\+=\s*(.+)$/u);
20849
21190
  if (augAssign) {
20850
21191
  const [, augLhs, augRhs] = augAssign;
20851
- const rhsTainted = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(augRhs));
21192
+ const rhsTainted = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(augRhs));
20852
21193
  if (rhsTainted || tainted.has(augLhs))
20853
21194
  tainted.set(augLhs, tainted.get(augLhs) ?? i2 + 1);
20854
21195
  continue;
20855
21196
  }
20856
- const forLoopMatch = line.match(/^\s*for\s+(\w+)\s+in\s+(.+?)(?:\s*:\s*)?$/);
21197
+ const forLoopMatch = line.match(/^\s*for\s+([\p{L}\p{N}_]+)\s+in\s+(.+?)(?:\s*:\s*)?$/u);
20857
21198
  if (forLoopMatch) {
20858
21199
  const [, iterVar, iterExpr] = forLoopMatch;
20859
21200
  const isDirectSource2 = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(iterExpr));
20860
- const isPropagated = [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(iterExpr));
21201
+ const isPropagated = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(iterExpr));
20861
21202
  if (isDirectSource2 || isPropagated)
20862
21203
  tainted.set(iterVar, i2 + 1);
20863
21204
  continue;
20864
21205
  }
20865
- const assignMatch = line.match(/^\s*(\w+)\s*=\s*(.+)$/);
21206
+ const assignMatch = line.match(/^\s*([\p{L}\p{N}_]+)\s*=\s*(.+)$/u);
20866
21207
  if (!assignMatch)
20867
21208
  continue;
20868
21209
  const [, lhs, rhs] = assignMatch;
20869
21210
  const isDirectSource = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(rhs));
20870
21211
  let propagatedFrom;
20871
- const dictAccessMatch = rhs.trim().match(/^(\w+)\[(['"])([^'"]+)\2\]$/);
21212
+ const dictAccessMatch = rhs.trim().match(/^([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]$/u);
20872
21213
  if (dictAccessMatch) {
20873
21214
  const [, container, , key] = dictAccessMatch;
20874
21215
  if (containerTainted.has(`${container}['${key}']`))
20875
21216
  propagatedFrom = `${container}['${key}']`;
20876
21217
  }
20877
21218
  if (!propagatedFrom) {
20878
- const confGetMatch = rhs.trim().match(/^(\w+)\.get\s*\(\s*(['"])([^'"]+)\2\s*,\s*(['"])([^'"]+)\4\s*\)$/);
21219
+ const confGetMatch = rhs.trim().match(/^([\p{L}\p{N}_]+)\.get\s*\(\s*(['"])([^'"]+)\2\s*,\s*(['"])([^'"]+)\4\s*\)$/u);
20879
21220
  if (confGetMatch) {
20880
21221
  const [, obj, , section, , key] = confGetMatch;
20881
21222
  if (containerTainted.has(`${obj}['${section}']['${key}']`))
@@ -20885,7 +21226,7 @@ function buildPythonTaintedVars(sourceCode) {
20885
21226
  if (!propagatedFrom) {
20886
21227
  const isSafeEnvRead = /\bos\.environ\.get\s*\(/.test(rhs) || /\bos\.getenv\s*\(/.test(rhs);
20887
21228
  if (!isSafeEnvRead)
20888
- propagatedFrom = [...tainted.keys()].find((v) => new RegExp(`\\b${v}\\b`).test(rhs));
21229
+ propagatedFrom = [...tainted.keys()].find((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(rhs));
20889
21230
  }
20890
21231
  if (isDirectSource) {
20891
21232
  tainted.set(lhs, i2 + 1);
@@ -21290,6 +21631,61 @@ function findBashPatternFindings(sourceCode, file) {
21290
21631
  }
21291
21632
  return findings;
21292
21633
  }
21634
+ function findBashRegexAllowlistSanitizers(code) {
21635
+ const sanitizers = [];
21636
+ const lines = code.split(`
21637
+ `);
21638
+ const guardRe = /^\s*if\s+\[\[\s*!\s*"?\$\{?(\w+)\}?"?\s*=~\s*(\S+)\s*\]\]\s*;\s*then\s+(exit|return|die)\b/;
21639
+ for (let i2 = 0;i2 < lines.length; i2++) {
21640
+ const m = guardRe.exec(lines[i2]);
21641
+ if (!m)
21642
+ continue;
21643
+ const regexLiteral = m[2];
21644
+ if (!isSafeBashAllowlistRegex(regexLiteral))
21645
+ continue;
21646
+ const ifLine1Indexed = i2 + 1;
21647
+ for (let l = ifLine1Indexed + 1;l <= lines.length; l++) {
21648
+ sanitizers.push({
21649
+ type: "regex_allowlist",
21650
+ method: "=~",
21651
+ line: l,
21652
+ sanitizes: [
21653
+ "command_injection",
21654
+ "path_traversal",
21655
+ "sql_injection",
21656
+ "code_injection",
21657
+ "ssrf",
21658
+ "xss",
21659
+ "open_redirect",
21660
+ "log_injection"
21661
+ ]
21662
+ });
21663
+ }
21664
+ }
21665
+ return sanitizers;
21666
+ }
21667
+ function isSafeBashAllowlistRegex(literal) {
21668
+ if (!literal.startsWith("^") || !literal.endsWith("$"))
21669
+ return false;
21670
+ const body2 = literal.slice(1, -1);
21671
+ if (body2.length === 0)
21672
+ return false;
21673
+ if (body2.includes(".*") || body2.includes(".+"))
21674
+ return false;
21675
+ if (body2.includes("|"))
21676
+ return false;
21677
+ if (/\\\d/.test(body2))
21678
+ return false;
21679
+ const safeToken = /\[[^\]]+\][+*?]?|\\.|[A-Za-z0-9_\-./]|[+*?]/g;
21680
+ let consumed = 0;
21681
+ let match;
21682
+ while ((match = safeToken.exec(body2)) !== null) {
21683
+ if (match.index !== consumed)
21684
+ return false;
21685
+ consumed += match[0].length;
21686
+ }
21687
+ return consumed === body2.length;
21688
+ }
21293
21689
 
21294
21690
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
21295
21691
  var JS_XSS_SANITIZERS = [
@@ -21326,7 +21722,10 @@ class SinkFilterPass {
21326
21722
  sinks.push(s);
21327
21723
  }
21328
21724
  }
21329
- const sanitizers = taintMatcher.sanitizers;
21725
+ const sanitizers = [
21726
+ ...taintMatcher.sanitizers,
21727
+ ...langSources.additionalSanitizers ?? []
21728
+ ];
21330
21729
  let filtered = sinks.filter((sink) => !constProp.unreachableLines.has(sink.line));
21331
21730
  filtered = filterCleanArraySinks(filtered, calls, constProp.taintedArrayElements, constProp.symbols);
21332
21731
  filtered = filterCleanVariableSinks(filtered, calls, constProp.tainted, constProp.symbols, dfg, constProp.sanitizedVars, constProp.synchronizedLines, language);
@@ -21811,6 +22210,21 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
21811
22210
  });
21812
22211
  }
21813
22212
  }
22213
+ if (source.variable) {
22214
+ const paramDefs = defsByLine.get(0) ?? [];
22215
+ for (const def of paramDefs) {
22216
+ if (def.kind === "param" && def.variable === source.variable) {
22217
+ tainted.push({
22218
+ variable: def.variable,
22219
+ defId: def.id,
22220
+ line: def.line,
22221
+ sourceType: source.type,
22222
+ sourceLine: source.line,
22223
+ confidence: source.confidence
22224
+ });
22225
+ }
22226
+ }
22227
+ }
21814
22228
  }
21815
22229
  return tainted;
21816
22230
  }
@@ -22384,7 +22798,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22384
22798
  if (reCache.has(s.variable))
22385
22799
  continue;
22386
22800
  const escaped = s.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22387
- reCache.set(s.variable, new RegExp(`\\b${escaped}\\b`));
22801
+ reCache.set(s.variable, new RegExp(`(?<![\\p{L}\\p{N}_])${escaped}(?![\\p{L}\\p{N}_])`, "u"));
22388
22802
  }
22389
22803
  const callsByLine = new Map;
22390
22804
  for (const call of calls) {
@@ -22526,6 +22940,13 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
22526
22940
  for (const def of graph.defsAtLine(source.line)) {
22527
22941
  seedIds.add(def.id);
22528
22942
  }
22943
+ if (source.variable) {
22944
+ for (const def of graph.defsAtLine(0)) {
22945
+ if (def.kind === "param" && def.variable === source.variable) {
22946
+ seedIds.add(def.id);
22947
+ }
22948
+ }
22949
+ }
22529
22950
  }
22530
22951
  const taintedDefIds = graph.propagateTaintedDefIds(seedIds);
22531
22952
  const taintedVarsFromCP = options.taintedVariables ?? new Set;
@@ -22666,7 +23087,34 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
22666
23087
  const targetMethod = getMethodNode(methodNodes, call.method_name);
22667
23088
  if (!targetMethod) {
22668
23089
  if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name)) {
22669
- const sink = {
23090
+ const isBash = graph.ir.meta.language === "bash";
23091
+ const bashSafeBuiltins = new Set([
23092
+ "echo",
23093
+ "printf",
23094
+ "test",
23095
+ "[",
23096
+ "[[",
23097
+ "true",
23098
+ "false",
23099
+ ":",
23100
+ "declare",
23101
+ "local",
23102
+ "export",
23103
+ "readonly",
23104
+ "typeset"
23105
+ ]);
23106
+ if (isBash && bashSafeBuiltins.has(call.method_name)) {
23107
+ continue;
23108
+ }
23109
+ const sink = isBash ? {
23110
+ type: "command_injection",
23111
+ cwe: "CWE-78",
23112
+ location: `Tainted data (${taintedArgVars.join(", ")}) passed unquoted to shell utility ${call.method_name}`,
23113
+ line: call.location.line,
23114
+ confidence: 0.6,
23115
+ method: call.method_name,
23116
+ argPositions: taintedArgPositions
23117
+ } : {
22670
23118
  type: "external_taint_escape",
22671
23119
  cwe: "CWE-668",
22672
23120
  location: `Tainted data (${taintedArgVars.join(", ")}) passed to external method ${call.receiver ? call.receiver + "." : ""}${call.method_name}()`,
@@ -23346,6 +23794,7 @@ function isPublicMethod(method, language) {
23346
23794
  return method.modifiers.includes("public");
23347
23795
  case "javascript":
23348
23796
  case "typescript":
23797
+ case "tsx":
23349
23798
  return !method.modifiers.includes("private") && !method.modifiers.includes("protected");
23350
23799
  case "python":
23351
23800
  return !method.name.startsWith("_");
@@ -23365,7 +23814,7 @@ class MissingPublicDocPass {
23365
23814
  if (UTIL_DIR_RE.test(graph.ir.meta.file)) {
23366
23815
  return { missingDocMethods: [], missingDocTypes: [] };
23367
23816
  }
23368
- if (!["java", "javascript", "typescript", "python"].includes(language)) {
23817
+ if (!["java", "javascript", "typescript", "tsx", "python"].includes(language)) {
23369
23818
  return { missingDocMethods: [], missingDocTypes: [] };
23370
23819
  }
23371
23820
  const lines = code.split(`
@@ -23889,6 +24338,7 @@ function hasDeclKeyword(lineText, language) {
23889
24338
  return /\b(?:int|long|float|double|boolean|byte|char|short|var|final)\b/.test(lineText) || /\b[A-Z]\w*(?:<[^>]*>)?\s+\w/.test(lineText);
23890
24339
  case "javascript":
23891
24340
  case "typescript":
24341
+ case "tsx":
23892
24342
  return /\b(?:let|const|var)\s+[\w{[]/.test(lineText);
23893
24343
  case "rust":
23894
24344
  return /\blet\s+(?:mut\s+)?\w/.test(lineText);
@@ -28395,6 +28845,7 @@ class WeakRandomPass {
28395
28845
  return "Use the `secrets` module (`secrets.token_bytes`, " + "`secrets.token_hex`, `secrets.choice`, `secrets.randbelow`).";
28396
28846
  case "javascript":
28397
28847
  case "typescript":
28848
+ case "tsx":
28398
28849
  return "Use `crypto.randomBytes(n)` (Node.js) or " + "`crypto.getRandomValues(typedArray)` (browser).";
28399
28850
  case "go":
28400
28851
  return "Use `crypto/rand` instead of `math/rand`. Example: " + "`b := make([]byte, 32); _, _ = rand.Read(b)` (where `rand` is `crypto/rand`).";
@@ -28433,7 +28884,7 @@ class WeakRandomPass {
28433
28884
  }
28434
28885
  return null;
28435
28886
  }
28436
- if (language === "javascript" || language === "typescript") {
28887
+ if (language === "javascript" || language === "typescript" || language === "tsx") {
28437
28888
  if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
28438
28889
  return "Math.random";
28439
28890
  }
@@ -30035,6 +30486,7 @@ function getNodeTypesForLanguage(language) {
30035
30486
  ]);
30036
30487
  case "javascript":
30037
30488
  case "typescript":
30489
+ case "tsx":
30038
30490
  return new Set([
30039
30491
  "call_expression",
30040
30492
  "new_expression",
@@ -30047,7 +30499,12 @@ function getNodeTypesForLanguage(language) {
30047
30499
  "import_statement",
30048
30500
  "export_statement",
30049
30501
  "member_expression",
30050
- "assignment_expression"
30502
+ "assignment_expression",
30503
+ "jsx_element",
30504
+ "jsx_self_closing_element",
30505
+ "jsx_opening_element",
30506
+ "jsx_attribute",
30507
+ "jsx_expression"
30051
30508
  ]);
30052
30509
  case "bash":
30053
30510
  return new Set([
@@ -30111,8 +30568,15 @@ async function analyze(code, filePath, language, options = {}) {
30111
30568
  if (language === "html") {
30112
30569
  return analyzeHtmlFile(code, filePath, options);
30113
30570
  }
30114
- logger.debug("Analyzing file", { filePath, language, codeLength: code.length });
30115
- const tree = await parse(code, language);
30571
+ let parseGrammar = language;
30572
+ if (language === "javascript" || language === "typescript") {
30573
+ const lower = filePath.toLowerCase();
30574
+ if (lower.endsWith(".tsx") || lower.endsWith(".jsx")) {
30575
+ parseGrammar = "tsx";
30576
+ }
30577
+ }
30578
+ logger.debug("Analyzing file", { filePath, language, parseGrammar, codeLength: code.length });
30579
+ const tree = await parse(code, parseGrammar);
30116
30580
  try {
30117
30581
  logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
30118
30582
  const parseStatus = extractParseStatus(tree);
@@ -30440,7 +30904,7 @@ var colors = {
30440
30904
  };
30441
30905
 
30442
30906
  // src/version.ts
30443
- var version = "3.58.0";
30907
+ var version = "3.62.0";
30444
30908
 
30445
30909
  // src/formatters.ts
30446
30910
  var SINK_SEVERITY = {
@@ -30463,7 +30927,11 @@ var SINK_SEVERITY = {
30463
30927
  insecure_cookie: "low",
30464
30928
  trust_boundary: "medium",
30465
30929
  external_taint_escape: "medium",
30466
- mybatis_mapper_call: "medium"
30930
+ mybatis_mapper_call: "medium",
30931
+ redos: "medium",
30932
+ format_string: "high",
30933
+ crlf: "medium",
30934
+ mass_assignment: "high"
30467
30935
  };
30468
30936
  var SINK_CWE = {
30469
30937
  sql_injection: "CWE-89",
@@ -30485,7 +30953,11 @@ var SINK_CWE = {
30485
30953
  insecure_cookie: "CWE-614",
30486
30954
  trust_boundary: "CWE-501",
30487
30955
  external_taint_escape: "CWE-20",
30488
- mybatis_mapper_call: "CWE-89"
30956
+ mybatis_mapper_call: "CWE-89",
30957
+ redos: "CWE-1333",
30958
+ format_string: "CWE-134",
30959
+ crlf: "CWE-113",
30960
+ mass_assignment: "CWE-915"
30489
30961
  };
30490
30962
  var VULNERABILITY_HELP = {
30491
30963
  sql_injection: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.58.0",
3
+ "version": "3.62.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.58.0"
68
+ "circle-ir": "^3.62.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",