cognium-dev 3.158.0 → 3.160.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 +126 -8
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -12982,7 +12982,10 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
12982
12982
  return true;
12983
12983
  }
12984
12984
  const subtypeArgArityOk = !pattern.arg_positions || pattern.arg_positions.length === 0 || pattern.arg_positions.every((pos) => pos < (call.arguments?.length ?? 0));
12985
- if (call.receiver_type && call.receiver_type === pattern.class) {} else if (call.receiver_type && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type, pattern.class)) {} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {} else if (call.receiver_type_fqn && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type_fqn, pattern.class)) {} else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
12985
+ if (call.receiver_type && call.receiver_type === pattern.class) {} else if (call.receiver_type && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type, pattern.class)) {} else if (call.receiver_type_fqn && call.receiver_type_fqn.endsWith("." + pattern.class)) {} else if (call.receiver_type_fqn && typeHierarchy && subtypeArgArityOk && typeHierarchy.isSubtypeOf(call.receiver_type_fqn, pattern.class)) {} else if (!call.receiver_type && !call.receiver_type_fqn && call.receiver && typeHierarchy && subtypeArgArityOk && (() => {
12986
+ const factoryReturn = typeHierarchy.resolveFactoryReturnType(call.receiver);
12987
+ return factoryReturn !== null && typeHierarchy.isSubtypeOf(factoryReturn, pattern.class);
12988
+ })()) {} else if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
12986
12989
  if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
12987
12990
  return true;
12988
12991
  }
@@ -13915,6 +13918,7 @@ class TypeHierarchyResolver {
13915
13918
  implementations = new Map;
13916
13919
  _subtypeCache = new Map;
13917
13920
  _implCache = new Map;
13921
+ factoryReturnTypes = new Map;
13918
13922
  addFromIR(ir, filePath) {
13919
13923
  for (const type of ir.types) {
13920
13924
  this.addType(type, filePath, ir.meta.package || null);
@@ -14120,11 +14124,22 @@ class TypeHierarchyResolver {
14120
14124
  getAllTypes() {
14121
14125
  return Array.from(this.types.values());
14122
14126
  }
14127
+ registerFactoryReturnType(factoryClass, method, returnFqn) {
14128
+ this.factoryReturnTypes.set(`${factoryClass}.${method}`, returnFqn);
14129
+ }
14130
+ resolveFactoryReturnType(receiver) {
14131
+ const m = receiver.match(/(?:^|\.)([A-Z]\w*)\.(\w+)\(\)$/);
14132
+ if (!m)
14133
+ return null;
14134
+ const key = `${m[1]}.${m[2]}`;
14135
+ return this.factoryReturnTypes.get(key) ?? null;
14136
+ }
14123
14137
  clear() {
14124
14138
  this.types.clear();
14125
14139
  this.nameToFqn.clear();
14126
14140
  this.subtypes.clear();
14127
14141
  this.implementations.clear();
14142
+ this.factoryReturnTypes.clear();
14128
14143
  }
14129
14144
  resolveTypeName(name2, currentPackage) {
14130
14145
  if (name2.includes("."))
@@ -14463,6 +14478,9 @@ function registerCommonLibraries(resolver) {
14463
14478
  for (const type of [...apacheHttpClient4x, ...apacheHttpClient5x]) {
14464
14479
  resolver.addType(type, "common-libraries", type.package);
14465
14480
  }
14481
+ resolver.registerFactoryReturnType("HttpClients", "createDefault", "org.apache.http.impl.client.CloseableHttpClient");
14482
+ resolver.registerFactoryReturnType("HttpClients", "createSystem", "org.apache.http.impl.client.CloseableHttpClient");
14483
+ resolver.registerFactoryReturnType("HttpClients", "createMinimal", "org.apache.http.impl.client.MinimalHttpClient");
14466
14484
  }
14467
14485
  // ../circle-ir/dist/resolution/symbol-table.js
14468
14486
  class SymbolTable {
@@ -22516,14 +22534,40 @@ class CrossFilePass {
22516
22534
  });
22517
22535
  }
22518
22536
  }
22537
+ const filteredTaintPaths = taintPaths.filter((tp) => {
22538
+ const sinkIR = projectGraph.getIR(tp.sink.file);
22539
+ if (!sinkIR)
22540
+ return true;
22541
+ const sinkTypeStr = tp.sink.type;
22542
+ if (tp.source.file === tp.sink.file) {
22543
+ const lo = Math.min(tp.source.line, tp.sink.line);
22544
+ const hi = Math.max(tp.source.line, tp.sink.line);
22545
+ for (const san of sinkIR.taint.sanitizers ?? []) {
22546
+ if (san.line < lo || san.line > hi)
22547
+ continue;
22548
+ if (san.sanitizes.includes(sinkTypeStr)) {
22549
+ return false;
22550
+ }
22551
+ }
22552
+ return true;
22553
+ }
22554
+ for (const san of sinkIR.taint.sanitizers ?? []) {
22555
+ if (san.line !== tp.sink.line)
22556
+ continue;
22557
+ if (san.sanitizes.includes(sinkTypeStr)) {
22558
+ return false;
22559
+ }
22560
+ }
22561
+ return true;
22562
+ });
22519
22563
  const typeHierarchy = projectGraph.typeHierarchy.toTypeHierarchyData();
22520
22564
  logger.info("cross-file: complete", {
22521
22565
  totalMs: Date.now() - startMs,
22522
- paths: taintPaths.length,
22566
+ paths: filteredTaintPaths.length,
22523
22567
  crossFileCalls: crossFileCalls.length,
22524
22568
  budgetExceeded: exceeded
22525
22569
  });
22526
- const result = { crossFileCalls, taintPaths, typeHierarchy };
22570
+ const result = { crossFileCalls, taintPaths: filteredTaintPaths, typeHierarchy };
22527
22571
  if (exceeded)
22528
22572
  result.budgetExceeded = true;
22529
22573
  return result;
@@ -23669,6 +23713,7 @@ class LanguageSourcesPass {
23669
23713
  if (language === "java") {
23670
23714
  additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
23671
23715
  additionalSanitizers.push(...findJavaPathNormalizeStartsWithGuardSanitizers(code));
23716
+ additionalSanitizers.push(...findJavaPathGetFileNameSanitizers(code));
23672
23717
  additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
23673
23718
  additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
23674
23719
  for (const finding of findJavaPatternFindings(code, graph.ir.meta.file)) {
@@ -25937,6 +25982,48 @@ function findJavaPathNormalizeStartsWithGuardSanitizers(code) {
25937
25982
  }
25938
25983
  return sanitizers;
25939
25984
  }
25985
+ function findJavaPathGetFileNameSanitizers(code) {
25986
+ const sanitizers = [];
25987
+ const lines = code.split(`
25988
+ `);
25989
+ const assignmentRe = /^\s*(?:(?:final\s+)?[A-Za-z_][\w.<>?,\s\[\]]*?\s+)?([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
25990
+ const rhsHasGetFileNameRe = /\.\s*getFileName\s*\(\s*\)/;
25991
+ const rhsHasPathsChainRe = /\b(?:Paths\s*\.\s*get|Path\s*\.\s*of)\s*\(/;
25992
+ const candidates = [];
25993
+ for (let i2 = 0;i2 < lines.length; i2++) {
25994
+ const m = assignmentRe.exec(lines[i2]);
25995
+ if (!m)
25996
+ continue;
25997
+ const rhs = m[2];
25998
+ if (!rhsHasGetFileNameRe.test(rhs))
25999
+ continue;
26000
+ if (!rhsHasPathsChainRe.test(rhs))
26001
+ continue;
26002
+ candidates.push({ line: i2 + 1, boundVar: m[1] });
26003
+ }
26004
+ if (candidates.length === 0)
26005
+ return sanitizers;
26006
+ for (const c of candidates) {
26007
+ sanitizers.push({
26008
+ type: "java_path_get_filename",
26009
+ method: "getFileName",
26010
+ line: c.line,
26011
+ sanitizes: ["path_traversal", "external_taint_escape"]
26012
+ });
26013
+ const varRefRe = new RegExp(`\\b${c.boundVar}\\b`);
26014
+ for (let l = c.line;l < lines.length; l++) {
26015
+ if (!varRefRe.test(lines[l]))
26016
+ continue;
26017
+ sanitizers.push({
26018
+ type: "java_path_get_filename",
26019
+ method: "getFileName",
26020
+ line: l + 1,
26021
+ sanitizes: ["path_traversal", "external_taint_escape"]
26022
+ });
26023
+ }
26024
+ }
26025
+ return sanitizers;
26026
+ }
25940
26027
  function findJavaInlineCrlfStripLogSanitizers(code) {
25941
26028
  const sanitizers = [];
25942
26029
  const lines = code.split(`
@@ -29759,6 +29846,12 @@ function correlateDollarBraceToArgPositions(method, refs) {
29759
29846
  }
29760
29847
  }
29761
29848
  });
29849
+ const declaredNameToIndex = new Map;
29850
+ method.parameters.forEach((param, idx) => {
29851
+ if (param.name && !declaredNameToIndex.has(param.name)) {
29852
+ declaredNameToIndex.set(param.name, idx);
29853
+ }
29854
+ });
29762
29855
  const positions = new Set;
29763
29856
  for (const ref of refs) {
29764
29857
  const namedIdx = paramNameToIndex.get(ref);
@@ -29766,6 +29859,11 @@ function correlateDollarBraceToArgPositions(method, refs) {
29766
29859
  positions.add(namedIdx);
29767
29860
  continue;
29768
29861
  }
29862
+ const declaredIdx = declaredNameToIndex.get(ref);
29863
+ if (declaredIdx !== undefined) {
29864
+ positions.add(declaredIdx);
29865
+ continue;
29866
+ }
29769
29867
  const positionalIdx = parsePositionalRef(ref);
29770
29868
  if (positionalIdx !== null && positionalIdx < method.parameters.length) {
29771
29869
  positions.add(positionalIdx);
@@ -29796,11 +29894,11 @@ class MyBatisAnnotationSqlSinkPass {
29796
29894
  category = "security";
29797
29895
  run(ctx) {
29798
29896
  if (ctx.language !== "java") {
29799
- return { annotatedMethodCount: 0, addedSinkCount: 0 };
29897
+ return { annotatedMethodCount: 0, addedSinkCount: 0, declarationFindingCount: 0 };
29800
29898
  }
29801
29899
  const { types, imports, calls } = ctx.graph.ir;
29802
29900
  if (!fileImportsMyBatis(imports)) {
29803
- return { annotatedMethodCount: 0, addedSinkCount: 0 };
29901
+ return { annotatedMethodCount: 0, addedSinkCount: 0, declarationFindingCount: 0 };
29804
29902
  }
29805
29903
  const mapperMethods = [];
29806
29904
  for (const type of types) {
@@ -29829,6 +29927,7 @@ class MyBatisAnnotationSqlSinkPass {
29829
29927
  interfaceSimpleName: type.name,
29830
29928
  interfaceFqn,
29831
29929
  methodName: method.name,
29930
+ declarationLine: method.start_line,
29832
29931
  taintedArgPositions: positions
29833
29932
  });
29834
29933
  }
@@ -29836,9 +29935,27 @@ class MyBatisAnnotationSqlSinkPass {
29836
29935
  if (mapperMethods.length === 0) {
29837
29936
  return {
29838
29937
  annotatedMethodCount: 0,
29839
- addedSinkCount: 0
29938
+ addedSinkCount: 0,
29939
+ declarationFindingCount: 0
29840
29940
  };
29841
29941
  }
29942
+ const file = ctx.graph.ir.meta.file;
29943
+ let declarationFindingCount = 0;
29944
+ for (const rec of mapperMethods) {
29945
+ ctx.addFinding({
29946
+ id: `${this.name}-${file}-${rec.declarationLine}-${rec.methodName}`,
29947
+ pass: this.name,
29948
+ category: this.category,
29949
+ rule_id: this.name,
29950
+ cwe: "CWE-89",
29951
+ severity: "critical",
29952
+ level: "error",
29953
+ message: `MyBatis Mapper method \`${rec.interfaceSimpleName}.${rec.methodName}\` uses raw ` + `\`\${}\` interpolation in its @Select/@Update/@Insert/@Delete annotation. ` + `Any caller passing dynamic input into arg position(s) ` + `${rec.taintedArgPositions.join(", ")} will execute unbound SQL (CWE-89). ` + `Replace \`\${x}\` with \`#{x}\` to enable JDBC parameter binding.`,
29954
+ file,
29955
+ line: rec.declarationLine
29956
+ });
29957
+ declarationFindingCount++;
29958
+ }
29842
29959
  const sinks = ctx.hasResult("taint-matcher") ? ctx.getResult("taint-matcher").sinks : ctx.graph.ir.taint.sinks;
29843
29960
  let addedSinkCount = 0;
29844
29961
  for (const call of calls) {
@@ -29866,7 +29983,8 @@ class MyBatisAnnotationSqlSinkPass {
29866
29983
  }
29867
29984
  return {
29868
29985
  annotatedMethodCount: mapperMethods.length,
29869
- addedSinkCount
29986
+ addedSinkCount,
29987
+ declarationFindingCount
29870
29988
  };
29871
29989
  }
29872
29990
  }
@@ -43682,7 +43800,7 @@ var colors = {
43682
43800
  };
43683
43801
 
43684
43802
  // src/version.ts
43685
- var version = "3.158.0";
43803
+ var version = "3.160.0";
43686
43804
 
43687
43805
  // src/formatters.ts
43688
43806
  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.158.0",
3
+ "version": "3.160.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.158.0"
69
+ "circle-ir": "^3.160.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",