cognium-dev 3.48.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 +353 -26
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -4035,15 +4035,54 @@ function extractJSClassInfo(node) {
4035
4035
  end_line: node.endPosition.row + 1
4036
4036
  };
4037
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
+ }
4038
4063
  function extractJSMethods(body2) {
4039
4064
  const methods = [];
4065
+ let pendingDecorators = [];
4040
4066
  for (let i2 = 0;i2 < body2.childCount; i2++) {
4041
4067
  const child = body2.child(i2);
4042
4068
  if (!child)
4043
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;
4044
4078
  if (child.type === "method_definition") {
4045
- methods.push(extractJSMethodInfo(child));
4079
+ const m = extractJSMethodInfo(child);
4080
+ if (pendingDecorators.length > 0) {
4081
+ m.annotations = pendingDecorators;
4082
+ }
4083
+ methods.push(m);
4046
4084
  }
4085
+ pendingDecorators = [];
4047
4086
  }
4048
4087
  return methods;
4049
4088
  }
@@ -4214,10 +4253,19 @@ function extractJSParameters(params) {
4214
4253
  if (typeNode) {
4215
4254
  paramType = getNodeText(typeNode).replace(/^:\s*/, "");
4216
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
+ }
4217
4265
  parameters.push({
4218
4266
  name: paramName,
4219
4267
  type: paramType,
4220
- annotations: [],
4268
+ annotations: decorators,
4221
4269
  line: child.startPosition.row + 1
4222
4270
  });
4223
4271
  }
@@ -10798,6 +10846,18 @@ var DEFAULT_SINKS = [
10798
10846
  { method: "getExecutionPreamble", class: "Shell", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [] },
10799
10847
  { method: "setQuotedArgumentsEnabled", class: "Shell", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
10800
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"] },
10801
10861
  { method: "exec", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10802
10862
  { method: "execSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10803
10863
  { method: "spawn", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -10838,6 +10898,29 @@ var DEFAULT_SINKS = [
10838
10898
  { method: "updateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
10839
10899
  { method: "deleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0] },
10840
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"] },
10841
10924
  { method: "get", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10842
10925
  { method: "post", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10843
10926
  { method: "request", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -10854,6 +10937,13 @@ var DEFAULT_SINKS = [
10854
10937
  { method: "get", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10855
10938
  { method: "post", class: "superagent", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
10856
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"] },
10857
10947
  { method: "system", class: "os", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10858
10948
  { method: "popen", class: "os", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
10859
10949
  { method: "run", class: "subprocess", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
@@ -10880,7 +10970,7 @@ var DEFAULT_SINKS = [
10880
10970
  { method: "rmdir", class: "os", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10881
10971
  { method: "rmtree", class: "shutil", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
10882
10972
  { method: "send_file", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["python"] },
10883
- { 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"] },
10884
10974
  { method: "Markup", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
10885
10975
  { method: "mark_safe", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0], languages: ["python"] },
10886
10976
  { method: "get", class: "requests", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
@@ -11142,6 +11232,10 @@ var DEFAULT_SANITIZERS = [
11142
11232
  { method: "secure_filename", class: "werkzeug.utils", removes: ["path_traversal"] },
11143
11233
  { method: "basename", class: "os.path", removes: ["path_traversal"] },
11144
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"] },
11145
11239
  { method: "int", removes: ["sql_injection", "command_injection", "xss"] },
11146
11240
  { method: "float", removes: ["sql_injection", "command_injection"] },
11147
11241
  { method: "query!", removes: ["sql_injection"] },
@@ -11270,7 +11364,7 @@ var PYTHON_TAINTED_PATTERNS = [
11270
11364
  function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy, language, code) {
11271
11365
  const sourceLines = code !== undefined ? code.split(`
11272
11366
  `) : undefined;
11273
- const sources = findSources(calls, types, config.sources, sourceLines);
11367
+ const sources = findSources(calls, types, config.sources, sourceLines, language);
11274
11368
  const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
11275
11369
  const sanitizers = findSanitizers(calls, types, config.sanitizers);
11276
11370
  return { sources, sinks, sanitizers };
@@ -11289,7 +11383,7 @@ function attachSourceLineCode(sources, sinks, code) {
11289
11383
  }
11290
11384
  }
11291
11385
  }
11292
- function findSources(calls, types, patterns, sourceLines) {
11386
+ function findSources(calls, types, patterns, sourceLines, language) {
11293
11387
  const sources = [];
11294
11388
  for (const call of calls) {
11295
11389
  for (const pattern of patterns) {
@@ -11344,23 +11438,31 @@ function findSources(calls, types, patterns, sourceLines) {
11344
11438
  }
11345
11439
  }
11346
11440
  }
11347
- 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)(?:<|$)/;
11348
11442
  for (const type of types) {
11349
11443
  for (const method of type.methods) {
11350
11444
  for (const param of method.parameters) {
11351
- if (param.type && RUST_EXTRACTOR_TYPES.test(param.type)) {
11352
- const paramLine = param.line ?? method.start_line;
11353
- const alreadyExists = sources.some((s) => s.line === paramLine && s.type === "http_body");
11354
- if (!alreadyExists) {
11355
- sources.push({
11356
- type: "http_body",
11357
- location: `${param.type} ${param.name} in ${method.name}`,
11358
- severity: "high",
11359
- line: paramLine,
11360
- confidence: 1
11361
- });
11362
- }
11363
- }
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
+ });
11364
11466
  }
11365
11467
  }
11366
11468
  }
@@ -11442,6 +11544,17 @@ function findSources(calls, types, patterns, sourceLines) {
11442
11544
  s.code = sourceLines[s.line - 1]?.trim();
11443
11545
  }
11444
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
+ }
11445
11558
  return result;
11446
11559
  }
11447
11560
  function isInterproceduralTaintableType(typeName) {
@@ -11538,6 +11651,26 @@ function isParameterizedQueryCall(call, pattern) {
11538
11651
  }
11539
11652
  return false;
11540
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
+ }
11541
11674
  var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
11542
11675
  function argIsClassLiteral(call, position) {
11543
11676
  const arg = call.arguments.find((a) => a.position === position);
@@ -11556,6 +11689,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
11556
11689
  if (isParameterizedQueryCall(call, pattern)) {
11557
11690
  continue;
11558
11691
  }
11692
+ if (isSafePythonSubprocessCall(call, pattern, language)) {
11693
+ continue;
11694
+ }
11559
11695
  if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
11560
11696
  continue;
11561
11697
  }
@@ -11950,7 +12086,8 @@ function receiverMightBeClass(receiver, className) {
11950
12086
  "controller",
11951
12087
  "task",
11952
12088
  "thread",
11953
- "job"
12089
+ "job",
12090
+ "cur"
11954
12091
  ]);
11955
12092
  const isAmbiguous = ambiguousIdentifiers.has(lowerReceiver);
11956
12093
  if (!isAmbiguous && lowerReceiver.length >= 3 && lowerClass.includes(lowerReceiver)) {
@@ -11960,7 +12097,9 @@ function receiverMightBeClass(receiver, className) {
11960
12097
  }
11961
12098
  if (!isAmbiguous && lowerReceiver.length >= 2) {
11962
12099
  if (lowerClass.startsWith(lowerReceiver) || lowerClass.endsWith(lowerReceiver)) {
11963
- return true;
12100
+ if (lowerReceiver.length / lowerClass.length >= 0.4) {
12101
+ return true;
12102
+ }
11964
12103
  }
11965
12104
  }
11966
12105
  if (!isAmbiguous && lowerReceiver.length >= 3) {
@@ -11981,6 +12120,8 @@ function receiverMightBeClass(receiver, className) {
11981
12120
  ps: ["PreparedStatement"],
11982
12121
  rs: ["ResultSet"],
11983
12122
  template: ["JdbcTemplate"],
12123
+ cur: ["Cursor"],
12124
+ cursor: ["Cursor"],
11984
12125
  writer: ["PrintWriter"],
11985
12126
  out: ["PrintWriter", "OutputStream"],
11986
12127
  reader: ["BufferedReader"],
@@ -14468,7 +14609,7 @@ function isFalsePositive(result, sinkLine, taintedVar) {
14468
14609
  if (varValue && varValue.type !== "unknown" && !result.tainted.has(taintedVar)) {
14469
14610
  return { isFalsePositive: true, reason: `variable_is_constant: ${varValue.value}` };
14470
14611
  }
14471
- if (result.symbols.size > 0 && !result.tainted.has(taintedVar)) {
14612
+ if (result.symbols.has(taintedVar) && !result.tainted.has(taintedVar)) {
14472
14613
  return { isFalsePositive: true, reason: "variable_not_tainted" };
14473
14614
  }
14474
14615
  return { isFalsePositive: false, reason: null };
@@ -20540,6 +20681,40 @@ function buildJavaScriptTaintedVars(sourceCode, language) {
20540
20681
  }
20541
20682
  return tainted;
20542
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
+ }
20543
20718
  var BASH_POSITIONAL_PARAMS = new Set(["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*"]);
20544
20719
  var BASH_UNTRUSTED_ENV_PATTERNS = [
20545
20720
  /^USER_INPUT$/i,
@@ -20962,7 +21137,23 @@ function evaluateSimpleExpression(expr, symbols) {
20962
21137
  }
20963
21138
  function isStringLiteralExpression(expr) {
20964
21139
  const trimmed = expr.trim();
20965
- 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;
20966
21157
  }
20967
21158
  function filterCleanArraySinks(sinks, calls, taintedArrayElements, symbols) {
20968
21159
  const callsByLine = new Map;
@@ -21393,7 +21584,7 @@ class TaintPropagationPass {
21393
21584
  flows.push(f);
21394
21585
  }
21395
21586
  }
21396
- 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) ?? [];
21397
21588
  for (const f of exprScanFlows) {
21398
21589
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type))
21399
21590
  continue;
@@ -21611,13 +21802,70 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
21611
21802
  }
21612
21803
  return flows;
21613
21804
  }
21614
- function detectExpressionScanFlows(calls, sources, sinks, unreachableLines, code, language) {
21805
+ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, code, language) {
21615
21806
  const flows = [];
21616
21807
  const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
21617
21808
  if (sourcesWithVar.length === 0)
21618
21809
  return flows;
21810
+ const aliasSanitizedFor = new Map;
21619
21811
  if (language === "python" && typeof code === "string") {
21620
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);
21621
21869
  if (derived.size > 0) {
21622
21870
  let anchor = sourcesWithVar[0];
21623
21871
  for (const s of sourcesWithVar) {
@@ -21669,6 +21917,9 @@ function detectExpressionScanFlows(calls, sources, sinks, unreachableLines, code
21669
21917
  continue;
21670
21918
  if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
21671
21919
  continue;
21920
+ if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
21921
+ break;
21922
+ }
21672
21923
  flows.push({
21673
21924
  source_line: source.line,
21674
21925
  sink_line: sink.line,
@@ -22242,6 +22493,9 @@ class InterproceduralPass {
22242
22493
  }
22243
22494
  }
22244
22495
  }
22496
+ if (additionalSinks.length > 0) {
22497
+ attachSourceLineCode([], additionalSinks, ctx.code);
22498
+ }
22245
22499
  return { additionalSinks, additionalFlows, interprocedural };
22246
22500
  }
22247
22501
  }
@@ -26781,6 +27035,77 @@ function isPotentialPojo(type) {
26781
27035
  return first >= 65 && first <= 90;
26782
27036
  }
26783
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
+
26784
27109
  // ../circle-ir/dist/graph/import-graph.js
26785
27110
  function dirname(filePath) {
26786
27111
  const idx = filePath.lastIndexOf("/");
@@ -27891,6 +28216,8 @@ async function analyze(code, filePath, language, options = {}) {
27891
28216
  pipeline.add(new SecurityHeadersPass(passOpts.securityHeaders));
27892
28217
  if (!disabledPasses.has("spring4shell"))
27893
28218
  pipeline.add(new Spring4ShellPass);
28219
+ if (!disabledPasses.has("insecure-cookie"))
28220
+ pipeline.add(new InsecureCookiePass);
27894
28221
  const { results, findings } = pipeline.run(graph, code, language, config);
27895
28222
  const sinkFilter = results.get("sink-filter");
27896
28223
  const interProc = results.get("interprocedural");
@@ -28084,7 +28411,7 @@ var colors = {
28084
28411
  };
28085
28412
 
28086
28413
  // src/version.ts
28087
- var version = "3.48.0";
28414
+ var version = "3.49.0";
28088
28415
 
28089
28416
  // src/formatters.ts
28090
28417
  var SINK_SEVERITY = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.48.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.48.0"
68
+ "circle-ir": "^3.49.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",