cognium-dev 3.176.0 → 3.178.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 +357 -51
  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"] },
@@ -12166,11 +12293,9 @@ function attachSourceLineCode(sources, sinks, code) {
12166
12293
  }
12167
12294
  function findSources(calls, types, patterns, sourceLines, language) {
12168
12295
  const sources = [];
12296
+ const patternsForLanguage = language === undefined ? patterns : patterns.filter((p) => !p.languages || p.languages.length === 0 || p.languages.includes(language));
12169
12297
  for (const call of calls) {
12170
- for (const pattern of patterns) {
12171
- if (pattern.languages && pattern.languages.length > 0 && language !== undefined && !pattern.languages.includes(language)) {
12172
- continue;
12173
- }
12298
+ for (const pattern of patternsForLanguage) {
12174
12299
  if (matchesSourcePattern(call, pattern)) {
12175
12300
  sources.push({
12176
12301
  type: pattern.type,
@@ -12186,11 +12311,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
12186
12311
  for (const type of types) {
12187
12312
  for (const method of type.methods) {
12188
12313
  for (const param of method.parameters) {
12189
- for (const pattern of patterns) {
12314
+ for (const pattern of patternsForLanguage) {
12190
12315
  if (pattern.annotation && pattern.param_tainted) {
12191
- if (pattern.languages && pattern.languages.length > 0 && language !== undefined && !pattern.languages.includes(language)) {
12192
- continue;
12193
- }
12194
12316
  if (matchesAnnotation(param.annotations, pattern.annotation)) {
12195
12317
  const paramLine = param.line ?? method.start_line;
12196
12318
  sources.push({
@@ -12209,12 +12331,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
12209
12331
  }
12210
12332
  for (const type of types) {
12211
12333
  for (const method of type.methods) {
12212
- for (const pattern of patterns) {
12334
+ for (const pattern of patternsForLanguage) {
12213
12335
  if (!pattern.method_annotation)
12214
12336
  continue;
12215
- if (pattern.languages && pattern.languages.length > 0 && language !== undefined && !pattern.languages.includes(language)) {
12216
- continue;
12217
- }
12218
12337
  if (!matchesAnnotation(method.annotations, pattern.method_annotation))
12219
12338
  continue;
12220
12339
  for (const param of method.parameters) {
@@ -12879,9 +12998,10 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
12879
12998
  return false;
12880
12999
  }
12881
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));
12882
13002
  const sinkMap = new Map;
12883
13003
  for (const call of calls) {
12884
- for (const pattern of patterns) {
13004
+ for (const pattern of patternsForLanguage) {
12885
13005
  if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
12886
13006
  if (isParameterizedQueryCall(call, pattern)) {
12887
13007
  continue;
@@ -13283,7 +13403,21 @@ function matchesAnnotation(annotations, targetAnnotation) {
13283
13403
  }
13284
13404
  return false;
13285
13405
  }
13406
+ var RECEIVER_MIGHT_BE_CLASS_CACHE = new Map;
13407
+ var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
13286
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) {
13287
13421
  if (className.startsWith("*") && className.length > 1) {
13288
13422
  const suffix = className.slice(1).toLowerCase();
13289
13423
  let simpleReceiver = receiver;
@@ -16139,8 +16273,16 @@ class AnalysisPipeline {
16139
16273
  }
16140
16274
  }
16141
16275
  // ../circle-ir/dist/analysis/dfg-walk.js
16276
+ var walkBackwardDefsMemo = new WeakMap;
16142
16277
  function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
16143
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
+ }
16144
16286
  const visited = new Set;
16145
16287
  const lines = new Set;
16146
16288
  let hopCapReached = false;
@@ -16174,7 +16316,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
16174
16316
  queue.push(fromId);
16175
16317
  }
16176
16318
  }
16177
- 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;
16178
16326
  }
16179
16327
 
16180
16328
  // ../circle-ir/dist/analysis/sanitizer-index.js
@@ -17021,12 +17169,12 @@ class ConstantPropagator {
17021
17169
  this.constructorParamPositions.clear();
17022
17170
  this.safePatternFieldsCache = null;
17023
17171
  this.isTaintedExpressionCache = null;
17024
- this.collectClassFields(tree.rootNode);
17172
+ const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
17025
17173
  for (const methodName of sanitizerMethods) {
17026
17174
  this.methodReturnsSanitized.add(methodName);
17027
17175
  }
17028
17176
  this.evaluator = new ExpressionEvaluator(this.source, (name2) => this.lookupSymbol(name2));
17029
- this.analyzeMethodReturns(tree.rootNode);
17177
+ this.analyzeMethodReturns(prePassMethods);
17030
17178
  this.seedPythonModuleConstants(tree.rootNode);
17031
17179
  this.visit(tree.rootNode);
17032
17180
  this.refineTaintFromConstants();
@@ -17078,8 +17226,7 @@ class ConstantPropagator {
17078
17226
  isLineReachable(line) {
17079
17227
  return !this.unreachableLines.has(line);
17080
17228
  }
17081
- analyzeMethodReturns(root) {
17082
- const methods = this.findAllMethods(root);
17229
+ analyzeMethodReturns(methods) {
17083
17230
  for (const method of methods) {
17084
17231
  const methodName = this.getMethodName(method);
17085
17232
  if (!methodName)
@@ -17344,12 +17491,16 @@ class ConstantPropagator {
17344
17491
  };
17345
17492
  return findAssignments(methodBody);
17346
17493
  }
17347
- collectClassFields(root) {
17494
+ collectClassFieldsAndMethods(root) {
17495
+ const methods = [];
17348
17496
  const stack = [root];
17349
17497
  while (stack.length > 0) {
17350
17498
  const n = stack.pop();
17351
17499
  if (!n)
17352
17500
  continue;
17501
+ if (n.type === "method_declaration" || n.type === "function_declaration") {
17502
+ methods.push(n);
17503
+ }
17353
17504
  if (n.type === "class_body") {
17354
17505
  for (const child of n.children) {
17355
17506
  if (child.type === "field_declaration") {
@@ -17363,14 +17514,17 @@ class ConstantPropagator {
17363
17514
  }
17364
17515
  }
17365
17516
  }
17366
- stack.push(child);
17517
+ if (child)
17518
+ stack.push(child);
17367
17519
  }
17368
17520
  continue;
17369
17521
  }
17370
17522
  for (const child of n.children) {
17371
- stack.push(child);
17523
+ if (child)
17524
+ stack.push(child);
17372
17525
  }
17373
17526
  }
17527
+ return methods;
17374
17528
  }
17375
17529
  fieldDeclHasPrimitiveLiteralValue(node) {
17376
17530
  const primitive = new Set([
@@ -17507,23 +17661,6 @@ class ConstantPropagator {
17507
17661
  this.symbols.set(name2, value);
17508
17662
  }
17509
17663
  }
17510
- findAllMethods(node) {
17511
- const methods = [];
17512
- const stack = [node];
17513
- while (stack.length > 0) {
17514
- const n = stack.pop();
17515
- if (!n)
17516
- continue;
17517
- if (n.type === "method_declaration" || n.type === "function_declaration") {
17518
- methods.push(n);
17519
- }
17520
- for (const child of n.children) {
17521
- if (child)
17522
- stack.push(child);
17523
- }
17524
- }
17525
- return methods;
17526
- }
17527
17664
  getMethodName(method) {
17528
17665
  const nameNode = method.childForFieldName("name");
17529
17666
  if (nameNode) {
@@ -19046,6 +19183,50 @@ function applyLibraryApiSurfaceDowngrade(findings) {
19046
19183
  });
19047
19184
  }
19048
19185
 
19186
+ // ../circle-ir/dist/analysis/note-coalescer.js
19187
+ function coalesceNoteLevelFindings(findings) {
19188
+ if (findings.length < 2)
19189
+ return [...findings];
19190
+ const groups = new Map;
19191
+ const order = [];
19192
+ for (const f of findings) {
19193
+ const key = `${f.file}\x00${f.line}`;
19194
+ const bucket = groups.get(key);
19195
+ if (bucket) {
19196
+ bucket.push(f);
19197
+ } else {
19198
+ groups.set(key, [f]);
19199
+ order.push(key);
19200
+ }
19201
+ }
19202
+ const out2 = [];
19203
+ for (const key of order) {
19204
+ const bucket = groups.get(key);
19205
+ if (bucket.length === 1) {
19206
+ out2.push(bucket[0]);
19207
+ continue;
19208
+ }
19209
+ const allNote = bucket.every((f) => f.level === "note");
19210
+ if (!allNote) {
19211
+ for (const f of bucket)
19212
+ out2.push(f);
19213
+ continue;
19214
+ }
19215
+ const uniqueRuleIds = new Set(bucket.map((f) => f.rule_id));
19216
+ if (uniqueRuleIds.size < 2) {
19217
+ for (const f of bucket)
19218
+ out2.push(f);
19219
+ continue;
19220
+ }
19221
+ const sorted = [...bucket].sort((a, b) => a.rule_id.localeCompare(b.rule_id));
19222
+ const primary = sorted[0];
19223
+ const additional = sorted.slice(1).map((f) => f.rule_id).concat(...sorted.map((f) => f.labels ?? []));
19224
+ const uniqueLabels = Array.from(new Set(additional)).filter((l) => l !== primary.rule_id);
19225
+ out2.push({ ...primary, labels: uniqueLabels });
19226
+ }
19227
+ return out2;
19228
+ }
19229
+
19049
19230
  // ../circle-ir/dist/analysis/entry-point-detection.js
19050
19231
  var TIER_1_METHOD_ANNOTATIONS = new Set([
19051
19232
  "RequestMapping",
@@ -30947,6 +31128,12 @@ var DATA_PARSER_TYPES = new Set([
30947
31128
  "OptionParser",
30948
31129
  "CmdLineParser"
30949
31130
  ]);
31131
+ var JAVA_EVAL_PARSER_DENYLIST = new Set([
31132
+ "GroovyShell",
31133
+ "GroovyClassLoader",
31134
+ "ScriptEngine",
31135
+ "CronParser"
31136
+ ]);
30950
31137
  var COMPILED_TEMPLATE_TYPES = new Set([
30951
31138
  "Template",
30952
31139
  "JetTemplate",
@@ -31416,8 +31603,13 @@ class SinkFilterPass {
31416
31603
  const method = sink.method ?? receiverMatch?.[2];
31417
31604
  if (method === "parse" && receiver) {
31418
31605
  const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
31419
- if (recvType && DATA_PARSER_TYPES.has(recvType))
31420
- return false;
31606
+ if (recvType) {
31607
+ if (DATA_PARSER_TYPES.has(recvType))
31608
+ return false;
31609
+ if (recvType.endsWith("Parser") && !JAVA_EVAL_PARSER_DENYLIST.has(recvType)) {
31610
+ return false;
31611
+ }
31612
+ }
31421
31613
  }
31422
31614
  if ((method === "render" || method === "process" || method === "merge" || method === "renderTo") && receiver) {
31423
31615
  const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
@@ -32435,6 +32627,117 @@ class SinkSemanticsPass {
32435
32627
  }
32436
32628
  }
32437
32629
 
32630
+ // ../circle-ir/dist/analysis/dependency-versions.js
32631
+ function resolveFastjsonFromPom(pomXml) {
32632
+ if (!pomXml)
32633
+ return null;
32634
+ const propMatch = pomXml.match(/<fastjson\.version>\s*([^<\s]+)\s*<\/fastjson\.version>/);
32635
+ if (propMatch) {
32636
+ const version = propMatch[1];
32637
+ return { version, noneAutotype: /_noneautotype/i.test(version) };
32638
+ }
32639
+ const depRe = /<dependency>[\s\S]*?<\/dependency>/g;
32640
+ let m;
32641
+ while ((m = depRe.exec(pomXml)) !== null) {
32642
+ const block = m[0];
32643
+ const gid = block.match(/<groupId>\s*([^<\s]+)\s*<\/groupId>/)?.[1];
32644
+ const aid = block.match(/<artifactId>\s*([^<\s]+)\s*<\/artifactId>/)?.[1];
32645
+ if (gid !== "com.alibaba")
32646
+ continue;
32647
+ if (aid !== "fastjson" && aid !== "fastjson2")
32648
+ continue;
32649
+ const ver = block.match(/<version>\s*([^<\s]+)\s*<\/version>/)?.[1];
32650
+ if (!ver)
32651
+ continue;
32652
+ if (/^\$\{/.test(ver))
32653
+ return null;
32654
+ return { version: ver, noneAutotype: /_noneautotype/i.test(ver) };
32655
+ }
32656
+ return null;
32657
+ }
32658
+ function fileReenablesFastjsonAutotype(source) {
32659
+ if (!source)
32660
+ return false;
32661
+ return /\bsetAutoTypeSupport\s*\(\s*true\b/.test(source);
32662
+ }
32663
+ function fileEnablesJacksonPolymorphism(source) {
32664
+ if (!source)
32665
+ return false;
32666
+ if (/\benableDefaultTyping\s*\(/.test(source))
32667
+ return true;
32668
+ if (/\bactivateDefaultTyping\s*\(/.test(source))
32669
+ return true;
32670
+ if (/@JsonTypeInfo\b/.test(source))
32671
+ return true;
32672
+ return false;
32673
+ }
32674
+ function fileConfiguresSnakeYamlSafely(source) {
32675
+ if (!source)
32676
+ return false;
32677
+ if (/\bnew\s+SafeConstructor\s*\(/.test(source))
32678
+ return true;
32679
+ if (/\bSafeConstructor\s+\w+\s*=/.test(source))
32680
+ return true;
32681
+ return false;
32682
+ }
32683
+
32684
+ // ../circle-ir/dist/analysis/passes/deserialization-safety-gate-pass.js
32685
+ var FASTJSON_METHODS = new Set(["parseObject", "parse"]);
32686
+ var FASTJSON_CLASSES = new Set(["JSON", "JSONObject"]);
32687
+ var JACKSON_METHODS = new Set(["readValue", "convertValue", "treeToValue"]);
32688
+ var JACKSON_CLASSES = new Set(["ObjectMapper", "ObjectReader"]);
32689
+ var SNAKEYAML_METHODS = new Set(["load", "loadAs", "loadAll"]);
32690
+ var SNAKEYAML_CLASSES = new Set(["Yaml"]);
32691
+
32692
+ class DeserializationSafetyGatePass {
32693
+ dependencyContext;
32694
+ name = "deserialization-safety-gate";
32695
+ category = "security";
32696
+ constructor(dependencyContext) {
32697
+ this.dependencyContext = dependencyContext;
32698
+ }
32699
+ run(ctx) {
32700
+ const { graph, language, code } = ctx;
32701
+ if (language !== "java") {
32702
+ return { droppedFastjson: 0, droppedJackson: 0, droppedSnakeYaml: 0 };
32703
+ }
32704
+ const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
32705
+ const pomXml = this.dependencyContext?.java?.pomXml;
32706
+ const fastjson = pomXml ? resolveFastjsonFromPom(pomXml) : null;
32707
+ const fastjsonHardened = fastjson?.noneAutotype === true && !fileReenablesFastjsonAutotype(code);
32708
+ const jacksonSafe = !fileEnablesJacksonPolymorphism(code);
32709
+ const snakeYamlSafe = fileConfiguresSnakeYamlSafely(code);
32710
+ let droppedFastjson = 0;
32711
+ let droppedJackson = 0;
32712
+ let droppedSnakeYaml = 0;
32713
+ const kept = sinks.filter((sink) => {
32714
+ if (sink.type !== "deserialization")
32715
+ return true;
32716
+ if (!sink.method)
32717
+ return true;
32718
+ if (fastjsonHardened && FASTJSON_METHODS.has(sink.method) && (sink.class === undefined || FASTJSON_CLASSES.has(sink.class))) {
32719
+ droppedFastjson++;
32720
+ return false;
32721
+ }
32722
+ if (jacksonSafe && JACKSON_METHODS.has(sink.method) && sink.class !== undefined && JACKSON_CLASSES.has(sink.class)) {
32723
+ droppedJackson++;
32724
+ return false;
32725
+ }
32726
+ if (snakeYamlSafe && SNAKEYAML_METHODS.has(sink.method) && sink.class !== undefined && SNAKEYAML_CLASSES.has(sink.class)) {
32727
+ droppedSnakeYaml++;
32728
+ return false;
32729
+ }
32730
+ return true;
32731
+ });
32732
+ const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml;
32733
+ if (totalDropped > 0) {
32734
+ sinks.length = 0;
32735
+ sinks.push(...kept);
32736
+ }
32737
+ return { droppedFastjson, droppedJackson, droppedSnakeYaml };
32738
+ }
32739
+ }
32740
+
32438
32741
  // ../circle-ir/dist/analysis/passes/cli-main-reflection-suppress-pass.js
32439
32742
  var REFLECTION_SINK_METHODS = new Set([
32440
32743
  "forName",
@@ -43976,6 +44279,8 @@ async function analyze(code, filePath, language, options = {}) {
43976
44279
  pipeline.add(new SinkFilterPass);
43977
44280
  if (!disabledPasses.has("sink-semantics"))
43978
44281
  pipeline.add(new SinkSemanticsPass);
44282
+ if (!disabledPasses.has("deserialization-safety-gate"))
44283
+ pipeline.add(new DeserializationSafetyGatePass(options.dependencyContext));
43979
44284
  if (!disabledPasses.has("cli-main-reflection-suppress"))
43980
44285
  pipeline.add(new CliMainReflectionSuppressPass);
43981
44286
  if (!disabledPasses.has("library-profile-sink-gate"))
@@ -44137,7 +44442,8 @@ async function analyze(code, filePath, language, options = {}) {
44137
44442
  const verifiedFindings = applyConfidenceFilter(findings, options.includeSpeculative === true);
44138
44443
  const downgradedFindings = applyLibraryApiSurfaceDowngrade(verifiedFindings);
44139
44444
  const profiledFindings = applyProjectProfileTransform(downgradedFindings, makeProfileResolver2(options.projectProfile));
44140
- const cappedFindings = applyPerFileFindingCap(filePath, profiledFindings, options.perFileFindingCap ?? DEFAULT_PER_FILE_FINDING_CAP);
44445
+ const coalescedFindings = coalesceNoteLevelFindings(profiledFindings);
44446
+ const cappedFindings = applyPerFileFindingCap(filePath, coalescedFindings, options.perFileFindingCap ?? DEFAULT_PER_FILE_FINDING_CAP);
44141
44447
  return {
44142
44448
  meta,
44143
44449
  types,
@@ -44918,7 +45224,7 @@ var colors = {
44918
45224
  };
44919
45225
 
44920
45226
  // src/version.ts
44921
- var version = "3.176.0";
45227
+ var version = "3.178.0";
44922
45228
 
44923
45229
  // src/formatters.ts
44924
45230
  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.176.0",
3
+ "version": "3.178.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.176.0"
69
+ "circle-ir": "^3.178.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",