cognium-dev 3.47.0 → 3.49.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 +452 -99
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -3391,11 +3391,14 @@ function disposeTree(tree) {
3391
3391
  } catch {}
3392
3392
  }
3393
3393
  function walkTree(node, visitor) {
3394
- visitor(node);
3395
- for (let i2 = 0;i2 < node.childCount; i2++) {
3396
- const child = node.child(i2);
3397
- if (child) {
3398
- walkTree(child, visitor);
3394
+ const stack = [node];
3395
+ while (stack.length > 0) {
3396
+ const current = stack.pop();
3397
+ visitor(current);
3398
+ for (let i2 = current.childCount - 1;i2 >= 0; i2--) {
3399
+ const child = current.child(i2);
3400
+ if (child)
3401
+ stack.push(child);
3399
3402
  }
3400
3403
  }
3401
3404
  }
@@ -4032,15 +4035,54 @@ function extractJSClassInfo(node) {
4032
4035
  end_line: node.endPosition.row + 1
4033
4036
  };
4034
4037
  }
4038
+ function extractDecoratorName(node) {
4039
+ const child = node.namedChildCount > 0 ? node.namedChild(0) : null;
4040
+ if (!child)
4041
+ return null;
4042
+ if (child.type === "identifier")
4043
+ return getNodeText(child);
4044
+ if (child.type === "call_expression") {
4045
+ const fn = child.childForFieldName("function");
4046
+ if (fn) {
4047
+ if (fn.type === "identifier")
4048
+ return getNodeText(fn);
4049
+ if (fn.type === "member_expression") {
4050
+ const propNode = fn.childForFieldName("property");
4051
+ if (propNode)
4052
+ return getNodeText(propNode);
4053
+ }
4054
+ }
4055
+ }
4056
+ if (child.type === "member_expression") {
4057
+ const propNode = child.childForFieldName("property");
4058
+ if (propNode)
4059
+ return getNodeText(propNode);
4060
+ }
4061
+ return null;
4062
+ }
4035
4063
  function extractJSMethods(body2) {
4036
4064
  const methods = [];
4065
+ let pendingDecorators = [];
4037
4066
  for (let i2 = 0;i2 < body2.childCount; i2++) {
4038
4067
  const child = body2.child(i2);
4039
4068
  if (!child)
4040
4069
  continue;
4070
+ if (child.type === "decorator") {
4071
+ const name2 = extractDecoratorName(child);
4072
+ if (name2)
4073
+ pendingDecorators.push(name2);
4074
+ continue;
4075
+ }
4076
+ if (child.type === "comment")
4077
+ continue;
4041
4078
  if (child.type === "method_definition") {
4042
- methods.push(extractJSMethodInfo(child));
4079
+ const m = extractJSMethodInfo(child);
4080
+ if (pendingDecorators.length > 0) {
4081
+ m.annotations = pendingDecorators;
4082
+ }
4083
+ methods.push(m);
4043
4084
  }
4085
+ pendingDecorators = [];
4044
4086
  }
4045
4087
  return methods;
4046
4088
  }
@@ -4211,10 +4253,19 @@ function extractJSParameters(params) {
4211
4253
  if (typeNode) {
4212
4254
  paramType = getNodeText(typeNode).replace(/^:\s*/, "");
4213
4255
  }
4256
+ const decorators = [];
4257
+ for (let j = 0;j < child.childCount; j++) {
4258
+ const c = child.child(j);
4259
+ if (c && c.type === "decorator") {
4260
+ const name2 = extractDecoratorName(c);
4261
+ if (name2)
4262
+ decorators.push(name2);
4263
+ }
4264
+ }
4214
4265
  parameters.push({
4215
4266
  name: paramName,
4216
4267
  type: paramType,
4217
- annotations: [],
4268
+ annotations: decorators,
4218
4269
  line: child.startPosition.row + 1
4219
4270
  });
4220
4271
  }
@@ -10795,6 +10846,18 @@ var DEFAULT_SINKS = [
10795
10846
  { method: "getExecutionPreamble", class: "Shell", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [] },
10796
10847
  { method: "setQuotedArgumentsEnabled", class: "Shell", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
10797
10848
  { method: "onNewInstance", class: "SandboxInterceptor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10849
+ { method: "info", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
10850
+ { method: "warn", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
10851
+ { method: "error", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
10852
+ { method: "debug", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
10853
+ { method: "trace", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["java"] },
10854
+ { method: "severe", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
10855
+ { method: "warning", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
10856
+ { method: "config", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
10857
+ { method: "fine", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
10858
+ { method: "finer", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
10859
+ { method: "finest", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0], languages: ["java"] },
10860
+ { method: "log", class: "Logger", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [1, 2, 3], languages: ["java"] },
10798
10861
  { method: "exec", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10799
10862
  { method: "execSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10800
10863
  { method: "spawn", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -10835,6 +10898,29 @@ var DEFAULT_SINKS = [
10835
10898
  { method: "updateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
10836
10899
  { method: "deleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
10837
10900
  { method: "deleteMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
10901
+ { method: "find", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10902
+ { method: "findOne", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10903
+ { method: "findById", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10904
+ { method: "findOneAndUpdate", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
10905
+ { method: "findOneAndDelete", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10906
+ { method: "findOneAndReplace", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
10907
+ { method: "updateOne", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
10908
+ { method: "updateMany", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
10909
+ { method: "deleteOne", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10910
+ { method: "deleteMany", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10911
+ { method: "countDocuments", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10912
+ { method: "aggregate", class: "Model", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10913
+ { method: "where", class: "Query", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10914
+ { method: "equals", class: "Query", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10915
+ { method: "findOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10916
+ { method: "findOneAndUpdate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
10917
+ { method: "findOneAndDelete", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10918
+ { method: "findOneAndReplace", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
10919
+ { method: "updateOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
10920
+ { method: "updateMany", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["javascript", "typescript"] },
10921
+ { method: "deleteOne", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10922
+ { method: "deleteMany", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10923
+ { method: "aggregate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["javascript", "typescript"] },
10838
10924
  { method: "get", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10839
10925
  { method: "post", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10840
10926
  { method: "request", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -10851,6 +10937,13 @@ var DEFAULT_SINKS = [
10851
10937
  { method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10852
10938
  { method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10853
10939
  { method: "default", class: "node-fetch", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10940
+ { method: "log", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
10941
+ { method: "warn", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
10942
+ { method: "error", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
10943
+ { method: "info", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
10944
+ { method: "debug", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
10945
+ { method: "trace", class: "console", type: "log_injection", cwe: "CWE-117", severity: "low", arg_positions: [0, 1, 2, 3], languages: ["javascript", "typescript"] },
10946
+ { method: "redirect", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["javascript", "typescript"] },
10854
10947
  { method: "system", class: "os", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10855
10948
  { method: "popen", class: "os", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10856
10949
  { method: "run", class: "subprocess", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -10877,7 +10970,7 @@ var DEFAULT_SINKS = [
10877
10970
  { method: "rmdir", class: "os", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10878
10971
  { method: "rmtree", class: "shutil", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
10879
10972
  { method: "send_file", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
10880
- { method: "render_template_string", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
10973
+ { method: "render_template_string", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0], languages: ["python"] },
10881
10974
  { method: "Markup", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
10882
10975
  { method: "mark_safe", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
10883
10976
  { method: "get", class: "requests", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -11139,6 +11232,10 @@ var DEFAULT_SANITIZERS = [
11139
11232
  { method: "secure_filename", class: "werkzeug.utils", removes: ["path_traversal"] },
11140
11233
  { method: "basename", class: "os.path", removes: ["path_traversal"] },
11141
11234
  { method: "normpath", class: "os.path", removes: ["path_traversal"] },
11235
+ { method: "realpath", class: "os.path", removes: ["path_traversal"] },
11236
+ { method: "abspath", class: "os.path", removes: ["path_traversal"] },
11237
+ { method: "realpath", class: "path", removes: ["path_traversal"] },
11238
+ { method: "abspath", class: "path", removes: ["path_traversal"] },
11142
11239
  { method: "int", removes: ["sql_injection", "command_injection", "xss"] },
11143
11240
  { method: "float", removes: ["sql_injection", "command_injection"] },
11144
11241
  { method: "query!", removes: ["sql_injection"] },
@@ -11267,7 +11364,7 @@ var PYTHON_TAINTED_PATTERNS = [
11267
11364
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
11268
11365
  const sourceLines = code !== undefined ? code.split(`
11269
11366
  `) : undefined;
11270
- const sources = findSources(calls, types, config.sources, sourceLines);
11367
+ const sources = findSources(calls, types, config.sources, sourceLines, language);
11271
11368
  const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
11272
11369
  const sanitizers = findSanitizers(calls, types, config.sanitizers);
11273
11370
  return { sources, sinks, sanitizers };
@@ -11286,7 +11383,7 @@ function attachSourceLineCode(sources, sinks, code) {
11286
11383
  }
11287
11384
  }
11288
11385
  }
11289
- function findSources(calls, types, patterns, sourceLines) {
11386
+ function findSources(calls, types, patterns, sourceLines, language) {
11290
11387
  const sources = [];
11291
11388
  for (const call of calls) {
11292
11389
  for (const pattern of patterns) {
@@ -11341,23 +11438,31 @@ function findSources(calls, types, patterns, sourceLines) {
11341
11438
  }
11342
11439
  }
11343
11440
  }
11344
- const RUST_EXTRACTOR_TYPES = /^(?:Json|Form|Query|Path|Extension|Multipart)(?:<|$)|^(?:Body|Bytes)$/;
11441
+ const RUST_EXTRACTOR_KIND = /(?:^|::)(Json|Form|Query|Path|Extension|Multipart|Body|Bytes)(?:<|$)/;
11345
11442
  for (const type of types) {
11346
11443
  for (const method of type.methods) {
11347
11444
  for (const param of method.parameters) {
11348
- if (param.type && RUST_EXTRACTOR_TYPES.test(param.type)) {
11349
- const paramLine = param.line ?? method.start_line;
11350
- const alreadyExists = sources.some((s) => s.line === paramLine && s.type === "http_body");
11351
- if (!alreadyExists) {
11352
- sources.push({
11353
- type: "http_body",
11354
- location: `${param.type} ${param.name} in ${method.name}`,
11355
- severity: "high",
11356
- line: paramLine,
11357
- confidence: 1
11358
- });
11359
- }
11360
- }
11445
+ if (!param.type)
11446
+ continue;
11447
+ const kindMatch = RUST_EXTRACTOR_KIND.exec(param.type);
11448
+ if (!kindMatch)
11449
+ continue;
11450
+ const kind = kindMatch[1];
11451
+ if (kind === "Extension")
11452
+ continue;
11453
+ const sourceType = kind === "Form" || kind === "Query" || kind === "Path" ? "http_param" : "http_body";
11454
+ const paramLine = param.line ?? method.start_line;
11455
+ const alreadyExists = sources.some((s) => s.line === paramLine && s.variable === param.name);
11456
+ if (alreadyExists)
11457
+ continue;
11458
+ sources.push({
11459
+ type: sourceType,
11460
+ location: `${param.type} ${param.name} in ${method.name}`,
11461
+ severity: "high",
11462
+ line: paramLine,
11463
+ confidence: 1,
11464
+ variable: param.name
11465
+ });
11361
11466
  }
11362
11467
  }
11363
11468
  }
@@ -11439,6 +11544,17 @@ function findSources(calls, types, patterns, sourceLines) {
11439
11544
  s.code = sourceLines[s.line - 1]?.trim();
11440
11545
  }
11441
11546
  }
11547
+ if (language === "rust" && sourceLines) {
11548
+ const LET_BINDING = /^\s*let\s+(?:mut\s+)?([A-Za-z_]\w*)\s*(?::\s*[^=]+)?=/;
11549
+ for (const s of result) {
11550
+ if (s.variable && s.variable.length > 0)
11551
+ continue;
11552
+ const lineText = sourceLines[s.line - 1] ?? "";
11553
+ const m = LET_BINDING.exec(lineText);
11554
+ if (m)
11555
+ s.variable = m[1];
11556
+ }
11557
+ }
11442
11558
  return result;
11443
11559
  }
11444
11560
  function isInterproceduralTaintableType(typeName) {
@@ -11535,6 +11651,26 @@ function isParameterizedQueryCall(call, pattern) {
11535
11651
  }
11536
11652
  return false;
11537
11653
  }
11654
+ function isSafePythonSubprocessCall(call, pattern, language) {
11655
+ if (language !== "python")
11656
+ return false;
11657
+ if (pattern.type !== "command_injection")
11658
+ return false;
11659
+ if (pattern.class !== "subprocess")
11660
+ return false;
11661
+ const arg0 = call.arguments.find((a) => a.position === 0);
11662
+ if (!arg0)
11663
+ return false;
11664
+ const expr0 = (arg0.literal ?? arg0.expression ?? "").trim();
11665
+ if (!expr0.startsWith("["))
11666
+ return false;
11667
+ for (const a of call.arguments) {
11668
+ const e = (a.expression ?? "").trim();
11669
+ if (/^shell\s*=\s*True\b/.test(e))
11670
+ return false;
11671
+ }
11672
+ return true;
11673
+ }
11538
11674
  var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
11539
11675
  function argIsClassLiteral(call, position) {
11540
11676
  const arg = call.arguments.find((a) => a.position === position);
@@ -11553,6 +11689,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
11553
11689
  if (isParameterizedQueryCall(call, pattern)) {
11554
11690
  continue;
11555
11691
  }
11692
+ if (isSafePythonSubprocessCall(call, pattern, language)) {
11693
+ continue;
11694
+ }
11556
11695
  if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
11557
11696
  continue;
11558
11697
  }
@@ -11947,7 +12086,8 @@ function receiverMightBeClass(receiver, className) {
11947
12086
  "controller",
11948
12087
  "task",
11949
12088
  "thread",
11950
- "job"
12089
+ "job",
12090
+ "cur"
11951
12091
  ]);
11952
12092
  const isAmbiguous = ambiguousIdentifiers.has(lowerReceiver);
11953
12093
  if (!isAmbiguous && lowerReceiver.length >= 3 && lowerClass.includes(lowerReceiver)) {
@@ -11957,7 +12097,9 @@ function receiverMightBeClass(receiver, className) {
11957
12097
  }
11958
12098
  if (!isAmbiguous && lowerReceiver.length >= 2) {
11959
12099
  if (lowerClass.startsWith(lowerReceiver) || lowerClass.endsWith(lowerReceiver)) {
11960
- return true;
12100
+ if (lowerReceiver.length / lowerClass.length >= 0.4) {
12101
+ return true;
12102
+ }
11961
12103
  }
11962
12104
  }
11963
12105
  if (!isAmbiguous && lowerReceiver.length >= 3) {
@@ -11978,6 +12120,8 @@ function receiverMightBeClass(receiver, className) {
11978
12120
  ps: ["PreparedStatement"],
11979
12121
  rs: ["ResultSet"],
11980
12122
  template: ["JdbcTemplate"],
12123
+ cur: ["Cursor"],
12124
+ cursor: ["Cursor"],
11981
12125
  writer: ["PrintWriter"],
11982
12126
  out: ["PrintWriter", "OutputStream"],
11983
12127
  reader: ["BufferedReader"],
@@ -13202,9 +13346,11 @@ class ConstantPropagator {
13202
13346
  return findAssignments(methodBody);
13203
13347
  }
13204
13348
  collectClassFields(root) {
13205
- const traverse = (n, inClass, inMethod) => {
13349
+ const stack = [root];
13350
+ while (stack.length > 0) {
13351
+ const n = stack.pop();
13206
13352
  if (!n)
13207
- return;
13353
+ continue;
13208
13354
  if (n.type === "class_body") {
13209
13355
  for (const child of n.children) {
13210
13356
  if (child.type === "field_declaration") {
@@ -13218,34 +13364,30 @@ class ConstantPropagator {
13218
13364
  }
13219
13365
  }
13220
13366
  }
13221
- if (child.type === "method_declaration" || child.type === "constructor_declaration") {
13222
- traverse(child, true, true);
13223
- } else {
13224
- traverse(child, true, false);
13225
- }
13367
+ stack.push(child);
13226
13368
  }
13227
- return;
13369
+ continue;
13228
13370
  }
13229
13371
  for (const child of n.children) {
13230
- traverse(child, inClass, inMethod);
13372
+ stack.push(child);
13231
13373
  }
13232
- };
13233
- traverse(root, false, false);
13374
+ }
13234
13375
  }
13235
13376
  findAllMethods(node) {
13236
13377
  const methods = [];
13237
- const traverse = (n) => {
13378
+ const stack = [node];
13379
+ while (stack.length > 0) {
13380
+ const n = stack.pop();
13238
13381
  if (!n)
13239
- return;
13382
+ continue;
13240
13383
  if (n.type === "method_declaration" || n.type === "function_declaration") {
13241
13384
  methods.push(n);
13242
13385
  }
13243
13386
  for (const child of n.children) {
13244
13387
  if (child)
13245
- traverse(child);
13388
+ stack.push(child);
13246
13389
  }
13247
- };
13248
- traverse(node);
13390
+ }
13249
13391
  return methods;
13250
13392
  }
13251
13393
  getMethodName(method) {
@@ -13290,9 +13432,20 @@ class ConstantPropagator {
13290
13432
  }
13291
13433
  }
13292
13434
  visit(node) {
13435
+ const stack = [node];
13436
+ while (stack.length > 0) {
13437
+ const current = stack.pop();
13438
+ if (this.visitOne(current))
13439
+ continue;
13440
+ for (let i2 = current.children.length - 1;i2 >= 0; i2--) {
13441
+ stack.push(current.children[i2]);
13442
+ }
13443
+ }
13444
+ }
13445
+ visitOne(node) {
13293
13446
  const line = getNodeLine(node);
13294
13447
  if (this.unreachableLines.has(line)) {
13295
- return;
13448
+ return true;
13296
13449
  }
13297
13450
  if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
13298
13451
  this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
@@ -13301,42 +13454,40 @@ class ConstantPropagator {
13301
13454
  case "method_declaration":
13302
13455
  case "constructor_declaration":
13303
13456
  this.handleMethodDeclaration(node);
13304
- return;
13457
+ return true;
13305
13458
  case "local_variable_declaration":
13306
13459
  this.handleVariableDeclaration(node);
13307
- break;
13460
+ return false;
13308
13461
  case "assignment_expression":
13309
13462
  this.handleAssignment(node);
13310
- break;
13463
+ return false;
13311
13464
  case "update_expression":
13312
13465
  this.handleUpdateExpression(node);
13313
- break;
13466
+ return false;
13314
13467
  case "if_statement":
13315
13468
  this.handleIfStatement(node);
13316
- return;
13469
+ return true;
13317
13470
  case "switch_expression":
13318
13471
  case "switch_statement":
13319
13472
  this.handleSwitch(node);
13320
- return;
13473
+ return true;
13321
13474
  case "ternary_expression":
13322
13475
  this.handleTernary(node);
13323
- break;
13476
+ return false;
13324
13477
  case "expression_statement":
13325
13478
  this.handleExpressionStatement(node);
13326
- break;
13479
+ return false;
13327
13480
  case "for_statement":
13328
13481
  case "enhanced_for_statement":
13329
13482
  case "while_statement":
13330
13483
  case "do_statement":
13331
13484
  this.handleLoopStatement(node);
13332
- return;
13485
+ return true;
13333
13486
  case "synchronized_statement":
13334
13487
  this.handleSynchronizedStatement(node);
13335
- return;
13488
+ return true;
13336
13489
  default:
13337
- for (const child of node.children) {
13338
- this.visit(child);
13339
- }
13490
+ return false;
13340
13491
  }
13341
13492
  }
13342
13493
  handleMethodDeclaration(node) {
@@ -14038,6 +14189,21 @@ class ConstantPropagator {
14038
14189
  return null;
14039
14190
  }
14040
14191
  isTaintedExpression(node) {
14192
+ const stack = [node];
14193
+ while (stack.length > 0) {
14194
+ const current = stack.pop();
14195
+ const result = this.isTaintedExpressionStep(current);
14196
+ if (result === true)
14197
+ return true;
14198
+ if (result === false)
14199
+ continue;
14200
+ for (let i2 = current.children.length - 1;i2 >= 0; i2--) {
14201
+ stack.push(current.children[i2]);
14202
+ }
14203
+ }
14204
+ return false;
14205
+ }
14206
+ isTaintedExpressionStep(node) {
14041
14207
  const text = getNodeText2(node, this.source);
14042
14208
  if (node.type === "method_invocation") {
14043
14209
  const nameNode = node.childForFieldName("name");
@@ -14290,12 +14456,7 @@ class ConstantPropagator {
14290
14456
  }
14291
14457
  return isTainted;
14292
14458
  }
14293
- for (const child of node.children) {
14294
- if (this.isTaintedExpression(child)) {
14295
- return true;
14296
- }
14297
- }
14298
- return false;
14459
+ return;
14299
14460
  }
14300
14461
  checkCollectionTaint(node) {
14301
14462
  const objectNode = node.childForFieldName("object");
@@ -14448,7 +14609,7 @@ function isFalsePositive(result, sinkLine, taintedVar) {
14448
14609
  if (varValue && varValue.type !== "unknown" && !result.tainted.has(taintedVar)) {
14449
14610
  return { isFalsePositive: true, reason: `variable_is_constant: ${varValue.value}` };
14450
14611
  }
14451
- if (result.symbols.size > 0 && !result.tainted.has(taintedVar)) {
14612
+ if (result.symbols.has(taintedVar) && !result.tainted.has(taintedVar)) {
14452
14613
  return { isFalsePositive: true, reason: "variable_not_tainted" };
14453
14614
  }
14454
14615
  return { isFalsePositive: false, reason: null };
@@ -14597,19 +14758,18 @@ class BaseLanguagePlugin {
14597
14758
  }
14598
14759
  findNodes(root, type) {
14599
14760
  const nodes = [];
14600
- const cursor = root.walk();
14601
- const visit = () => {
14602
- if (cursor.nodeType === type) {
14603
- nodes.push(cursor.currentNode);
14761
+ const stack = [root];
14762
+ while (stack.length > 0) {
14763
+ const node = stack.pop();
14764
+ if (node.type === type) {
14765
+ nodes.push(node);
14604
14766
  }
14605
- if (cursor.gotoFirstChild()) {
14606
- do {
14607
- visit();
14608
- } while (cursor.gotoNextSibling());
14609
- cursor.gotoParent();
14767
+ for (let i2 = node.childCount - 1;i2 >= 0; i2--) {
14768
+ const child = node.child(i2);
14769
+ if (child)
14770
+ stack.push(child);
14610
14771
  }
14611
- };
14612
- visit();
14772
+ }
14613
14773
  return nodes;
14614
14774
  }
14615
14775
  findChildByType(node, type) {
@@ -14930,17 +15090,18 @@ class JavaPlugin extends BaseLanguagePlugin {
14930
15090
  }
14931
15091
  }
14932
15092
  };
14933
- const walk = (node) => {
15093
+ const stack = [tree.rootNode];
15094
+ while (stack.length > 0) {
15095
+ const node = stack.pop();
14934
15096
  if (node.type === "field_declaration" || node.type === "local_variable_declaration") {
14935
15097
  collectDecl(node);
14936
15098
  }
14937
15099
  for (let i2 = 0;i2 < node.childCount; i2++) {
14938
15100
  const child = node.child(i2);
14939
15101
  if (child)
14940
- walk(child);
15102
+ stack.push(child);
14941
15103
  }
14942
- };
14943
- walk(tree.rootNode);
15104
+ }
14944
15105
  this._typeMapCache.set(tree, map);
14945
15106
  return map;
14946
15107
  }
@@ -19455,16 +19616,19 @@ function extractHtmlContent(rootNode) {
19455
19616
  return { scriptBlocks, eventHandlers };
19456
19617
  }
19457
19618
  function walkNode(node, scriptBlocks, eventHandlers) {
19458
- if (node.type === "script_element") {
19459
- extractScriptBlock(node, scriptBlocks);
19460
- }
19461
- if (node.type === "element" || node.type === "self_closing_tag") {
19462
- extractEventHandlers(node, eventHandlers);
19463
- }
19464
- for (let i2 = 0;i2 < node.childCount; i2++) {
19465
- const child = node.child(i2);
19466
- if (child) {
19467
- walkNode(child, scriptBlocks, eventHandlers);
19619
+ const stack = [node];
19620
+ while (stack.length > 0) {
19621
+ const current = stack.pop();
19622
+ if (current.type === "script_element") {
19623
+ extractScriptBlock(current, scriptBlocks);
19624
+ }
19625
+ if (current.type === "element" || current.type === "self_closing_tag") {
19626
+ extractEventHandlers(current, eventHandlers);
19627
+ }
19628
+ for (let i2 = current.childCount - 1;i2 >= 0; i2--) {
19629
+ const child = current.child(i2);
19630
+ if (child)
19631
+ stack.push(child);
19468
19632
  }
19469
19633
  }
19470
19634
  }
@@ -19569,13 +19733,16 @@ function runHtmlAttributeSecurityChecks(rootNode, filePath) {
19569
19733
  return findings;
19570
19734
  }
19571
19735
  function walkForSecurityChecks(node, filePath, findings) {
19572
- if (node.type === "element" || node.type === "self_closing_tag" || node.type === "script_element" || node.type === "style_element") {
19573
- checkElement(node, filePath, findings);
19574
- }
19575
- for (let i2 = 0;i2 < node.childCount; i2++) {
19576
- const child = node.child(i2);
19577
- if (child) {
19578
- walkForSecurityChecks(child, filePath, findings);
19736
+ const stack = [node];
19737
+ while (stack.length > 0) {
19738
+ const current = stack.pop();
19739
+ if (current.type === "element" || current.type === "self_closing_tag" || current.type === "script_element" || current.type === "style_element") {
19740
+ checkElement(current, filePath, findings);
19741
+ }
19742
+ for (let i2 = current.childCount - 1;i2 >= 0; i2--) {
19743
+ const child = current.child(i2);
19744
+ if (child)
19745
+ stack.push(child);
19579
19746
  }
19580
19747
  }
19581
19748
  }
@@ -20514,6 +20681,40 @@ function buildJavaScriptTaintedVars(sourceCode, language) {
20514
20681
  }
20515
20682
  return tainted;
20516
20683
  }
20684
+ function buildRustTaintedVars(sourceCode, seedVars) {
20685
+ const derived = new Map;
20686
+ const knownTainted = new Set(seedVars);
20687
+ const lines = sourceCode.split(`
20688
+ `);
20689
+ let changed = true;
20690
+ while (changed) {
20691
+ changed = false;
20692
+ for (let i2 = 0;i2 < lines.length; i2++) {
20693
+ const line = lines[i2];
20694
+ const trimmed = line.trimStart();
20695
+ if (trimmed.startsWith("//"))
20696
+ continue;
20697
+ const letMatch = line.match(/^\s*let\s+(?:mut\s+)?([A-Za-z_]\w*)\s*(?::\s*[^=]+)?=\s*(.+?)(?:;|$)/);
20698
+ const assignMatch = !letMatch ? line.match(/^\s*([A-Za-z_]\w*)\s*=\s*(.+?)(?:;|$)/) : null;
20699
+ const m = letMatch ?? assignMatch;
20700
+ if (!m)
20701
+ continue;
20702
+ const lhs = m[1];
20703
+ const rhs = m[2];
20704
+ if (lhs === "if" || lhs === "while" || lhs === "for" || lhs === "match" || lhs === "return")
20705
+ continue;
20706
+ if (knownTainted.has(lhs))
20707
+ continue;
20708
+ const ref = [...knownTainted].some((v) => new RegExp(`\\b${v}\\b`).test(rhs));
20709
+ if (ref) {
20710
+ derived.set(lhs, i2 + 1);
20711
+ knownTainted.add(lhs);
20712
+ changed = true;
20713
+ }
20714
+ }
20715
+ }
20716
+ return derived;
20717
+ }
20517
20718
  var BASH_POSITIONAL_PARAMS = new Set(["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*"]);
20518
20719
  var BASH_UNTRUSTED_ENV_PATTERNS = [
20519
20720
  /^USER_INPUT$/i,
@@ -20936,7 +21137,23 @@ function evaluateSimpleExpression(expr, symbols) {
20936
21137
  }
20937
21138
  function isStringLiteralExpression(expr) {
20938
21139
  const trimmed = expr.trim();
20939
- return trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'");
21140
+ if (trimmed.length < 2)
21141
+ return false;
21142
+ const quote = trimmed[0];
21143
+ if (quote !== '"' && quote !== "'")
21144
+ return false;
21145
+ let i2 = 1;
21146
+ while (i2 < trimmed.length) {
21147
+ const c = trimmed[i2];
21148
+ if (c === "\\") {
21149
+ i2 += 2;
21150
+ continue;
21151
+ }
21152
+ if (c === quote)
21153
+ return i2 === trimmed.length - 1;
21154
+ i2++;
21155
+ }
21156
+ return false;
20940
21157
  }
20941
21158
  function filterCleanArraySinks(sinks, calls, taintedArrayElements, symbols) {
20942
21159
  const callsByLine = new Map;
@@ -21367,7 +21584,7 @@ class TaintPropagationPass {
21367
21584
  flows.push(f);
21368
21585
  }
21369
21586
  }
21370
- const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, constProp.unreachableLines, ctx.code, ctx.language) ?? [];
21587
+ const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, ctx.code, ctx.language) ?? [];
21371
21588
  for (const f of exprScanFlows) {
21372
21589
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type))
21373
21590
  continue;
@@ -21585,13 +21802,70 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
21585
21802
  }
21586
21803
  return flows;
21587
21804
  }
21588
- function detectExpressionScanFlows(calls, sources, sinks, unreachableLines, code, language) {
21805
+ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, code, language) {
21589
21806
  const flows = [];
21590
21807
  const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
21591
21808
  if (sourcesWithVar.length === 0)
21592
21809
  return flows;
21810
+ const aliasSanitizedFor = new Map;
21593
21811
  if (language === "python" && typeof code === "string") {
21594
21812
  const derived = buildPythonTaintedVars(code);
21813
+ if (derived.size > 0) {
21814
+ let anchor = sourcesWithVar[0];
21815
+ for (const s of sourcesWithVar) {
21816
+ if (s.line < anchor.line)
21817
+ anchor = s;
21818
+ }
21819
+ const existingVars = new Set(sourcesWithVar.map((s) => s.variable));
21820
+ for (const [varName] of derived) {
21821
+ if (!varName || existingVars.has(varName))
21822
+ continue;
21823
+ sourcesWithVar.push({
21824
+ ...anchor,
21825
+ variable: varName
21826
+ });
21827
+ existingVars.add(varName);
21828
+ }
21829
+ if (sanitizers && sanitizers.length > 0) {
21830
+ const sanitizersByLine = new Map;
21831
+ for (const s of sanitizers) {
21832
+ const arr = sanitizersByLine.get(s.line) ?? [];
21833
+ arr.push(s);
21834
+ sanitizersByLine.set(s.line, arr);
21835
+ }
21836
+ const codeLines = code.split(`
21837
+ `);
21838
+ for (const [varName, originLine] of derived) {
21839
+ const lineSans = sanitizersByLine.get(originLine);
21840
+ if (!lineSans || lineSans.length === 0)
21841
+ continue;
21842
+ const lineText = codeLines[originLine - 1] ?? "";
21843
+ const rhsMatch = lineText.match(/^\s*\w+\s*=\s*(.+)$/);
21844
+ if (!rhsMatch)
21845
+ continue;
21846
+ const rhs = rhsMatch[1];
21847
+ for (const san of lineSans) {
21848
+ const sanMatch = san.method.match(/^(?:(\w+)\.)?(\w+)\(\)$/);
21849
+ if (!sanMatch)
21850
+ continue;
21851
+ const sanName = sanMatch[1] ? `${sanMatch[1]}.${sanMatch[2]}` : sanMatch[2];
21852
+ if (!rhs.includes(`${sanName}(`))
21853
+ continue;
21854
+ let set = aliasSanitizedFor.get(varName);
21855
+ if (!set) {
21856
+ set = new Set;
21857
+ aliasSanitizedFor.set(varName, set);
21858
+ }
21859
+ for (const t of san.sanitizes)
21860
+ set.add(t);
21861
+ }
21862
+ }
21863
+ }
21864
+ }
21865
+ }
21866
+ if (language === "rust" && typeof code === "string") {
21867
+ const seedVars = new Set(sourcesWithVar.map((s) => s.variable));
21868
+ const derived = buildRustTaintedVars(code, seedVars);
21595
21869
  if (derived.size > 0) {
21596
21870
  let anchor = sourcesWithVar[0];
21597
21871
  for (const s of sourcesWithVar) {
@@ -21643,6 +21917,9 @@ function detectExpressionScanFlows(calls, sources, sinks, unreachableLines, code
21643
21917
  continue;
21644
21918
  if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
21645
21919
  continue;
21920
+ if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
21921
+ break;
21922
+ }
21646
21923
  flows.push({
21647
21924
  source_line: source.line,
21648
21925
  sink_line: sink.line,
@@ -22216,6 +22493,9 @@ class InterproceduralPass {
22216
22493
  }
22217
22494
  }
22218
22495
  }
22496
+ if (additionalSinks.length > 0) {
22497
+ attachSourceLineCode([], additionalSinks, ctx.code);
22498
+ }
22219
22499
  return { additionalSinks, additionalFlows, interprocedural };
22220
22500
  }
22221
22501
  }
@@ -26755,6 +27035,77 @@ function isPotentialPojo(type) {
26755
27035
  return first >= 65 && first <= 90;
26756
27036
  }
26757
27037
 
27038
+ // ../circle-ir/dist/analysis/passes/insecure-cookie-pass.js
27039
+ var COOKIE_RESPONSE_RECEIVERS = new Set([
27040
+ "res",
27041
+ "response",
27042
+ "reply"
27043
+ ]);
27044
+ var SECURE_TRUE_RE = /\bsecure\s*:\s*true\b/;
27045
+ var HTTPONLY_TRUE_RE = /\bhttpOnly\s*:\s*true\b/i;
27046
+
27047
+ class InsecureCookiePass {
27048
+ name = "insecure-cookie";
27049
+ category = "security";
27050
+ run(ctx) {
27051
+ const { graph, language } = ctx;
27052
+ if (language !== "javascript" && language !== "typescript") {
27053
+ return { insecureCookies: [] };
27054
+ }
27055
+ const file = graph.ir.meta.file;
27056
+ const insecureCookies = [];
27057
+ for (const call of graph.ir.calls) {
27058
+ if (call.method_name !== "cookie")
27059
+ continue;
27060
+ const receiver = call.receiver ?? "";
27061
+ if (!COOKIE_RESPONSE_RECEIVERS.has(receiver))
27062
+ continue;
27063
+ if (call.arguments.length < 2)
27064
+ continue;
27065
+ const opts = call.arguments.find((a) => a.position === 2);
27066
+ const optsExpr = (opts?.expression ?? "").trim();
27067
+ const optionsPresent = optsExpr.length > 0;
27068
+ const missingSecure = !SECURE_TRUE_RE.test(optsExpr);
27069
+ const missingHttpOnly = !HTTPONLY_TRUE_RE.test(optsExpr);
27070
+ if (!missingSecure && !missingHttpOnly)
27071
+ continue;
27072
+ const line = call.location.line;
27073
+ insecureCookies.push({
27074
+ line,
27075
+ receiver,
27076
+ missingSecure,
27077
+ missingHttpOnly,
27078
+ optionsPresent
27079
+ });
27080
+ const missing = [];
27081
+ if (missingSecure)
27082
+ missing.push("`secure: true`");
27083
+ if (missingHttpOnly)
27084
+ missing.push("`httpOnly: true`");
27085
+ ctx.addFinding({
27086
+ id: `${this.name}-${file}-${line}`,
27087
+ pass: this.name,
27088
+ category: this.category,
27089
+ rule_id: this.name,
27090
+ cwe: "CWE-614",
27091
+ severity: "medium",
27092
+ level: "warning",
27093
+ message: `Cookie set without ${missing.join(" and ")} — vulnerable to ` + `cleartext transmission (CWE-614) and client-side JS access ` + `(CWE-1004).`,
27094
+ file,
27095
+ line,
27096
+ fix: 'Pass `{ secure: true, httpOnly: true, sameSite: "lax" }` as the ' + "third argument to `res.cookie()`.",
27097
+ evidence: {
27098
+ receiver,
27099
+ options_present: optionsPresent,
27100
+ missing_secure: missingSecure,
27101
+ missing_http_only: missingHttpOnly
27102
+ }
27103
+ });
27104
+ }
27105
+ return { insecureCookies };
27106
+ }
27107
+ }
27108
+
26758
27109
  // ../circle-ir/dist/graph/import-graph.js
26759
27110
  function dirname(filePath) {
26760
27111
  const idx = filePath.lastIndexOf("/");
@@ -27865,6 +28216,8 @@ async function analyze(code, filePath, language, options = {}) {
27865
28216
  pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
27866
28217
  if (!disabledPasses.has("spring4shell"))
27867
28218
  pipeline.add(new Spring4ShellPass);
28219
+ if (!disabledPasses.has("insecure-cookie"))
28220
+ pipeline.add(new InsecureCookiePass);
27868
28221
  const { results, findings } = pipeline.run(graph, code, language, config);
27869
28222
  const sinkFilter = results.get("sink-filter");
27870
28223
  const interProc = results.get("interprocedural");
@@ -28058,7 +28411,7 @@ var colors = {
28058
28411
  };
28059
28412
 
28060
28413
  // src/version.ts
28061
- var version = "3.47.0";
28414
+ var version = "3.49.0";
28062
28415
 
28063
28416
  // src/formatters.ts
28064
28417
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.47.0",
3
+ "version": "3.49.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.47.0"
68
+ "circle-ir": "^3.49.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",