cognium-dev 3.59.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 +397 -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,6 +20853,7 @@ 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));
20641
20858
  additionalSources.push(...findOopFieldReadSources(types, code, language));
20642
20859
  additionalSources.push(...findJavaScriptAssignmentSources(code, language));
@@ -20690,9 +20907,10 @@ class LanguageSourcesPass {
20690
20907
  for (const finding of bashFindings) {
20691
20908
  ctx.addFinding(finding);
20692
20909
  }
20910
+ additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
20693
20911
  }
20694
20912
  attachSourceLineCode(additionalSources, additionalSinks, code);
20695
- return { additionalSources, additionalSinks, pyTaintedVars, pySanitizedVars, jsTaintedVars };
20913
+ return { additionalSources, additionalSinks, additionalSanitizers, pyTaintedVars, pySanitizedVars, jsTaintedVars };
20696
20914
  }
20697
20915
  }
20698
20916
  function findGetterSources(types, instanceFieldTaint, _sourceCode) {
@@ -20943,62 +21161,62 @@ function buildPythonTaintedVars(sourceCode) {
20943
21161
  const line = lines[i2];
20944
21162
  if (line.trimStart().startsWith("#"))
20945
21163
  continue;
20946
- const subscriptAssign = line.match(/^\s*(\w+)\[(['"])([^'"]+)\2\]\s*=\s*(.+)$/);
21164
+ const subscriptAssign = line.match(/^\s*([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]\s*=\s*(.+)$/u);
20947
21165
  if (subscriptAssign) {
20948
21166
  const [, container, , key, rhs2] = subscriptAssign;
20949
- 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));
20950
21168
  if (isTaintedRhs)
20951
21169
  containerTainted.set(`${container}['${key}']`, i2 + 1);
20952
21170
  continue;
20953
21171
  }
20954
- 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);
20955
21173
  if (setCallMatch) {
20956
21174
  const [, obj, , section, , key, rhs2] = setCallMatch;
20957
- 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));
20958
21176
  if (isTaintedRhs)
20959
21177
  containerTainted.set(`${obj}['${section}']['${key}']`, i2 + 1);
20960
21178
  continue;
20961
21179
  }
20962
- 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);
20963
21181
  if (containerAppendMatch) {
20964
21182
  const [, receiver, , argExpr] = containerAppendMatch;
20965
- 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));
20966
21184
  const argIsDirectSource = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(argExpr));
20967
21185
  if (argIsTainted || argIsDirectSource)
20968
21186
  tainted.set(receiver, tainted.get(receiver) ?? i2 + 1);
20969
21187
  continue;
20970
21188
  }
20971
- const augAssign = line.match(/^\s*(\w+)\s*\+=\s*(.+)$/);
21189
+ const augAssign = line.match(/^\s*([\p{L}\p{N}_]+)\s*\+=\s*(.+)$/u);
20972
21190
  if (augAssign) {
20973
21191
  const [, augLhs, augRhs] = augAssign;
20974
- 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));
20975
21193
  if (rhsTainted || tainted.has(augLhs))
20976
21194
  tainted.set(augLhs, tainted.get(augLhs) ?? i2 + 1);
20977
21195
  continue;
20978
21196
  }
20979
- 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);
20980
21198
  if (forLoopMatch) {
20981
21199
  const [, iterVar, iterExpr] = forLoopMatch;
20982
21200
  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));
21201
+ const isPropagated = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(iterExpr));
20984
21202
  if (isDirectSource2 || isPropagated)
20985
21203
  tainted.set(iterVar, i2 + 1);
20986
21204
  continue;
20987
21205
  }
20988
- const assignMatch = line.match(/^\s*(\w+)\s*=\s*(.+)$/);
21206
+ const assignMatch = line.match(/^\s*([\p{L}\p{N}_]+)\s*=\s*(.+)$/u);
20989
21207
  if (!assignMatch)
20990
21208
  continue;
20991
21209
  const [, lhs, rhs] = assignMatch;
20992
21210
  const isDirectSource = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(rhs));
20993
21211
  let propagatedFrom;
20994
- const dictAccessMatch = rhs.trim().match(/^(\w+)\[(['"])([^'"]+)\2\]$/);
21212
+ const dictAccessMatch = rhs.trim().match(/^([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]$/u);
20995
21213
  if (dictAccessMatch) {
20996
21214
  const [, container, , key] = dictAccessMatch;
20997
21215
  if (containerTainted.has(`${container}['${key}']`))
20998
21216
  propagatedFrom = `${container}['${key}']`;
20999
21217
  }
21000
21218
  if (!propagatedFrom) {
21001
- 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);
21002
21220
  if (confGetMatch) {
21003
21221
  const [, obj, , section, , key] = confGetMatch;
21004
21222
  if (containerTainted.has(`${obj}['${section}']['${key}']`))
@@ -21008,7 +21226,7 @@ function buildPythonTaintedVars(sourceCode) {
21008
21226
  if (!propagatedFrom) {
21009
21227
  const isSafeEnvRead = /\bos\.environ\.get\s*\(/.test(rhs) || /\bos\.getenv\s*\(/.test(rhs);
21010
21228
  if (!isSafeEnvRead)
21011
- 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));
21012
21230
  }
21013
21231
  if (isDirectSource) {
21014
21232
  tainted.set(lhs, i2 + 1);
@@ -21413,6 +21631,61 @@ function findBashPatternFindings(sourceCode, file) {
21413
21631
  }
21414
21632
  return findings;
21415
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
+ }
21416
21689
 
21417
21690
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
21418
21691
  var JS_XSS_SANITIZERS = [
@@ -21449,7 +21722,10 @@ class SinkFilterPass {
21449
21722
  sinks.push(s);
21450
21723
  }
21451
21724
  }
21452
- const sanitizers = taintMatcher.sanitizers;
21725
+ const sanitizers = [
21726
+ ...taintMatcher.sanitizers,
21727
+ ...langSources.additionalSanitizers ?? []
21728
+ ];
21453
21729
  let filtered = sinks.filter((sink) => !constProp.unreachableLines.has(sink.line));
21454
21730
  filtered = filterCleanArraySinks(filtered, calls, constProp.taintedArrayElements, constProp.symbols);
21455
21731
  filtered = filterCleanVariableSinks(filtered, calls, constProp.tainted, constProp.symbols, dfg, constProp.sanitizedVars, constProp.synchronizedLines, language);
@@ -21934,6 +22210,21 @@ function findInitialTaint(sources, callsByLine, defsByLine) {
21934
22210
  });
21935
22211
  }
21936
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
+ }
21937
22228
  }
21938
22229
  return tainted;
21939
22230
  }
@@ -22507,7 +22798,7 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22507
22798
  if (reCache.has(s.variable))
22508
22799
  continue;
22509
22800
  const escaped = s.variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
22510
- 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"));
22511
22802
  }
22512
22803
  const callsByLine = new Map;
22513
22804
  for (const call of calls) {
@@ -22649,6 +22940,13 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
22649
22940
  for (const def of graph.defsAtLine(source.line)) {
22650
22941
  seedIds.add(def.id);
22651
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
+ }
22652
22950
  }
22653
22951
  const taintedDefIds = graph.propagateTaintedDefIds(seedIds);
22654
22952
  const taintedVarsFromCP = options.taintedVariables ?? new Set;
@@ -22789,7 +23087,34 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
22789
23087
  const targetMethod = getMethodNode(methodNodes, call.method_name);
22790
23088
  if (!targetMethod) {
22791
23089
  if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name)) {
22792
- 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
+ } : {
22793
23118
  type: "external_taint_escape",
22794
23119
  cwe: "CWE-668",
22795
23120
  location: `Tainted data (${taintedArgVars.join(", ")}) passed to external method ${call.receiver ? call.receiver + "." : ""}${call.method_name}()`,
@@ -23469,6 +23794,7 @@ function isPublicMethod(method, language) {
23469
23794
  return method.modifiers.includes("public");
23470
23795
  case "javascript":
23471
23796
  case "typescript":
23797
+ case "tsx":
23472
23798
  return !method.modifiers.includes("private") && !method.modifiers.includes("protected");
23473
23799
  case "python":
23474
23800
  return !method.name.startsWith("_");
@@ -23488,7 +23814,7 @@ class MissingPublicDocPass {
23488
23814
  if (UTIL_DIR_RE.test(graph.ir.meta.file)) {
23489
23815
  return { missingDocMethods: [], missingDocTypes: [] };
23490
23816
  }
23491
- if (!["java", "javascript", "typescript", "python"].includes(language)) {
23817
+ if (!["java", "javascript", "typescript", "tsx", "python"].includes(language)) {
23492
23818
  return { missingDocMethods: [], missingDocTypes: [] };
23493
23819
  }
23494
23820
  const lines = code.split(`
@@ -24012,6 +24338,7 @@ function hasDeclKeyword(lineText, language) {
24012
24338
  return /\b(?:int|long|float|double|boolean|byte|char|short|var|final)\b/.test(lineText) || /\b[A-Z]\w*(?:<[^>]*>)?\s+\w/.test(lineText);
24013
24339
  case "javascript":
24014
24340
  case "typescript":
24341
+ case "tsx":
24015
24342
  return /\b(?:let|const|var)\s+[\w{[]/.test(lineText);
24016
24343
  case "rust":
24017
24344
  return /\blet\s+(?:mut\s+)?\w/.test(lineText);
@@ -28518,6 +28845,7 @@ class WeakRandomPass {
28518
28845
  return "Use the `secrets` module (`secrets.token_bytes`, " + "`secrets.token_hex`, `secrets.choice`, `secrets.randbelow`).";
28519
28846
  case "javascript":
28520
28847
  case "typescript":
28848
+ case "tsx":
28521
28849
  return "Use `crypto.randomBytes(n)` (Node.js) or " + "`crypto.getRandomValues(typedArray)` (browser).";
28522
28850
  case "go":
28523
28851
  return "Use `crypto/rand` instead of `math/rand`. Example: " + "`b := make([]byte, 32); _, _ = rand.Read(b)` (where `rand` is `crypto/rand`).";
@@ -28556,7 +28884,7 @@ class WeakRandomPass {
28556
28884
  }
28557
28885
  return null;
28558
28886
  }
28559
- if (language === "javascript" || language === "typescript") {
28887
+ if (language === "javascript" || language === "typescript" || language === "tsx") {
28560
28888
  if (method === "random" && (receiver === "Math" || receiver.endsWith(".Math"))) {
28561
28889
  return "Math.random";
28562
28890
  }
@@ -30158,6 +30486,7 @@ function getNodeTypesForLanguage(language) {
30158
30486
  ]);
30159
30487
  case "javascript":
30160
30488
  case "typescript":
30489
+ case "tsx":
30161
30490
  return new Set([
30162
30491
  "call_expression",
30163
30492
  "new_expression",
@@ -30170,7 +30499,12 @@ function getNodeTypesForLanguage(language) {
30170
30499
  "import_statement",
30171
30500
  "export_statement",
30172
30501
  "member_expression",
30173
- "assignment_expression"
30502
+ "assignment_expression",
30503
+ "jsx_element",
30504
+ "jsx_self_closing_element",
30505
+ "jsx_opening_element",
30506
+ "jsx_attribute",
30507
+ "jsx_expression"
30174
30508
  ]);
30175
30509
  case "bash":
30176
30510
  return new Set([
@@ -30234,8 +30568,15 @@ async function analyze(code, filePath, language, options = {}) {
30234
30568
  if (language === "html") {
30235
30569
  return analyzeHtmlFile(code, filePath, options);
30236
30570
  }
30237
- logger.debug("Analyzing file", { filePath, language, codeLength: code.length });
30238
- 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);
30239
30580
  try {
30240
30581
  logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
30241
30582
  const parseStatus = extractParseStatus(tree);
@@ -30563,7 +30904,7 @@ var colors = {
30563
30904
  };
30564
30905
 
30565
30906
  // src/version.ts
30566
- var version = "3.59.0";
30907
+ var version = "3.62.0";
30567
30908
 
30568
30909
  // src/formatters.ts
30569
30910
  var SINK_SEVERITY = {
@@ -30586,7 +30927,11 @@ var SINK_SEVERITY = {
30586
30927
  insecure_cookie: "low",
30587
30928
  trust_boundary: "medium",
30588
30929
  external_taint_escape: "medium",
30589
- mybatis_mapper_call: "medium"
30930
+ mybatis_mapper_call: "medium",
30931
+ redos: "medium",
30932
+ format_string: "high",
30933
+ crlf: "medium",
30934
+ mass_assignment: "high"
30590
30935
  };
30591
30936
  var SINK_CWE = {
30592
30937
  sql_injection: "CWE-89",
@@ -30608,7 +30953,11 @@ var SINK_CWE = {
30608
30953
  insecure_cookie: "CWE-614",
30609
30954
  trust_boundary: "CWE-501",
30610
30955
  external_taint_escape: "CWE-20",
30611
- 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"
30612
30961
  };
30613
30962
  var VULNERABILITY_HELP = {
30614
30963
  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.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.59.0"
68
+ "circle-ir": "^3.62.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",