cognium-dev 3.56.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 +496 -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;
@@ -13622,6 +13889,27 @@ class ConstantPropagator {
13622
13889
  if (nameNode) {
13623
13890
  const varName = getNodeText2(nameNode, this.source);
13624
13891
  loopVarNames.add(varName);
13892
+ const collectionNode = node.childForFieldName("value");
13893
+ if (collectionNode) {
13894
+ const collectionName = getNodeText2(collectionNode, this.source);
13895
+ const scopedCollection = this.currentMethod ? `${this.currentMethod}:${collectionName}` : collectionName;
13896
+ let elementIsTainted = this.tainted.has(collectionName) || this.tainted.has(scopedCollection);
13897
+ if (!elementIsTainted) {
13898
+ const taintedIndices = this.taintedArrayElements.get(collectionName);
13899
+ if (taintedIndices && taintedIndices.size > 0)
13900
+ elementIsTainted = true;
13901
+ }
13902
+ if (!elementIsTainted) {
13903
+ const taintedKeys = this.taintedCollections.get(collectionName);
13904
+ if (taintedKeys && taintedKeys.size > 0)
13905
+ elementIsTainted = true;
13906
+ }
13907
+ if (elementIsTainted) {
13908
+ const scopedVar = this.currentMethod ? `${this.currentMethod}:${varName}` : varName;
13909
+ this.tainted.add(varName);
13910
+ this.tainted.add(scopedVar);
13911
+ }
13912
+ }
13625
13913
  }
13626
13914
  }
13627
13915
  for (const varName of loopVarNames) {
@@ -13943,6 +14231,16 @@ class ConstantPropagator {
13943
14231
  }
13944
14232
  this.inConditionalBranch = wasInConditional;
13945
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
+ }
13946
14244
  }
13947
14245
  }
13948
14246
  normalizeCondition(cond) {
@@ -14114,15 +14412,26 @@ class ConstantPropagator {
14114
14412
  return null;
14115
14413
  }
14116
14414
  isSanitizerMethodCall(node) {
14117
- if (node.type !== "method_invocation") {
14415
+ const methodName = this.extractCallName(node);
14416
+ if (!methodName)
14118
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");
14119
14426
  }
14120
- const nameNode = node.childForFieldName("name");
14121
- if (!nameNode) {
14122
- 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);
14123
14433
  }
14124
- const methodName = getNodeText2(nameNode, this.source);
14125
- return SANITIZER_METHODS.has(methodName) || this.methodReturnsSanitized.has(methodName);
14434
+ return getNodeText2(fnNode, this.source);
14126
14435
  }
14127
14436
  isAntiSanitizerCall(node) {
14128
14437
  if (node.type !== "method_invocation") {
@@ -14514,9 +14823,21 @@ class ConstantPropagator {
14514
14823
  this.taintedCollections.set(collectionName, new Set);
14515
14824
  }
14516
14825
  this.taintedCollections.get(collectionName).add(keyStr);
14826
+ const scopedCollection = this.currentMethod ? `${this.currentMethod}:${collectionName}` : collectionName;
14827
+ this.tainted.add(scopedCollection);
14828
+ this.tainted.add(collectionName);
14517
14829
  }
14518
14830
  }
14519
14831
  }
14832
+ if (methodName === "append" || methodName === "insert") {
14833
+ const args2 = argsNode.children.filter((c) => c.type !== "(" && c.type !== ")" && c.type !== ",");
14834
+ const valueArg = methodName === "insert" && args2.length >= 2 ? args2[1] : args2[0];
14835
+ if (valueArg && this.isTaintedExpression(valueArg)) {
14836
+ const scopedCollection = this.currentMethod ? `${this.currentMethod}:${collectionName}` : collectionName;
14837
+ this.tainted.add(scopedCollection);
14838
+ this.tainted.add(collectionName);
14839
+ }
14840
+ }
14520
14841
  if (methodName === "add" || methodName === "addLast") {
14521
14842
  const args2 = argsNode.children.filter((c) => c.type !== "(" && c.type !== ")" && c.type !== ",");
14522
14843
  if (args2.length >= 1) {
@@ -20788,26 +21109,44 @@ function findBashTaintSources(sourceCode, dfg) {
20788
21109
  const lines = sourceCode.split(`
20789
21110
  `);
20790
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;
20791
21114
  for (let i2 = 0;i2 < lines.length; i2++) {
20792
21115
  const line = lines[i2];
20793
21116
  const trimmed = line.trim();
20794
21117
  const lineNumber = i2 + 1;
20795
21118
  if (trimmed.startsWith("#"))
20796
21119
  continue;
20797
- const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
20798
- let m;
20799
- while ((m = positionalRe.exec(line)) !== null) {
20800
- const param = m[1] ?? m[2];
20801
- const alreadyExists = sources.some((s) => s.line === lineNumber && s.variable === param);
20802
- if (!alreadyExists) {
20803
- sources.push({
20804
- type: "io_input",
20805
- location: `positional parameter $${param}`,
20806
- severity: "high",
20807
- line: lineNumber,
20808
- confidence: 1,
20809
- variable: param
20810
- });
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;
20811
21150
  }
20812
21151
  }
20813
21152
  const cmdSubAssign = trimmed.match(/^(\w+)=\$\((\w+)\s/);
@@ -21254,10 +21593,15 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
21254
21593
  const callsAtSink = callsByLine.get(sink.line) ?? [];
21255
21594
  const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
21256
21595
  const relevantCalls = sink.method ? callsAtSink.filter((c) => c.method_name === sink.method) : callsAtSink;
21596
+ const trustArgPositions = language !== "bash" && language !== "shell";
21257
21597
  for (const call of relevantCalls) {
21258
21598
  let allArgsAreClean = true;
21599
+ let dangerousArgCount = 0;
21259
21600
  const methodName = call.in_method;
21260
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++;
21261
21605
  if (language === "bash" && arg.expression === call.method_name && !arg.variable && arg.literal == null)
21262
21606
  continue;
21263
21607
  if (arg.variable && !arg.expression?.includes("[")) {
@@ -21285,7 +21629,7 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
21285
21629
  allArgsAreClean = false;
21286
21630
  }
21287
21631
  }
21288
- if (allArgsAreClean && call.arguments.length > 0)
21632
+ if (allArgsAreClean && dangerousArgCount > 0)
21289
21633
  return false;
21290
21634
  }
21291
21635
  return true;
@@ -21336,6 +21680,17 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
21336
21680
  }
21337
21681
 
21338
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
+ }
21339
21694
  function propagateTaint2(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
21340
21695
  let graph;
21341
21696
  let sources;
@@ -21371,7 +21726,7 @@ function propagateTaint2(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSani
21371
21726
  const defsByLine = graph.defsByLine;
21372
21727
  const usesByLine = graph.usesByLine;
21373
21728
  const callsByLine = graph.callsByLine;
21374
- const sanitizersByLine = graph.sanitizersByLine;
21729
+ const sanitizersByLine = sanitizers.length > 0 ? buildSanitizersByLine(sanitizers) : graph.sanitizersByLine;
21375
21730
  const defById = graph.defById;
21376
21731
  const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
21377
21732
  const initialTaint = rawInitialTaint.filter((tv) => {
@@ -21639,7 +21994,7 @@ class TaintPropagationPass {
21639
21994
  flows.push(f);
21640
21995
  }
21641
21996
  }
21642
- const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines) ?? [];
21997
+ const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code) ?? [];
21643
21998
  for (const f of collectionFlows) {
21644
21999
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line))
21645
22000
  continue;
@@ -21661,13 +22016,13 @@ class TaintPropagationPass {
21661
22016
  continue;
21662
22017
  flows.push(f);
21663
22018
  }
21664
- const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines) ?? [];
22019
+ const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
21665
22020
  for (const f of paramFlows) {
21666
22021
  if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
21667
22022
  flows.push(f);
21668
22023
  }
21669
22024
  }
21670
- 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) ?? [];
21671
22026
  for (const f of exprScanFlows) {
21672
22027
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type))
21673
22028
  continue;
@@ -21689,10 +22044,25 @@ class TaintPropagationPass {
21689
22044
  continue;
21690
22045
  flows.push(f);
21691
22046
  }
21692
- 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 };
21693
22063
  }
21694
22064
  }
21695
- function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines) {
22065
+ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLines, code) {
21696
22066
  const flows = [];
21697
22067
  const callsByLine = new Map;
21698
22068
  for (const call of calls) {
@@ -21715,6 +22085,9 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
21715
22085
  if (taintedVars.has(varName) || taintedVars.has(scopedName)) {
21716
22086
  const source = sources[0];
21717
22087
  if (source) {
22088
+ if (typeof code === "string" && isReassignedToLiteralBetween(code, varName, source.line, sink.line)) {
22089
+ continue;
22090
+ }
21718
22091
  flows.push({
21719
22092
  source_line: source.line,
21720
22093
  sink_line: sink.line,
@@ -21749,6 +22122,9 @@ function detectCollectionFlows(calls, sources, sinks, taintedVars, unreachableLi
21749
22122
  if (taintedVars.has(collectionVar) || taintedVars.has(scopedCollection)) {
21750
22123
  const source = sources[0];
21751
22124
  if (source) {
22125
+ if (typeof code === "string" && isReassignedToLiteralBetween(code, collectionVar, source.line, sink.line)) {
22126
+ continue;
22127
+ }
21752
22128
  flows.push({
21753
22129
  source_line: source.line,
21754
22130
  sink_line: sink.line,
@@ -21819,7 +22195,7 @@ function detectArrayElementFlows(calls, sources, sinks, taintedArrayElements, un
21819
22195
  }
21820
22196
  return flows;
21821
22197
  }
21822
- function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines) {
22198
+ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines, tainted, code) {
21823
22199
  const flows = [];
21824
22200
  const paramSourcesByMethod = new Map;
21825
22201
  for (const source of sources) {
@@ -21865,6 +22241,9 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
21865
22241
  if (paramSource) {
21866
22242
  const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line);
21867
22243
  if (!exists) {
22244
+ if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line)) {
22245
+ continue;
22246
+ }
21868
22247
  flows.push({
21869
22248
  source_line: paramSource.line,
21870
22249
  sink_line: sink.line,
@@ -21885,7 +22264,28 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
21885
22264
  }
21886
22265
  return flows;
21887
22266
  }
21888
- 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) {
21889
22289
  const flows = [];
21890
22290
  const sourcesWithVar = sources.filter((s) => typeof s.variable === "string" && s.variable.length > 0);
21891
22291
  const aliasSanitizedFor = new Map;
@@ -21940,10 +22340,10 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
21940
22340
  continue;
21941
22341
  const rhs = rhsMatch[1];
21942
22342
  for (const san of lineSans) {
21943
- const sanMatch = san.method.match(/^(?:(\w+)\.)?(\w+)\(\)$/);
22343
+ const sanMatch = san.method.match(/(\w+)\(\)$/);
21944
22344
  if (!sanMatch)
21945
22345
  continue;
21946
- const sanName = sanMatch[1] ? `${sanMatch[1]}.${sanMatch[2]}` : sanMatch[2];
22346
+ const sanName = sanMatch[1];
21947
22347
  if (!rhs.includes(`${sanName}(`))
21948
22348
  continue;
21949
22349
  let set = aliasSanitizedFor.get(varName);
@@ -22015,6 +22415,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22015
22415
  if (aliasSanitizedFor.get(source.variable)?.has(sink.type)) {
22016
22416
  break;
22017
22417
  }
22418
+ if (typeof code === "string" && isReassignedToLiteralBetween(code, source.variable, source.line, sink.line)) {
22419
+ break;
22420
+ }
22018
22421
  flows.push({
22019
22422
  source_line: source.line,
22020
22423
  sink_line: sink.line,
@@ -22049,6 +22452,9 @@ function detectExpressionScanFlows(calls, sources, sinks, sanitizers, unreachabl
22049
22452
  for (const source of colocSources) {
22050
22453
  if (!canSourceReachSink(source.type, sink.type))
22051
22454
  continue;
22455
+ if (source.type === "file_input" && sink.type === "path_traversal" && sink.method && source.location.includes(`${sink.method}(`)) {
22456
+ continue;
22457
+ }
22052
22458
  if (flows.some((f) => f.source_line === source.line && f.sink_line === sink.line && f.sink_type === sink.type))
22053
22459
  continue;
22054
22460
  flows.push({
@@ -26292,6 +26698,25 @@ var JS_ROUTE_METHODS = new Set([
26292
26698
  "head",
26293
26699
  "options"
26294
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/;
26295
26720
 
26296
26721
  class SecurityHeadersPass {
26297
26722
  name = "security-headers";
@@ -26322,6 +26747,7 @@ class SecurityHeadersPass {
26322
26747
  list.push(call);
26323
26748
  }
26324
26749
  const hasHandler = detectHandler(graph, calls);
26750
+ const hasGlobalMiddleware = detectGlobalSecurityMiddleware(graph, calls);
26325
26751
  for (const rule of this.rules) {
26326
26752
  const headerKey = rule.header.toLowerCase();
26327
26753
  const writes = writtenHeaders.get(headerKey) ?? [];
@@ -26330,6 +26756,8 @@ class SecurityHeadersPass {
26330
26756
  continue;
26331
26757
  if (rule.requiresHandler !== false && !hasHandler)
26332
26758
  continue;
26759
+ if (hasGlobalMiddleware)
26760
+ continue;
26333
26761
  ctx.addFinding({
26334
26762
  id: `${rule.rule_id}-${file}`,
26335
26763
  pass: this.name,
@@ -26504,6 +26932,28 @@ function detectHandler(graph, calls) {
26504
26932
  }
26505
26933
  return false;
26506
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
+ }
26507
26957
  function checkInheritedCorsHeaders(fileAnalyses, typeHierarchy, sourceLines) {
26508
26958
  const findings = [];
26509
26959
  for (const { file: parentFile, analysis: parentIR } of fileAnalyses) {
@@ -29990,7 +30440,7 @@ var colors = {
29990
30440
  };
29991
30441
 
29992
30442
  // src/version.ts
29993
- var version = "3.56.0";
30443
+ var version = "3.58.0";
29994
30444
 
29995
30445
  // src/formatters.ts
29996
30446
  var SINK_SEVERITY = {
@@ -30764,7 +31214,8 @@ var TEST_PATTERNS = [
30764
31214
  /_test\.py$/,
30765
31215
  /_tests\.py$/,
30766
31216
  /test_.*\.py$/,
30767
- /_test\.rs$/
31217
+ /_test\.rs$/,
31218
+ /_test\.go$/
30768
31219
  ];
30769
31220
  function isTestFile2(filePath) {
30770
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.56.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.56.0"
68
+ "circle-ir": "^3.58.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.5.0",