cognium-dev 3.57.0 → 3.58.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 +463 -45
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -10233,10 +10233,10 @@ var DEFAULT_SOURCES = [
10233
10233
  { method: "cookie", class: "HttpRequest", type: "http_cookie", severity: "high", return_tainted: true },
10234
10234
  { method: "param", class: "Request", type: "http_param", severity: "high", return_tainted: true },
10235
10235
  { method: "cookies", class: "Request", type: "http_cookie", severity: "high", return_tainted: true },
10236
- { method: "Json", type: "http_body", severity: "high", return_tainted: true },
10237
- { method: "Query", type: "http_param", severity: "high", return_tainted: true },
10238
- { method: "Path", type: "http_path", severity: "high", return_tainted: true },
10239
- { method: "Form", type: "http_param", severity: "high", return_tainted: true },
10236
+ { method: "Json", type: "http_body", severity: "high", return_tainted: true, languages: ["rust"] },
10237
+ { method: "Query", type: "http_param", severity: "high", return_tainted: true, languages: ["rust"] },
10238
+ { method: "Path", type: "http_path", severity: "high", return_tainted: true, languages: ["rust"] },
10239
+ { method: "Form", type: "http_param", severity: "high", return_tainted: true, languages: ["rust"] },
10240
10240
  { method: "var", class: "env", type: "env_input", severity: "medium", return_tainted: true },
10241
10241
  { method: "var_os", class: "env", type: "env_input", severity: "medium", return_tainted: true },
10242
10242
  { method: "args", class: "env", type: "env_input", severity: "medium", return_tainted: true },
@@ -10389,10 +10389,10 @@ var DEFAULT_SINKS = [
10389
10389
  { method: "UrlResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10390
10390
  { method: "PathResource", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10391
10391
  { method: "forFile", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10392
- { method: "resolve", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10393
- { method: "resolve", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10394
- { method: "resolveSibling", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10395
- { method: "relativize", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
10392
+ { method: "resolve", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
10393
+ { method: "resolve", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
10394
+ { method: "resolveSibling", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0], languages: ["java"] },
10395
+ { method: "relativize", class: "Path", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0], languages: ["java"] },
10396
10396
  { method: "staticFiles", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10397
10397
  { method: "setRoot", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
10398
10398
  { method: "setWebRoot", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
@@ -11208,6 +11208,11 @@ var DEFAULT_SANITIZERS = [
11208
11208
  { method: "toRealPath", class: "Path", removes: ["path_traversal"] },
11209
11209
  { method: "file_name", removes: ["path_traversal"] },
11210
11210
  { method: "canonicalize", removes: ["path_traversal"] },
11211
+ { method: "Base", class: "filepath", removes: ["path_traversal"] },
11212
+ { method: "Base", class: "path", removes: ["path_traversal"] },
11213
+ { method: "Clean", class: "filepath", removes: ["path_traversal"] },
11214
+ { method: "Clean", class: "path", removes: ["path_traversal"] },
11215
+ { method: "EvalSymlinks", class: "filepath", removes: ["path_traversal"] },
11211
11216
  { method: "replace", removes: ["log_injection"] },
11212
11217
  { method: "encodeForLDAP", removes: ["ldap_injection"] },
11213
11218
  { method: "encodeForDN", removes: ["ldap_injection"] },
@@ -11268,6 +11273,7 @@ var DEFAULT_SANITIZERS = [
11268
11273
  { method: "abspath", class: "os.path", removes: ["path_traversal"] },
11269
11274
  { method: "realpath", class: "path", removes: ["path_traversal"] },
11270
11275
  { method: "abspath", class: "path", removes: ["path_traversal"] },
11276
+ { method: "resolve", class: "Path", removes: ["path_traversal"] },
11271
11277
  { method: "int", removes: ["sql_injection", "command_injection", "xss"] },
11272
11278
  { method: "float", removes: ["sql_injection", "command_injection"] },
11273
11279
  { method: "query!", removes: ["sql_injection"] },
@@ -11287,7 +11293,25 @@ var DEFAULT_SANITIZERS = [
11287
11293
  { method: "encode_text", class: "html_escape", removes: ["xss"] },
11288
11294
  { method: "encode_attribute", class: "html_escape", removes: ["xss"] },
11289
11295
  { method: "escape_html", removes: ["xss"] },
11290
- { method: "parse", removes: ["sql_injection", "command_injection", "xss"] }
11296
+ { method: "parse", removes: ["sql_injection", "command_injection", "xss"] },
11297
+ { method: "parseInt", class: "Integer", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11298
+ { method: "parseLong", class: "Long", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11299
+ { method: "parseFloat", class: "Float", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11300
+ { method: "parseDouble", class: "Double", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11301
+ { method: "parseShort", class: "Short", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11302
+ { method: "parseByte", class: "Byte", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11303
+ { method: "fromString", class: "UUID", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11304
+ { method: "BigInt", removes: ["sql_injection", "nosql_injection", "command_injection", "path_traversal", "code_injection"] },
11305
+ { method: "Atoi", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11306
+ { method: "ParseInt", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11307
+ { method: "ParseFloat", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11308
+ { method: "ParseUint", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11309
+ { method: "ParseBool", class: "strconv", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11310
+ { method: "Parse", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11311
+ { method: "MustParse", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11312
+ { method: "bool", removes: ["sql_injection", "command_injection", "xss", "code_injection"] },
11313
+ { method: "UUID", class: "uuid", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] },
11314
+ { method: "Decimal", class: "decimal", removes: ["sql_injection", "command_injection", "path_traversal", "code_injection"] }
11291
11315
  ];
11292
11316
  function getDefaultConfig() {
11293
11317
  return {
@@ -11398,7 +11422,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
11398
11422
  `) : undefined;
11399
11423
  const sources = findSources(calls, types, config.sources, sourceLines, language);
11400
11424
  const sinks = findSinks(calls, config.sinks, typeHierarchy, language, sourceLines);
11401
- const sanitizers = findSanitizers(calls, types, config.sanitizers);
11425
+ const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
11402
11426
  return { sources, sinks, sanitizers };
11403
11427
  }
11404
11428
  function attachSourceLineCode(sources, sinks, code) {
@@ -11419,6 +11443,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
11419
11443
  const sources = [];
11420
11444
  for (const call of calls) {
11421
11445
  for (const pattern of patterns) {
11446
+ if (pattern.languages && pattern.languages.length > 0 && language !== undefined && !pattern.languages.includes(language)) {
11447
+ continue;
11448
+ }
11422
11449
  if (matchesSourcePattern(call, pattern)) {
11423
11450
  sources.push({
11424
11451
  type: pattern.type,
@@ -12048,6 +12075,15 @@ function receiverMightBeClass(receiver, className) {
12048
12075
  if (receiver === className) {
12049
12076
  return true;
12050
12077
  }
12078
+ if (receiver.endsWith(")")) {
12079
+ const ctorMatch = receiver.match(/^(\w+)\(/);
12080
+ if (ctorMatch) {
12081
+ const ctorName = ctorMatch[1];
12082
+ if (ctorName === className || ctorName.toLowerCase() === className.toLowerCase()) {
12083
+ return true;
12084
+ }
12085
+ }
12086
+ }
12051
12087
  if (receiver.includes("::")) {
12052
12088
  const scopePrefix = receiver.match(/^(\w+)::/);
12053
12089
  if (scopePrefix) {
@@ -12290,7 +12326,7 @@ function calculateSinkConfidence(call, pattern) {
12290
12326
  }
12291
12327
  return Math.min(confidence, 1);
12292
12328
  }
12293
- function findSanitizers(calls, types, patterns) {
12329
+ function findSanitizers(calls, types, patterns, sourceLines) {
12294
12330
  const sanitizers = [];
12295
12331
  const sanitizerMethods = new Set;
12296
12332
  for (const type of types) {
@@ -12300,6 +12336,77 @@ function findSanitizers(calls, types, patterns) {
12300
12336
  }
12301
12337
  }
12302
12338
  }
12339
+ const wrapperSanitizers = new Map;
12340
+ for (const type of types) {
12341
+ for (const method of type.methods) {
12342
+ const bodySize = method.end_line - method.start_line;
12343
+ if (bodySize < 0 || bodySize > 2)
12344
+ continue;
12345
+ const paramNames = new Set(method.parameters.map((p) => p.name));
12346
+ if (paramNames.size === 0)
12347
+ continue;
12348
+ const inside = [];
12349
+ for (const c of calls) {
12350
+ if (c.location.line < method.start_line || c.location.line > method.end_line)
12351
+ continue;
12352
+ if (c.method_name === method.name)
12353
+ continue;
12354
+ inside.push(c);
12355
+ }
12356
+ if (inside.length !== 1)
12357
+ continue;
12358
+ const innerCall = inside[0];
12359
+ let matched;
12360
+ for (const pattern of patterns) {
12361
+ if (matchesSanitizerPattern(innerCall, pattern)) {
12362
+ matched = pattern;
12363
+ break;
12364
+ }
12365
+ }
12366
+ if (!matched || !matched.removes || matched.removes.length === 0)
12367
+ continue;
12368
+ let argOk = false;
12369
+ for (const arg of innerCall.arguments) {
12370
+ if (arg.variable && paramNames.has(arg.variable)) {
12371
+ argOk = true;
12372
+ break;
12373
+ }
12374
+ }
12375
+ if (!argOk)
12376
+ continue;
12377
+ if (sourceLines) {
12378
+ const lineText = sourceLines[innerCall.location.line - 1] ?? "";
12379
+ const stripped = lineText.trim();
12380
+ const returnMatch = stripped.match(/^return\s+(?:await\s+)?(.*)$/);
12381
+ if (!returnMatch)
12382
+ continue;
12383
+ const after = returnMatch[1].replace(/;\s*$/, "").trimEnd();
12384
+ const callPrefix = innerCall.receiver ? `${innerCall.receiver}.${innerCall.method_name}(` : `${innerCall.method_name}(`;
12385
+ if (!after.startsWith(callPrefix))
12386
+ continue;
12387
+ if (!after.endsWith(")"))
12388
+ continue;
12389
+ }
12390
+ const existing = wrapperSanitizers.get(method.name);
12391
+ if (existing) {
12392
+ const set = new Set([...existing, ...matched.removes]);
12393
+ wrapperSanitizers.set(method.name, Array.from(set));
12394
+ } else {
12395
+ wrapperSanitizers.set(method.name, [...matched.removes]);
12396
+ }
12397
+ }
12398
+ }
12399
+ for (const call of calls) {
12400
+ const removes = wrapperSanitizers.get(call.method_name);
12401
+ if (!removes)
12402
+ continue;
12403
+ sanitizers.push({
12404
+ type: "derived_wrapper",
12405
+ method: formatSanitizerMethod(call),
12406
+ line: call.location.line,
12407
+ sanitizes: removes
12408
+ });
12409
+ }
12303
12410
  for (const call of calls) {
12304
12411
  if (sanitizerMethods.has(call.method_name)) {
12305
12412
  sanitizers.push({
@@ -12957,7 +13064,24 @@ var SANITIZER_METHODS = new Set([
12957
13064
  "validatePath",
12958
13065
  "validateCityName",
12959
13066
  "validateInput",
12960
- "sanitizeInput"
13067
+ "sanitizeInput",
13068
+ "parseInt",
13069
+ "parseLong",
13070
+ "parseFloat",
13071
+ "parseDouble",
13072
+ "parseShort",
13073
+ "parseByte",
13074
+ "fromString",
13075
+ "Number",
13076
+ "BigInt",
13077
+ "Atoi",
13078
+ "ParseInt",
13079
+ "ParseFloat",
13080
+ "ParseUint",
13081
+ "ParseBool",
13082
+ "int",
13083
+ "float",
13084
+ "bool"
12961
13085
  ]);
12962
13086
  var ANTI_SANITIZER_METHODS = new Set([
12963
13087
  "decode",
@@ -13030,6 +13154,7 @@ class ConstantPropagator {
13030
13154
  currentClassName = null;
13031
13155
  inConstructor = false;
13032
13156
  constructorParamPositions = new Map;
13157
+ safePatternFieldsCache = null;
13033
13158
  analyze(tree, sourceCode, additionalTaintPatterns = [], sanitizerMethods = [], taintedParameters = []) {
13034
13159
  this.source = sourceCode;
13035
13160
  this.additionalTaintPatterns = additionalTaintPatterns;
@@ -13060,12 +13185,14 @@ class ConstantPropagator {
13060
13185
  this.currentClassName = null;
13061
13186
  this.inConstructor = false;
13062
13187
  this.constructorParamPositions.clear();
13188
+ this.safePatternFieldsCache = null;
13063
13189
  this.collectClassFields(tree.rootNode);
13064
13190
  for (const methodName of sanitizerMethods) {
13065
13191
  this.methodReturnsSanitized.add(methodName);
13066
13192
  }
13067
13193
  this.evaluator = new ExpressionEvaluator(this.source, (name2) => this.lookupSymbol(name2));
13068
13194
  this.analyzeMethodReturns(tree.rootNode);
13195
+ this.seedPythonModuleConstants(tree.rootNode);
13069
13196
  this.visit(tree.rootNode);
13070
13197
  this.refineTaintFromConstants();
13071
13198
  const resultTainted = new Set(this.tainted);
@@ -13410,6 +13537,141 @@ class ConstantPropagator {
13410
13537
  }
13411
13538
  }
13412
13539
  }
13540
+ fieldDeclHasPrimitiveLiteralValue(node) {
13541
+ const primitive = new Set([
13542
+ "true",
13543
+ "false",
13544
+ "null_literal",
13545
+ "decimal_integer_literal",
13546
+ "hex_integer_literal",
13547
+ "octal_integer_literal",
13548
+ "binary_integer_literal",
13549
+ "decimal_floating_point_literal",
13550
+ "hex_floating_point_literal",
13551
+ "character_literal",
13552
+ "string_literal",
13553
+ "number",
13554
+ "string"
13555
+ ]);
13556
+ for (const child of node.children) {
13557
+ if (child.type !== "variable_declarator")
13558
+ continue;
13559
+ const value = child.childForFieldName("value");
13560
+ if (!value)
13561
+ continue;
13562
+ if (!primitive.has(value.type))
13563
+ return false;
13564
+ }
13565
+ return true;
13566
+ }
13567
+ getSafePatternFields() {
13568
+ if (this.safePatternFieldsCache !== null)
13569
+ return this.safePatternFieldsCache;
13570
+ const set = new Set;
13571
+ const re = /\b(?:public\s+|private\s+|protected\s+)?(?:static\s+final|final\s+static)\s+(?:java\.util\.regex\.)?Pattern\s+(\w+)\s*=\s*(?:java\.util\.regex\.)?Pattern\s*\.\s*compile\s*\(\s*"((?:[^"\\]|\\.)*)"/g;
13572
+ let m;
13573
+ while ((m = re.exec(this.source)) !== null) {
13574
+ const name2 = m[1];
13575
+ const regex = m[2];
13576
+ if (this.isStrictAnchoredRegex(regex))
13577
+ set.add(name2);
13578
+ }
13579
+ this.safePatternFieldsCache = set;
13580
+ return set;
13581
+ }
13582
+ isStrictAnchoredRegex(re) {
13583
+ if (!re.startsWith("^") || !re.endsWith("$"))
13584
+ return false;
13585
+ const stripped = re.replace(/\[(?:[^\]\\]|\\.)*\]/g, "");
13586
+ const cleaned = stripped.replace(/\\./g, "");
13587
+ if (cleaned.includes("."))
13588
+ return false;
13589
+ if (cleaned.includes("|"))
13590
+ return false;
13591
+ return true;
13592
+ }
13593
+ detectRegexAllowlistGuard(condition, consequence) {
13594
+ if (!consequence)
13595
+ return null;
13596
+ let condText = getNodeText2(condition, this.source).replace(/\s+/g, "");
13597
+ while (condText.startsWith("(") && condText.endsWith(")")) {
13598
+ const inner = condText.slice(1, -1);
13599
+ let depth = 0;
13600
+ let balanced = true;
13601
+ for (let i2 = 0;i2 < inner.length; i2++) {
13602
+ if (inner[i2] === "(")
13603
+ depth++;
13604
+ else if (inner[i2] === ")")
13605
+ depth--;
13606
+ if (depth < 0) {
13607
+ balanced = false;
13608
+ break;
13609
+ }
13610
+ }
13611
+ if (!balanced || depth !== 0)
13612
+ break;
13613
+ condText = inner;
13614
+ }
13615
+ const m = condText.match(/^!(\w+)\.matcher\((\w+)\)\.matches\(\)$/);
13616
+ if (!m)
13617
+ return null;
13618
+ const patternName = m[1];
13619
+ const varName = m[2];
13620
+ if (!this.getSafePatternFields().has(patternName))
13621
+ return null;
13622
+ if (!this.consequenceContainsThrow(consequence))
13623
+ return null;
13624
+ return varName;
13625
+ }
13626
+ consequenceContainsThrow(node) {
13627
+ if (node.type === "throw_statement")
13628
+ return true;
13629
+ const stack = [node];
13630
+ while (stack.length > 0) {
13631
+ const n = stack.pop();
13632
+ if (!n)
13633
+ continue;
13634
+ if (n.type === "throw_statement")
13635
+ return true;
13636
+ if (n.type === "if_statement" || n.type === "switch_statement")
13637
+ continue;
13638
+ for (const c of n.children)
13639
+ stack.push(c);
13640
+ }
13641
+ return false;
13642
+ }
13643
+ seedPythonModuleConstants(root) {
13644
+ if (root.type !== "module")
13645
+ return;
13646
+ for (const child of root.children) {
13647
+ const target = child.type === "assignment" ? child : child.type === "expression_statement" && child.children.length > 0 ? child.children[0] : null;
13648
+ if (!target || target.type !== "assignment")
13649
+ continue;
13650
+ const left = target.childForFieldName("left");
13651
+ const right = target.childForFieldName("right");
13652
+ if (!left || !right)
13653
+ continue;
13654
+ if (left.type !== "identifier")
13655
+ continue;
13656
+ const allowed = new Set([
13657
+ "true",
13658
+ "false",
13659
+ "none",
13660
+ "integer",
13661
+ "float",
13662
+ "string"
13663
+ ]);
13664
+ if (!allowed.has(right.type))
13665
+ continue;
13666
+ const name2 = getNodeText2(left, this.source);
13667
+ if (!name2)
13668
+ continue;
13669
+ const value = this.evaluateExpression(right);
13670
+ if (!isKnown(value))
13671
+ continue;
13672
+ this.symbols.set(name2, value);
13673
+ }
13674
+ }
13413
13675
  findAllMethods(node) {
13414
13676
  const methods = [];
13415
13677
  const stack = [node];
@@ -13495,6 +13757,11 @@ class ConstantPropagator {
13495
13757
  case "local_variable_declaration":
13496
13758
  this.handleVariableDeclaration(node);
13497
13759
  return false;
13760
+ case "field_declaration":
13761
+ if (this.fieldDeclHasPrimitiveLiteralValue(node)) {
13762
+ this.handleVariableDeclaration(node);
13763
+ }
13764
+ return false;
13498
13765
  case "assignment_expression":
13499
13766
  this.handleAssignment(node);
13500
13767
  return false;
@@ -13964,6 +14231,16 @@ class ConstantPropagator {
13964
14231
  }
13965
14232
  this.inConditionalBranch = wasInConditional;
13966
14233
  this.tainted = new Set([...taintedBefore, ...taintedAfterThen, ...taintedAfterElse]);
14234
+ const guardedVar = this.detectRegexAllowlistGuard(condition, consequence);
14235
+ if (guardedVar) {
14236
+ this.tainted.delete(guardedVar);
14237
+ this.sanitizedVars.add(guardedVar);
14238
+ const scoped = this.getScopedName(guardedVar);
14239
+ if (scoped !== guardedVar) {
14240
+ this.tainted.delete(scoped);
14241
+ this.sanitizedVars.add(scoped);
14242
+ }
14243
+ }
13967
14244
  }
13968
14245
  }
13969
14246
  normalizeCondition(cond) {
@@ -14135,15 +14412,26 @@ class ConstantPropagator {
14135
14412
  return null;
14136
14413
  }
14137
14414
  isSanitizerMethodCall(node) {
14138
- if (node.type !== "method_invocation") {
14415
+ const methodName = this.extractCallName(node);
14416
+ if (!methodName)
14139
14417
  return false;
14418
+ return SANITIZER_METHODS.has(methodName) || this.methodReturnsSanitized.has(methodName);
14419
+ }
14420
+ extractCallName(node) {
14421
+ let fnNode = null;
14422
+ if (node.type === "method_invocation") {
14423
+ fnNode = node.childForFieldName("name");
14424
+ } else if (node.type === "call_expression" || node.type === "call") {
14425
+ fnNode = node.childForFieldName("function");
14140
14426
  }
14141
- const nameNode = node.childForFieldName("name");
14142
- if (!nameNode) {
14143
- return false;
14427
+ if (!fnNode)
14428
+ return null;
14429
+ if (fnNode.type === "selector_expression" || fnNode.type === "member_expression" || fnNode.type === "attribute") {
14430
+ const tail = fnNode.childForFieldName("field") || fnNode.childForFieldName("property") || fnNode.childForFieldName("attribute");
14431
+ if (tail)
14432
+ return getNodeText2(tail, this.source);
14144
14433
  }
14145
- const methodName = getNodeText2(nameNode, this.source);
14146
- return SANITIZER_METHODS.has(methodName) || this.methodReturnsSanitized.has(methodName);
14434
+ return getNodeText2(fnNode, this.source);
14147
14435
  }
14148
14436
  isAntiSanitizerCall(node) {
14149
14437
  if (node.type !== "method_invocation") {
@@ -20821,26 +21109,44 @@ function findBashTaintSources(sourceCode, dfg) {
20821
21109
  const lines = sourceCode.split(`
20822
21110
  `);
20823
21111
  const definedVars = new Set(dfg.defs.filter((d) => d.kind === "local").map((d) => d.variable));
21112
+ const fnHeaderRe = /^\s*(?:function\s+)?[A-Za-z_][\w-]*\s*\(\s*\)\s*\{?\s*$|^\s*function\s+[A-Za-z_][\w-]*\s*\{?\s*$/;
21113
+ let braceDepth = 0;
20824
21114
  for (let i2 = 0;i2 < lines.length; i2++) {
20825
21115
  const line = lines[i2];
20826
21116
  const trimmed = line.trim();
20827
21117
  const lineNumber = i2 + 1;
20828
21118
  if (trimmed.startsWith("#"))
20829
21119
  continue;
20830
- const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
20831
- let m;
20832
- while ((m = positionalRe.exec(line)) !== null) {
20833
- const param = m[1] ?? m[2];
20834
- const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
20835
- if (!alreadyExists) {
20836
- sources.push({
20837
- type: "io_input",
20838
- location: `positional parameter $${param}`,
20839
- severity: "high",
20840
- line: lineNumber,
20841
- confidence: 1,
20842
- variable: param
20843
- });
21120
+ const insideFunction = braceDepth > 0;
21121
+ if (!insideFunction) {
21122
+ const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
21123
+ let m;
21124
+ while ((m = positionalRe.exec(line)) !== null) {
21125
+ const param = m[1] ?? m[2];
21126
+ const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
21127
+ if (!alreadyExists) {
21128
+ sources.push({
21129
+ type: "io_input",
21130
+ location: `positional parameter $${param}`,
21131
+ severity: "high",
21132
+ line: lineNumber,
21133
+ confidence: 1,
21134
+ variable: param
21135
+ });
21136
+ }
21137
+ }
21138
+ }
21139
+ if (fnHeaderRe.test(line) || /^\s*[A-Za-z_][\w-]*\s*\(\s*\)\s*\{/.test(line)) {
21140
+ const openBracesOnLine = (line.match(/\{/g) ?? []).length;
21141
+ const closeBracesOnLine = (line.match(/\}/g) ?? []).length;
21142
+ braceDepth += openBracesOnLine - closeBracesOnLine;
21143
+ } else {
21144
+ if (braceDepth > 0) {
21145
+ const openBracesOnLine = (line.match(/\{/g) ?? []).length;
21146
+ const closeBracesOnLine = (line.match(/\}/g) ?? []).length;
21147
+ braceDepth += openBracesOnLine - closeBracesOnLine;
21148
+ if (braceDepth < 0)
21149
+ braceDepth = 0;
20844
21150
  }
20845
21151
  }
20846
21152
  const cmdSubAssign = trimmed.match(/^(\w+)=\$\((\w+)\s/);
@@ -21287,10 +21593,15 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
21287
21593
  const callsAtSink = callsByLine.get(sink.line) ?? [];
21288
21594
  const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
21289
21595
  const relevantCalls = sink.method ? callsAtSink.filter((c) => c.method_name === sink.method) : callsAtSink;
21596
+ const trustArgPositions = language !== "bash" && language !== "shell";
21290
21597
  for (const call of relevantCalls) {
21291
21598
  let allArgsAreClean = true;
21599
+ let dangerousArgCount = 0;
21292
21600
  const methodName = call.in_method;
21293
21601
  for (const arg of call.arguments) {
21602
+ if (trustArgPositions && sink.argPositions && sink.argPositions.length > 0 && !sink.argPositions.includes(arg.position))
21603
+ continue;
21604
+ dangerousArgCount++;
21294
21605
  if (language === "bash" && arg.expression === call.method_name && !arg.variable && arg.literal == null)
21295
21606
  continue;
21296
21607
  if (arg.variable && !arg.expression?.includes("[")) {
@@ -21318,7 +21629,7 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
21318
21629
  allArgsAreClean = false;
21319
21630
  }
21320
21631
  }
21321
- if (allArgsAreClean && call.arguments.length > 0)
21632
+ if (allArgsAreClean && dangerousArgCount > 0)
21322
21633
  return false;
21323
21634
  }
21324
21635
  return true;
@@ -21369,6 +21680,17 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
21369
21680
  }
21370
21681
 
21371
21682
  // ../circle-ir/dist/analysis/taint-propagation.js
21683
+ function buildSanitizersByLine(sanitizers) {
21684
+ const out2 = new Map;
21685
+ for (const san of sanitizers) {
21686
+ const existing = out2.get(san.line);
21687
+ if (existing)
21688
+ existing.push(san);
21689
+ else
21690
+ out2.set(san.line, [san]);
21691
+ }
21692
+ return out2;
21693
+ }
21372
21694
  function propagateTaint2(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
21373
21695
  let graph;
21374
21696
  let sources;
@@ -21404,7 +21726,7 @@ function propagateTaint2(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSani
21404
21726
  const defsByLine = graph.defsByLine;
21405
21727
  const usesByLine = graph.usesByLine;
21406
21728
  const callsByLine = graph.callsByLine;
21407
- const sanitizersByLine = graph.sanitizersByLine;
21729
+ const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
21408
21730
  const defById = graph.defById;
21409
21731
  const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
21410
21732
  const initialTaint = rawInitialTaint.filter((tv) => {
@@ -21672,7 +21994,7 @@ class TaintPropagationPass {
21672
21994
  flows.push(f);
21673
21995
  }
21674
21996
  }
21675
- const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines) ?? [];
21997
+ const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code) ?? [];
21676
21998
  for (const f of collectionFlows) {
21677
21999
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line))
21678
22000
  continue;
@@ -21694,13 +22016,13 @@ class TaintPropagationPass {
21694
22016
  continue;
21695
22017
  flows.push(f);
21696
22018
  }
21697
- const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines) ?? [];
22019
+ const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
21698
22020
  for (const f of paramFlows) {
21699
22021
  if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
21700
22022
  flows.push(f);
21701
22023
  }
21702
22024
  }
21703
- const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, ctx.code, ctx.language) ?? [];
22025
+ const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
21704
22026
  for (const f of exprScanFlows) {
21705
22027
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type))
21706
22028
  continue;
@@ -21722,10 +22044,25 @@ class TaintPropagationPass {
21722
22044
  continue;
21723
22045
  flows.push(f);
21724
22046
  }
21725
- return { flows };
22047
+ const sanitizedNames = constProp.sanitizedVars;
22048
+ const finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
22049
+ if (f.path.length === 0)
22050
+ return true;
22051
+ const sourceVar = f.path[0].variable;
22052
+ if (!sourceVar)
22053
+ return true;
22054
+ if (sanitizedNames.has(sourceVar))
22055
+ return false;
22056
+ for (const s of sanitizedNames) {
22057
+ if (s.endsWith(`:${sourceVar}`))
22058
+ return false;
22059
+ }
22060
+ return true;
22061
+ });
22062
+ return { flows: finalFlows };
21726
22063
  }
21727
22064
  }
21728
- function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines) {
22065
+ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code) {
21729
22066
  const flows = [];
21730
22067
  const callsByLine = new Map;
21731
22068
  for (const call of calls) {
@@ -21748,6 +22085,9 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
21748
22085
  if (taintedVars.has(varName) || taintedVars.has(scopedName)) {
21749
22086
  const source = sources[0];
21750
22087
  if (source) {
22088
+ if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, source.line, sink.line)) {
22089
+ continue;
22090
+ }
21751
22091
  flows.push({
21752
22092
  source_line: source.line,
21753
22093
  sink_line: sink.line,
@@ -21782,6 +22122,9 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
21782
22122
  if (taintedVars.has(collectionVar) || taintedVars.has(scopedCollection)) {
21783
22123
  const source = sources[0];
21784
22124
  if (source) {
22125
+ if (typeof code === "string" && isReassignedToLiteralBetween(code, collectionVar, source.line, sink.line)) {
22126
+ continue;
22127
+ }
21785
22128
  flows.push({
21786
22129
  source_line: source.line,
21787
22130
  sink_line: sink.line,
@@ -21852,7 +22195,7 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
21852
22195
  }
21853
22196
  return flows;
21854
22197
  }
21855
- function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines) {
22198
+ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
21856
22199
  const flows = [];
21857
22200
  const paramSourcesByMethod = new Map;
21858
22201
  for (const source of sources) {
@@ -21898,6 +22241,9 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
21898
22241
  if (paramSource) {
21899
22242
  const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line);
21900
22243
  if (!exists) {
22244
+ if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line)) {
22245
+ continue;
22246
+ }
21901
22247
  flows.push({
21902
22248
  source_line: paramSource.line,
21903
22249
  sink_line: sink.line,
@@ -21918,7 +22264,28 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
21918
22264
  }
21919
22265
  return flows;
21920
22266
  }
21921
- function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, code, language) {
22267
+ function isReassignedToLiteralBetween(code, variable, srcLine, sinkLine) {
22268
+ if (!variable || sinkLine - srcLine < 2)
22269
+ return false;
22270
+ if (!/^[A-Za-z_][\w]*$/.test(variable))
22271
+ return false;
22272
+ const lines = code.split(`
22273
+ `);
22274
+ const lo = Math.max(0, srcLine);
22275
+ const hi = Math.min(lines.length, sinkLine - 1);
22276
+ const strLit = `(?:"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'|\`[^\`\\\\]*(?:\\\\.[^\`\\\\]*)*\`)`;
22277
+ const reNaked = new RegExp(`^\\s*${variable}\\s*(?::?=)\\s*${strLit}\\s*;?\\s*$`);
22278
+ const reGuarded = new RegExp(`^\\s*if\\b.*\\b${variable}\\s*=\\s*${strLit}\\s*;?\\s*$`);
22279
+ for (let i2 = lo;i2 < hi; i2++) {
22280
+ const line = lines[i2];
22281
+ if (!line)
22282
+ continue;
22283
+ if (reNaked.test(line) || reGuarded.test(line))
22284
+ return true;
22285
+ }
22286
+ return false;
22287
+ }
22288
+ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachableLines, tainted, code, language) {
21922
22289
  const flows = [];
21923
22290
  const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
21924
22291
  const aliasSanitizedFor = new Map;
@@ -21973,10 +22340,10 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
21973
22340
  continue;
21974
22341
  const rhs = rhsMatch[1];
21975
22342
  for (const san of lineSans) {
21976
- const sanMatch = san.method.match(/^(?:(\w+)\.)?(\w+)\(\)$/);
22343
+ const sanMatch = san.method.match(/(\w+)\(\)$/);
21977
22344
  if (!sanMatch)
21978
22345
  continue;
21979
- const sanName = sanMatch[1] ? `${sanMatch[1]}.${sanMatch[2]}` : sanMatch[2];
22346
+ const sanName = sanMatch[1];
21980
22347
  if (!rhs.includes(`${sanName}(`))
21981
22348
  continue;
21982
22349
  let set = aliasSanitizedFor.get(varName);
@@ -22048,6 +22415,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22048
22415
  if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
22049
22416
  break;
22050
22417
  }
22418
+ if (typeof code === "string" && isReassignedToLiteralBetween(code, source.variable, source.line, sink.line)) {
22419
+ break;
22420
+ }
22051
22421
  flows.push({
22052
22422
  source_line: source.line,
22053
22423
  sink_line: sink.line,
@@ -22082,6 +22452,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22082
22452
  for (const source of colocSources) {
22083
22453
  if (!canSourceReachSink(source.type, sink.type))
22084
22454
  continue;
22455
+ if (source.type === "file_input" && sink.type === "path_traversal" && sink.method && source.location.includes(`${sink.method}(`)) {
22456
+ continue;
22457
+ }
22085
22458
  if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
22086
22459
  continue;
22087
22460
  flows.push({
@@ -26325,6 +26698,25 @@ var JS_ROUTE_METHODS = new Set([
26325
26698
  "head",
26326
26699
  "options"
26327
26700
  ]);
26701
+ var SECURITY_MIDDLEWARE_METHODS = new Set([
26702
+ "helmet",
26703
+ "frameguard",
26704
+ "contentSecurityPolicy",
26705
+ "hsts",
26706
+ "noSniff",
26707
+ "xssFilter",
26708
+ "referrerPolicy",
26709
+ "permittedCrossDomainPolicies",
26710
+ "dnsPrefetchControl",
26711
+ "frameOptions",
26712
+ "headers",
26713
+ "httpStrictTransportSecurity",
26714
+ "contentTypeOptions",
26715
+ "xssProtection",
26716
+ "Talisman",
26717
+ "Secure"
26718
+ ]);
26719
+ var SECURITY_MIDDLEWARE_ANNOTATIONS_RE = /\b(EnableWebSecurity|SecurityFilterChain|after_request|before_request)\b/;
26328
26720
 
26329
26721
  class SecurityHeadersPass {
26330
26722
  name = "security-headers";
@@ -26355,6 +26747,7 @@ class SecurityHeadersPass {
26355
26747
  list.push(call);
26356
26748
  }
26357
26749
  const hasHandler = detectHandler(graph, calls);
26750
+ const hasGlobalMiddleware = detectGlobalSecurityMiddleware(graph, calls);
26358
26751
  for (const rule of this.rules) {
26359
26752
  const headerKey = rule.header.toLowerCase();
26360
26753
  const writes = writtenHeaders.get(headerKey) ?? [];
@@ -26363,6 +26756,8 @@ class SecurityHeadersPass {
26363
26756
  continue;
26364
26757
  if (rule.requiresHandler !== false && !hasHandler)
26365
26758
  continue;
26759
+ if (hasGlobalMiddleware)
26760
+ continue;
26366
26761
  ctx.addFinding({
26367
26762
  id: `${rule.rule_id}-${file}`,
26368
26763
  pass: this.name,
@@ -26537,6 +26932,28 @@ function detectHandler(graph, calls) {
26537
26932
  }
26538
26933
  return false;
26539
26934
  }
26935
+ function detectGlobalSecurityMiddleware(graph, calls) {
26936
+ for (const call of calls) {
26937
+ if (SECURITY_MIDDLEWARE_METHODS.has(call.method_name))
26938
+ return true;
26939
+ if (call.method_name === "use" && call.arguments.length > 0) {
26940
+ const firstArg = call.arguments[0].expression ?? "";
26941
+ if (/\b(helmet|Talisman|secure)\b/.test(firstArg))
26942
+ return true;
26943
+ }
26944
+ }
26945
+ for (const type of graph.ir.types) {
26946
+ if (type.annotations.some((a) => SECURITY_MIDDLEWARE_ANNOTATIONS_RE.test(a)))
26947
+ return true;
26948
+ for (const method of type.methods) {
26949
+ if (method.annotations.some((a) => SECURITY_MIDDLEWARE_ANNOTATIONS_RE.test(a)))
26950
+ return true;
26951
+ if (/^security[A-Za-z]*FilterChain$/i.test(method.name))
26952
+ return true;
26953
+ }
26954
+ }
26955
+ return false;
26956
+ }
26540
26957
  function checkInheritedCorsHeaders(fileAnalyses, typeHierarchy, sourceLines) {
26541
26958
  const findings = [];
26542
26959
  for (const { file: parentFile, analysis: parentIR } of fileAnalyses) {
@@ -30023,7 +30440,7 @@ var colors = {
30023
30440
  };
30024
30441
 
30025
30442
  // src/version.ts
30026
- var version = "3.57.0";
30443
+ var version = "3.58.0";
30027
30444
 
30028
30445
  // src/formatters.ts
30029
30446
  var SINK_SEVERITY = {
@@ -30797,7 +31214,8 @@ var TEST_PATTERNS = [
30797
31214
  /_test\.py$/,
30798
31215
  /_tests\.py$/,
30799
31216
  /test_.*\.py$/,
30800
- /_test\.rs$/
31217
+ /_test\.rs$/,
31218
+ /_test\.go$/
30801
31219
  ];
30802
31220
  function isTestFile2(filePath) {
30803
31221
  return TEST_PATTERNS.some((pattern) => pattern.test(filePath));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.57.0",
3
+ "version": "3.58.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.57.0"
68
+ "circle-ir": "^3.58.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",