cognium-dev 3.175.0 → 3.177.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 +325 -55
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -6725,8 +6725,16 @@ function extractGoCallInfo(node) {
6725
6725
  if (funcNode.type === "selector_expression") {
6726
6726
  const operand = funcNode.childForFieldName("operand");
6727
6727
  const field = funcNode.childForFieldName("field");
6728
- receiver = operand ? getNodeText(operand) : null;
6729
6728
  methodName = field ? getNodeText(field) : getNodeText(funcNode);
6729
+ if (operand) {
6730
+ const opText = getNodeText(operand);
6731
+ if (operand.type === "identifier") {
6732
+ const resolved = resolveGoLocalReceiverType(opText, node);
6733
+ receiver = resolved !== null ? resolved : opText;
6734
+ } else {
6735
+ receiver = opText;
6736
+ }
6737
+ }
6730
6738
  } else if (funcNode.type === "identifier") {
6731
6739
  methodName = getNodeText(funcNode);
6732
6740
  } else {
@@ -6806,6 +6814,89 @@ function findGoEnclosingFunction(node) {
6806
6814
  }
6807
6815
  return null;
6808
6816
  }
6817
+ function resolveGoLocalReceiverType(operandName, callNode) {
6818
+ let cur = callNode.parent;
6819
+ while (cur) {
6820
+ if (cur.type === "method_declaration") {
6821
+ const receiver = cur.childForFieldName("receiver");
6822
+ if (receiver) {
6823
+ const t = extractGoParamTypeForName(receiver, operandName);
6824
+ if (t !== null)
6825
+ return t;
6826
+ }
6827
+ const params = cur.childForFieldName("parameters");
6828
+ if (params) {
6829
+ const t = extractGoParamTypeForName(params, operandName);
6830
+ if (t !== null)
6831
+ return t;
6832
+ }
6833
+ return null;
6834
+ }
6835
+ if (cur.type === "function_declaration" || cur.type === "func_literal") {
6836
+ const params = cur.childForFieldName("parameters");
6837
+ if (params) {
6838
+ const t = extractGoParamTypeForName(params, operandName);
6839
+ if (t !== null)
6840
+ return t;
6841
+ }
6842
+ return null;
6843
+ }
6844
+ cur = cur.parent;
6845
+ }
6846
+ return null;
6847
+ }
6848
+ function extractGoParamTypeForName(list, operandName) {
6849
+ for (let i2 = 0;i2 < list.namedChildCount; i2++) {
6850
+ const child = list.namedChild(i2);
6851
+ if (!child || child.type !== "parameter_declaration")
6852
+ continue;
6853
+ const typeNode = child.childForFieldName("type");
6854
+ if (!typeNode)
6855
+ continue;
6856
+ let matched = false;
6857
+ for (let j = 0;j < child.namedChildCount; j++) {
6858
+ const c = child.namedChild(j);
6859
+ if (!c)
6860
+ continue;
6861
+ if (c.type === "identifier" && getNodeText(c) === operandName) {
6862
+ matched = true;
6863
+ break;
6864
+ }
6865
+ if (c.type === "identifier_list") {
6866
+ for (let k = 0;k < c.namedChildCount; k++) {
6867
+ const id = c.namedChild(k);
6868
+ if (id && id.type === "identifier" && getNodeText(id) === operandName) {
6869
+ matched = true;
6870
+ break;
6871
+ }
6872
+ }
6873
+ if (matched)
6874
+ break;
6875
+ }
6876
+ }
6877
+ if (!matched)
6878
+ continue;
6879
+ return extractGoTypeLastSegment(typeNode);
6880
+ }
6881
+ return null;
6882
+ }
6883
+ function extractGoTypeLastSegment(typeNode) {
6884
+ if (typeNode.type === "pointer_type") {
6885
+ const inner = typeNode.namedChild(0);
6886
+ return inner ? extractGoTypeLastSegment(inner) : null;
6887
+ }
6888
+ if (typeNode.type === "qualified_type") {
6889
+ const name2 = typeNode.childForFieldName("name");
6890
+ if (name2)
6891
+ return getNodeText(name2);
6892
+ const last = typeNode.namedChild(typeNode.namedChildCount - 1);
6893
+ return last ? getNodeText(last) : null;
6894
+ }
6895
+ if (typeNode.type === "type_identifier" || typeNode.type === "identifier") {
6896
+ return getNodeText(typeNode);
6897
+ }
6898
+ return null;
6899
+ }
6809
6900
  // ../circle-ir/dist/core/extractors/imports.js
6810
6901
  function detectLanguage2(tree) {
6811
6902
  const root = tree.rootNode;
@@ -7583,10 +7674,10 @@ function buildCFG(tree, language, cache) {
7583
7674
  const allEdges = [];
7584
7675
  let blockIdCounter = 0;
7585
7676
  if (effectiveLanguage === "bash") {
7586
- return buildBashCFG(tree, blockIdCounter);
7677
+ return buildBashCFG(tree, blockIdCounter, cache);
7587
7678
  }
7588
7679
  if (effectiveLanguage === "go") {
7589
- return buildGoCFG(tree, blockIdCounter);
7680
+ return buildGoCFG(tree, blockIdCounter, cache);
7590
7681
  }
7591
7682
  if (isJavaScript) {
7592
7683
  const functions = [
@@ -7946,11 +8037,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
7946
8037
  nextId: currentId
7947
8038
  };
7948
8039
  }
7949
- function buildBashCFG(tree, startId) {
8040
+ function buildBashCFG(tree, startId, cache) {
7950
8041
  const allBlocks = [];
7951
8042
  const allEdges = [];
7952
8043
  let blockIdCounter = startId;
7953
- const functions = findNodes(tree.rootNode, "function_definition");
8044
+ const functions = getNodesFromCache(tree.rootNode, "function_definition", cache);
7954
8045
  for (const func2 of functions) {
7955
8046
  const body2 = func2.childForFieldName("body");
7956
8047
  if (!body2)
@@ -8068,12 +8159,12 @@ function isStatement(node, isJavaScript) {
8068
8159
  ]);
8069
8160
  return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
8070
8161
  }
8071
- function buildGoCFG(tree, blockIdCounter) {
8162
+ function buildGoCFG(tree, blockIdCounter, cache) {
8072
8163
  const allBlocks = [];
8073
8164
  const allEdges = [];
8074
8165
  const functions = [
8075
- ...findNodes(tree.rootNode, "function_declaration"),
8076
- ...findNodes(tree.rootNode, "method_declaration")
8166
+ ...getNodesFromCache(tree.rootNode, "function_declaration", cache),
8167
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache)
8077
8168
  ];
8078
8169
  for (const func2 of functions) {
8079
8170
  const body2 = func2.childForFieldName("body");
@@ -10568,6 +10659,40 @@ var OPEN_REDIRECT_FRAMEWORK_SINKS = [
10568
10659
  { method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
10569
10660
  { method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
10570
10661
  ];
10662
+ var DESERIALIZATION_FRAMEWORK_SINKS = [
10663
+ { method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10664
+ { method: "load", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10665
+ { method: "loads", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10666
+ { method: "load", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10667
+ { method: "loads", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10668
+ { method: "load", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10669
+ { method: "loads", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10670
+ { method: "load", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10671
+ { method: "decode", class: "jsonpickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10672
+ { method: "Decode", class: "Decoder", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
10673
+ { method: "Unmarshal", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
10674
+ { method: "unserialize", class: "nodeSerialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
10675
+ ];
10676
+ var NOSQL_FRAMEWORK_SINKS = [
10677
+ { method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10678
+ { method: "find_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10679
+ { method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10680
+ { method: "update_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
10681
+ { method: "update_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
10682
+ { method: "delete_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10683
+ { method: "delete_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10684
+ { method: "count_documents", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10685
+ { method: "find", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10686
+ { method: "findOne", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10687
+ { method: "findAll", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10688
+ { method: "find", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10689
+ { method: "aggregate", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10690
+ { method: "Find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
10691
+ { method: "FindOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
10692
+ { method: "UpdateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
10693
+ { method: "UpdateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
10694
+ { method: "DeleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] }
10695
+ ];
10571
10696
  var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
10572
10697
  { method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
10573
10698
  { method: "set_many", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [0], languages: ["python"] },
@@ -11653,7 +11778,9 @@ var DEFAULT_SINKS = [
11653
11778
  { method: "Generate", class: "LLM", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
11654
11779
  { method: "GenerateContent", class: "Model", type: "prompt_injection", cwe: "CWE-1427", severity: "high", arg_positions: [0, 1, 2, 3], languages: ["go"] },
11655
11780
  ...OPEN_REDIRECT_FRAMEWORK_SINKS,
11656
- ...TRUST_BOUNDARY_FRAMEWORK_SINKS
11781
+ ...TRUST_BOUNDARY_FRAMEWORK_SINKS,
11782
+ ...DESERIALIZATION_FRAMEWORK_SINKS,
11783
+ ...NOSQL_FRAMEWORK_SINKS
11657
11784
  ];
11658
11785
  var DEFAULT_SANITIZERS = [
11659
11786
  { method: "setString", class: "PreparedStatement", removes: ["sql_injection"] },
@@ -11948,6 +12075,84 @@ var DEFAULT_HEADER_RULES = [
11948
12075
  note: "Fires when the value is not a string literal (likely reflected from request)"
11949
12076
  }
11950
12077
  ];
12078
+ // ../circle-ir/dist/analysis/arg-type-resolver.js
12079
+ function findEnclosingType(callInMethod, types) {
12080
+ if (!callInMethod)
12081
+ return null;
12082
+ for (const t of types) {
12083
+ for (const m of t.methods) {
12084
+ if (m.name === callInMethod)
12085
+ return t;
12086
+ }
12087
+ }
12088
+ return null;
12089
+ }
12090
+ function resolveIdentifierType(name2, callInMethod, types) {
12091
+ if (!name2)
12092
+ return null;
12093
+ const enclosing = findEnclosingType(callInMethod, types);
12094
+ if (enclosing) {
12095
+ for (const m of enclosing.methods) {
12096
+ if (m.name !== callInMethod)
12097
+ continue;
12098
+ for (const p of m.parameters) {
12099
+ if (p.name === name2 && p.type)
12100
+ return p.type;
12101
+ }
12102
+ }
12103
+ for (const f of enclosing.fields) {
12104
+ if (f.name === name2 && f.type)
12105
+ return f.type;
12106
+ }
12107
+ return null;
12108
+ }
12109
+ for (const t of types) {
12110
+ for (const m of t.methods) {
12111
+ for (const p of m.parameters) {
12112
+ if (p.name === name2 && p.type)
12113
+ return p.type;
12114
+ }
12115
+ }
12116
+ for (const f of t.fields) {
12117
+ if (f.name === name2 && f.type)
12118
+ return f.type;
12119
+ }
12120
+ }
12121
+ return null;
12122
+ }
12123
+ function resolveCallReturnType(calleeName, callInMethod, types) {
12124
+ if (!calleeName)
12125
+ return null;
12126
+ const enclosing = findEnclosingType(callInMethod, types);
12127
+ if (enclosing) {
12128
+ for (const m of enclosing.methods) {
12129
+ if (m.name === calleeName && m.return_type)
12130
+ return m.return_type;
12131
+ }
12132
+ }
12133
+ for (const t of types) {
12134
+ for (const m of t.methods) {
12135
+ if (m.name === calleeName && m.return_type)
12136
+ return m.return_type;
12137
+ }
12138
+ }
12139
+ return null;
12140
+ }
12141
+ function isBoundedClassType(rawType) {
12142
+ if (!rawType)
12143
+ return false;
12144
+ const stripped = rawType.trim().replace(/^(?:java\.lang\.)+/, "");
12145
+ return /^Class(?:\s*<[\s\S]*>)?$/.test(stripped);
12146
+ }
12147
+ function isArgvContainerType(rawType) {
12148
+ if (!rawType)
12149
+ return false;
12150
+ const s = rawType.trim().replace(/^(?:java\.util\.|java\.lang\.)+/, "");
12151
+ if (/^String\s*(?:\[\s*\]|\.\.\.)$/.test(s))
12152
+ return true;
12153
+ return /^(?:List|ArrayList|LinkedList|Collection|Iterable|Deque|Queue)\s*<\s*(?:String|CharSequence|java\.lang\.String)\b/.test(s);
12154
+ }
12155
+
11951
12156
  // ../circle-ir/dist/analysis/taint-matcher.js
11952
12157
  var PYTHON_TAINTED_PATTERNS = [
11953
12158
  { pattern: /\brequest\.args\b/, sourceType: "http_param" },
@@ -11971,7 +12176,7 @@ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy,
11971
12176
  const sources = findSources(calls, types, config.sources, sourceLines, language);
11972
12177
  let sinkPatterns = expandPromisifyAliases(config.sinks, sourceLines, language);
11973
12178
  sinkPatterns = expandIndirectEvalAliases(sinkPatterns, sourceLines, language);
11974
- const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines);
12179
+ const sinks = findSinks(calls, sinkPatterns, typeHierarchy, language, sourceLines, types);
11975
12180
  const sanitizers = findSanitizers(calls, types, config.sanitizers, sourceLines);
11976
12181
  return { sources, sinks, sanitizers };
11977
12182
  }
@@ -12088,11 +12293,9 @@ function attachSourceLineCode(sources, sinks, code) {
12088
12293
  }
12089
12294
  function findSources(calls, types, patterns, sourceLines, language) {
12090
12295
  const sources = [];
12296
+ const patternsForLanguage = language === undefined ? patterns : patterns.filter((p) => !p.languages || p.languages.length === 0 || p.languages.includes(language));
12091
12297
  for (const call of calls) {
12092
- for (const pattern of patterns) {
12093
- if (pattern.languages && pattern.languages.length > 0 && language !== undefined && !pattern.languages.includes(language)) {
12094
- continue;
12095
- }
12298
+ for (const pattern of patternsForLanguage) {
12096
12299
  if (matchesSourcePattern(call, pattern)) {
12097
12300
  sources.push({
12098
12301
  type: pattern.type,
@@ -12108,11 +12311,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
12108
12311
  for (const type of types) {
12109
12312
  for (const method of type.methods) {
12110
12313
  for (const param of method.parameters) {
12111
- for (const pattern of patterns) {
12314
+ for (const pattern of patternsForLanguage) {
12112
12315
  if (pattern.annotation && pattern.param_tainted) {
12113
- if (pattern.languages && pattern.languages.length > 0 && language !== undefined && !pattern.languages.includes(language)) {
12114
- continue;
12115
- }
12116
12316
  if (matchesAnnotation(param.annotations, pattern.annotation)) {
12117
12317
  const paramLine = param.line ?? method.start_line;
12118
12318
  sources.push({
@@ -12131,12 +12331,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
12131
12331
  }
12132
12332
  for (const type of types) {
12133
12333
  for (const method of type.methods) {
12134
- for (const pattern of patterns) {
12334
+ for (const pattern of patternsForLanguage) {
12135
12335
  if (!pattern.method_annotation)
12136
12336
  continue;
12137
- if (pattern.languages && pattern.languages.length > 0 && language !== undefined && !pattern.languages.includes(language)) {
12138
- continue;
12139
- }
12140
12337
  if (!matchesAnnotation(method.annotations, pattern.method_annotation))
12141
12338
  continue;
12142
12339
  for (const param of method.parameters) {
@@ -12587,7 +12784,7 @@ function isSafeRustCommandCall(call, pattern, language) {
12587
12784
  return false;
12588
12785
  }
12589
12786
  var CLASS_LITERAL_RE = /^(?:[A-Za-z_][\w]*\.)*[A-Z][\w]*(?:\[\])*\.class$/;
12590
- function argIsClassLiteral(call, position) {
12787
+ function argIsClassLiteral(call, position, types) {
12591
12788
  const arg = call.arguments.find((a) => a.position === position);
12592
12789
  if (!arg)
12593
12790
  return false;
@@ -12596,7 +12793,28 @@ function argIsClassLiteral(call, position) {
12596
12793
  return false;
12597
12794
  if (CLASS_LITERAL_RE.test(expr))
12598
12795
  return true;
12599
- return TYPE_TOKEN_RE.test(expr);
12796
+ if (TYPE_TOKEN_RE.test(expr))
12797
+ return true;
12798
+ if (!types || types.length === 0)
12799
+ return false;
12800
+ const argExpr = (arg.expression ?? "").trim();
12801
+ const varName = arg.variable ?? "";
12802
+ const isBareIdentifier = varName !== "" && argExpr === varName && !/[(.]/.test(argExpr);
12803
+ if (isBareIdentifier) {
12804
+ const t = resolveIdentifierType(varName, call.in_method, types);
12805
+ if (isBoundedClassType(t))
12806
+ return true;
12807
+ }
12808
+ const isCallExpr = varName !== "" && new RegExp(`^${escapeRe(varName)}\\s*\\(`).test(argExpr);
12809
+ if (isCallExpr) {
12810
+ const t = resolveCallReturnType(varName, call.in_method, types);
12811
+ if (isBoundedClassType(t))
12812
+ return true;
12813
+ }
12814
+ return false;
12815
+ }
12816
+ function escapeRe(s) {
12817
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12600
12818
  }
12601
12819
  var TYPE_TOKEN_RE = /^new\s+(?:TypeReference|TypeToken)\s*<[\s\S]*>\s*\(\s*\)\s*\{\s*\}$/;
12602
12820
  function argIsStringLiteral(call, position) {
@@ -12779,10 +12997,11 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
12779
12997
  }
12780
12998
  return false;
12781
12999
  }
12782
- function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
13000
+ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
13001
+ const patternsForLanguage = language === undefined ? patterns : patterns.filter((p) => !p.languages || p.languages.length === 0 || p.languages.includes(language));
12783
13002
  const sinkMap = new Map;
12784
13003
  for (const call of calls) {
12785
- for (const pattern of patterns) {
13004
+ for (const pattern of patternsForLanguage) {
12786
13005
  if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
12787
13006
  if (isParameterizedQueryCall(call, pattern)) {
12788
13007
  continue;
@@ -12799,7 +13018,7 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines) {
12799
13018
  if (isSafeJSChildProcessCall(call, pattern, language)) {
12800
13019
  continue;
12801
13020
  }
12802
- if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at)) {
13021
+ if (pattern.safe_if_class_literal_at !== undefined && argIsClassLiteral(call, pattern.safe_if_class_literal_at, types)) {
12803
13022
  continue;
12804
13023
  }
12805
13024
  if (pattern.safe_if_string_literal_at !== undefined && argIsStringLiteral(call, pattern.safe_if_string_literal_at)) {
@@ -13184,7 +13403,21 @@ function matchesAnnotation(annotations, targetAnnotation) {
13184
13403
  }
13185
13404
  return false;
13186
13405
  }
13406
+ var RECEIVER_MIGHT_BE_CLASS_CACHE = new Map;
13407
+ var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
13187
13408
  function receiverMightBeClass(receiver, className) {
13409
+ const key = receiver + "\x00" + className;
13410
+ const cached = RECEIVER_MIGHT_BE_CLASS_CACHE.get(key);
13411
+ if (cached !== undefined)
13412
+ return cached;
13413
+ const result = receiverMightBeClassImpl(receiver, className);
13414
+ if (RECEIVER_MIGHT_BE_CLASS_CACHE.size >= RECEIVER_MIGHT_BE_CLASS_CACHE_CAP) {
13415
+ RECEIVER_MIGHT_BE_CLASS_CACHE.clear();
13416
+ }
13417
+ RECEIVER_MIGHT_BE_CLASS_CACHE.set(key, result);
13418
+ return result;
13419
+ }
13420
+ function receiverMightBeClassImpl(receiver, className) {
13188
13421
  if (className.startsWith("*") && className.length > 1) {
13189
13422
  const suffix = className.slice(1).toLowerCase();
13190
13423
  let simpleReceiver = receiver;
@@ -16040,8 +16273,16 @@ class AnalysisPipeline {
16040
16273
  }
16041
16274
  }
16042
16275
  // ../circle-ir/dist/analysis/dfg-walk.js
16276
+ var walkBackwardDefsMemo = new WeakMap;
16043
16277
  function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
16044
16278
  const maxHops = options.maxHops ?? 32;
16279
+ let perFile = walkBackwardDefsMemo.get(chainsByToDef);
16280
+ if (perFile !== undefined) {
16281
+ const key = `${startDefId}|${maxHops}`;
16282
+ const hit = perFile.get(key);
16283
+ if (hit !== undefined)
16284
+ return hit;
16285
+ }
16045
16286
  const visited = new Set;
16046
16287
  const lines = new Set;
16047
16288
  let hopCapReached = false;
@@ -16075,7 +16316,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
16075
16316
  queue.push(fromId);
16076
16317
  }
16077
16318
  }
16078
- return { visited, lines, hopCapReached };
16319
+ const result = { visited, lines, hopCapReached };
16320
+ if (perFile === undefined) {
16321
+ perFile = new Map;
16322
+ walkBackwardDefsMemo.set(chainsByToDef, perFile);
16323
+ }
16324
+ perFile.set(`${startDefId}|${maxHops}`, result);
16325
+ return result;
16079
16326
  }
16080
16327
 
16081
16328
  // ../circle-ir/dist/analysis/sanitizer-index.js
@@ -16922,12 +17169,12 @@ class ConstantPropagator {
16922
17169
  this.constructorParamPositions.clear();
16923
17170
  this.safePatternFieldsCache = null;
16924
17171
  this.isTaintedExpressionCache = null;
16925
- this.collectClassFields(tree.rootNode);
17172
+ const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
16926
17173
  for (const methodName of sanitizerMethods) {
16927
17174
  this.methodReturnsSanitized.add(methodName);
16928
17175
  }
16929
17176
  this.evaluator = new ExpressionEvaluator(this.source, (name2) => this.lookupSymbol(name2));
16930
- this.analyzeMethodReturns(tree.rootNode);
17177
+ this.analyzeMethodReturns(prePassMethods);
16931
17178
  this.seedPythonModuleConstants(tree.rootNode);
16932
17179
  this.visit(tree.rootNode);
16933
17180
  this.refineTaintFromConstants();
@@ -16979,8 +17226,7 @@ class ConstantPropagator {
16979
17226
  isLineReachable(line) {
16980
17227
  return !this.unreachableLines.has(line);
16981
17228
  }
16982
- analyzeMethodReturns(root) {
16983
- const methods = this.findAllMethods(root);
17229
+ analyzeMethodReturns(methods) {
16984
17230
  for (const method of methods) {
16985
17231
  const methodName = this.getMethodName(method);
16986
17232
  if (!methodName)
@@ -17245,12 +17491,16 @@ class ConstantPropagator {
17245
17491
  };
17246
17492
  return findAssignments(methodBody);
17247
17493
  }
17248
- collectClassFields(root) {
17494
+ collectClassFieldsAndMethods(root) {
17495
+ const methods = [];
17249
17496
  const stack = [root];
17250
17497
  while (stack.length > 0) {
17251
17498
  const n = stack.pop();
17252
17499
  if (!n)
17253
17500
  continue;
17501
+ if (n.type === "method_declaration" || n.type === "function_declaration") {
17502
+ methods.push(n);
17503
+ }
17254
17504
  if (n.type === "class_body") {
17255
17505
  for (const child of n.children) {
17256
17506
  if (child.type === "field_declaration") {
@@ -17264,14 +17514,17 @@ class ConstantPropagator {
17264
17514
  }
17265
17515
  }
17266
17516
  }
17267
- stack.push(child);
17517
+ if (child)
17518
+ stack.push(child);
17268
17519
  }
17269
17520
  continue;
17270
17521
  }
17271
17522
  for (const child of n.children) {
17272
- stack.push(child);
17523
+ if (child)
17524
+ stack.push(child);
17273
17525
  }
17274
17526
  }
17527
+ return methods;
17275
17528
  }
17276
17529
  fieldDeclHasPrimitiveLiteralValue(node) {
17277
17530
  const primitive = new Set([
@@ -17408,23 +17661,6 @@ class ConstantPropagator {
17408
17661
  this.symbols.set(name2, value);
17409
17662
  }
17410
17663
  }
17411
- findAllMethods(node) {
17412
- const methods = [];
17413
- const stack = [node];
17414
- while (stack.length > 0) {
17415
- const n = stack.pop();
17416
- if (!n)
17417
- continue;
17418
- if (n.type === "method_declaration" || n.type === "function_declaration") {
17419
- methods.push(n);
17420
- }
17421
- for (const child of n.children) {
17422
- if (child)
17423
- stack.push(child);
17424
- }
17425
- }
17426
- return methods;
17427
- }
17428
17664
  getMethodName(method) {
17429
17665
  const nameNode = method.childForFieldName("name");
17430
17666
  if (nameNode) {
@@ -30796,6 +31032,9 @@ class MyBatisAnnotationSqlSinkPass {
30796
31032
  }
30797
31033
 
30798
31034
  // ../circle-ir/dist/analysis/passes/sink-filter-pass.js
31035
+ function escapeReSf(s) {
31036
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31037
+ }
30799
31038
  var JS_XSS_SANITIZERS = [
30800
31039
  /\bDOMPurify\.sanitize\s*\(/,
30801
31040
  /\bsanitizeHtml\s*\(/,
@@ -30845,6 +31084,12 @@ var DATA_PARSER_TYPES = new Set([
30845
31084
  "OptionParser",
30846
31085
  "CmdLineParser"
30847
31086
  ]);
31087
+ var JAVA_EVAL_PARSER_DENYLIST = new Set([
31088
+ "GroovyShell",
31089
+ "GroovyClassLoader",
31090
+ "ScriptEngine",
31091
+ "CronParser"
31092
+ ]);
30848
31093
  var COMPILED_TEMPLATE_TYPES = new Set([
30849
31094
  "Template",
30850
31095
  "JetTemplate",
@@ -31314,8 +31559,13 @@ class SinkFilterPass {
31314
31559
  const method = sink.method ?? receiverMatch?.[2];
31315
31560
  if (method === "parse" && receiver) {
31316
31561
  const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
31317
- if (recvType && DATA_PARSER_TYPES.has(recvType))
31318
- return false;
31562
+ if (recvType) {
31563
+ if (DATA_PARSER_TYPES.has(recvType))
31564
+ return false;
31565
+ if (recvType.endsWith("Parser") && !JAVA_EVAL_PARSER_DENYLIST.has(recvType)) {
31566
+ return false;
31567
+ }
31568
+ }
31319
31569
  }
31320
31570
  if ((method === "render" || method === "process" || method === "merge" || method === "renderTo") && receiver) {
31321
31571
  const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
@@ -31425,6 +31675,8 @@ class SinkFilterPass {
31425
31675
  if (language === "java") {
31426
31676
  const sourceLines = ctx.code.split(`
31427
31677
  `);
31678
+ const irCalls = ctx.graph.ir.calls;
31679
+ const irTypes = ctx.graph.ir.types;
31428
31680
  filtered = filtered.filter((sink) => {
31429
31681
  if (sink.type !== "command_injection")
31430
31682
  return true;
@@ -31435,6 +31687,24 @@ class SinkFilterPass {
31435
31687
  return true;
31436
31688
  if (PROCESS_BUILDER_ARGV_FORM_RE.test(sinkLineText))
31437
31689
  return false;
31690
+ const ctorCall = irCalls.find((c) => c.method_name === "ProcessBuilder" && c.location.line === sink.line && (c.receiver === null || c.receiver === undefined));
31691
+ if (!ctorCall || ctorCall.arguments.length === 0)
31692
+ return true;
31693
+ const arg0 = ctorCall.arguments[0];
31694
+ if (!arg0)
31695
+ return true;
31696
+ const argExpr = (arg0.expression ?? "").trim();
31697
+ const varName = arg0.variable ?? "";
31698
+ if (varName !== "" && argExpr === varName && !/[(.]/.test(argExpr)) {
31699
+ const t = resolveIdentifierType(varName, ctorCall.in_method, irTypes);
31700
+ if (isArgvContainerType(t))
31701
+ return false;
31702
+ }
31703
+ if (varName !== "" && new RegExp(`^${escapeReSf(varName)}\\s*\\(`).test(argExpr)) {
31704
+ const t = resolveCallReturnType(varName, ctorCall.in_method, irTypes);
31705
+ if (isArgvContainerType(t))
31706
+ return false;
31707
+ }
31438
31708
  return true;
31439
31709
  });
31440
31710
  }
@@ -44796,7 +45066,7 @@ var colors = {
44796
45066
  };
44797
45067
 
44798
45068
  // src/version.ts
44799
- var version = "3.175.0";
45069
+ var version = "3.177.0";
44800
45070
 
44801
45071
  // src/formatters.ts
44802
45072
  var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognium-dev",
3
- "version": "3.175.0",
3
+ "version": "3.177.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",
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@cognium/project-profile-detect": "^1.1.0",
69
- "circle-ir": "^3.175.0"
69
+ "circle-ir": "^3.177.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",