cognium-dev 3.178.0 → 3.180.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 +206 -6
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -11683,12 +11683,20 @@ var DEFAULT_SINKS = [
11683
11683
  { method: "format", class: "String", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
11684
11684
  { method: "format", class: "Formatter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
11685
11685
  { method: "printf", class: "System.out", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
11686
+ { method: "format", class: "MessageFormat", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
11687
+ { method: "printf", class: "PrintStream", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
11688
+ { method: "format", class: "PrintStream", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
11689
+ { method: "printf", class: "PrintWriter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
11690
+ { method: "format", class: "PrintWriter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
11686
11691
  { method: "printf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["python"] },
11687
11692
  { method: "fprintf", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [1], languages: ["python"] },
11688
11693
  { method: "Sprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11689
11694
  { method: "Printf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11690
11695
  { method: "Errorf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11691
11696
  { method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] },
11697
+ { method: "Printf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11698
+ { method: "Fatalf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11699
+ { method: "Panicf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
11692
11700
  { method: "setHeader", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
11693
11701
  { method: "writeHead", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [2], languages: ["javascript", "typescript"] },
11694
11702
  { method: "cookie", type: "crlf", cwe: "CWE-113", severity: "medium", arg_positions: [1], languages: ["javascript", "typescript"] },
@@ -13680,6 +13688,11 @@ function calculateSinkConfidence(call, pattern) {
13680
13688
  if (pattern.class && call.receiver) {
13681
13689
  if (receiverMightBeClass(call.receiver, pattern.class)) {
13682
13690
  confidence += 0.1;
13691
+ const receiverLc = call.receiver.toLowerCase();
13692
+ const classLc = pattern.class.toLowerCase();
13693
+ if (call.receiver === pattern.class || receiverLc === classLc) {
13694
+ confidence += 0.05;
13695
+ }
13683
13696
  }
13684
13697
  }
13685
13698
  if (pattern.severity === "critical") {
@@ -32655,6 +32668,130 @@ function resolveFastjsonFromPom(pomXml) {
32655
32668
  }
32656
32669
  return null;
32657
32670
  }
32671
+ function resolveFastjsonFromGradle(buildGradle) {
32672
+ if (!buildGradle)
32673
+ return null;
32674
+ const directRe = /['"(]\s*com\.alibaba:fastjson:([^'"$\s)]+)\s*['")]/;
32675
+ const direct = buildGradle.match(directRe);
32676
+ if (direct) {
32677
+ const version2 = direct[1];
32678
+ return { version: version2, noneAutotype: /_noneautotype/i.test(version2) };
32679
+ }
32680
+ const constraintsBlockRe = /['"(]\s*com\.alibaba:fastjson\s*['")]\s*\)?\s*\{[\s\S]*?\bversion\s*\{[\s\S]*?\b(?:strictly|require|prefer)\s+['"]([^'"\s]+)['"]/;
32681
+ const constraintsBlock = buildGradle.match(constraintsBlockRe);
32682
+ if (constraintsBlock) {
32683
+ const version2 = constraintsBlock[1];
32684
+ return { version: version2, noneAutotype: /_noneautotype/i.test(version2) };
32685
+ }
32686
+ const propRefRe = /['"(]\s*com\.alibaba:fastjson:\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?\s*['")]/;
32687
+ const propRef = buildGradle.match(propRefRe);
32688
+ if (!propRef)
32689
+ return null;
32690
+ const propName = propRef[1];
32691
+ const defRe = new RegExp(`\\b${propName}\\s*[=:]\\s*['"]([^'"\\s]+)['"]`);
32692
+ const def = buildGradle.match(defRe);
32693
+ if (!def)
32694
+ return null;
32695
+ const version = def[1];
32696
+ return { version, noneAutotype: /_noneautotype/i.test(version) };
32697
+ }
32698
+ function resolveFastjsonFromGradleCatalog(buildGradle, libsVersionsToml) {
32699
+ if (!buildGradle || !libsVersionsToml)
32700
+ return null;
32701
+ const librariesMatch = libsVersionsToml.match(/\[libraries\]([\s\S]*?)(?=\n\[|$)/);
32702
+ if (!librariesMatch)
32703
+ return null;
32704
+ const librariesBlock = librariesMatch[1];
32705
+ const moduleForm = librariesBlock.match(/^\s*([\w.-]+)\s*=\s*\{[^}]*\bmodule\s*=\s*"com\.alibaba:fastjson"[^}]*\}/m);
32706
+ const groupNameForm = librariesBlock.match(/^\s*([\w.-]+)\s*=\s*\{[^}]*\bgroup\s*=\s*"com\.alibaba"[^}]*\bname\s*=\s*"fastjson"[^}]*\}/m);
32707
+ const entry = moduleForm ?? groupNameForm;
32708
+ if (!entry)
32709
+ return null;
32710
+ const alias = entry[1];
32711
+ const entryBody = entry[0];
32712
+ const aliasEsc = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32713
+ const aliasRefRe = new RegExp(`\\blibs\\s*\\.\\s*${aliasEsc}\\b`);
32714
+ if (!aliasRefRe.test(buildGradle))
32715
+ return null;
32716
+ const inlineVersionM = entryBody.match(/\bversion\s*=\s*"([^"$][^"]*)"/);
32717
+ if (inlineVersionM) {
32718
+ const version2 = inlineVersionM[1];
32719
+ return { version: version2, noneAutotype: /_noneautotype/i.test(version2) };
32720
+ }
32721
+ const versionRefM = entryBody.match(/\bversion\.ref\s*=\s*"([^"]+)"/);
32722
+ if (!versionRefM)
32723
+ return null;
32724
+ const versionAlias = versionRefM[1];
32725
+ const versionsMatch = libsVersionsToml.match(/\[versions\]([\s\S]*?)(?=\n\[|$)/);
32726
+ if (!versionsMatch)
32727
+ return null;
32728
+ const versionsBlock = versionsMatch[1];
32729
+ const versionAliasEsc = versionAlias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32730
+ const versionLineRe = new RegExp(`^\\s*${versionAliasEsc}\\s*=\\s*"([^"]+)"`, "m");
32731
+ const versionLine = versionsBlock.match(versionLineRe);
32732
+ if (!versionLine)
32733
+ return null;
32734
+ const version = versionLine[1];
32735
+ return { version, noneAutotype: /_noneautotype/i.test(version) };
32736
+ }
32737
+ function resolvePyYamlFromRequirements(requirementsTxt) {
32738
+ if (!requirementsTxt)
32739
+ return null;
32740
+ for (const rawLine of requirementsTxt.split(/\r?\n/)) {
32741
+ const line = rawLine.replace(/#.*$/, "").trim();
32742
+ if (!line)
32743
+ continue;
32744
+ if (line.startsWith("-"))
32745
+ continue;
32746
+ const m = line.match(/^(pyyaml)\s*(==|>=|~=|>|===)\s*(\d+(?:\.\d+)*)/i);
32747
+ if (!m)
32748
+ continue;
32749
+ const version = m[3];
32750
+ return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
32751
+ }
32752
+ return null;
32753
+ }
32754
+ function resolvePyYamlFromPyproject(pyprojectToml) {
32755
+ if (!pyprojectToml)
32756
+ return null;
32757
+ const poetryLine = pyprojectToml.match(/^\s*(pyyaml)\s*=\s*"([\^~=<>]*)(\d+(?:\.\d+)*)"/im);
32758
+ if (poetryLine) {
32759
+ const version = poetryLine[3];
32760
+ return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
32761
+ }
32762
+ const poetryTable = pyprojectToml.match(/^\s*(pyyaml)\s*=\s*\{[^}]*\bversion\s*=\s*"([\^~=<>]*)(\d+(?:\.\d+)*)"/im);
32763
+ if (poetryTable) {
32764
+ const version = poetryTable[3];
32765
+ return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
32766
+ }
32767
+ const pep621 = pyprojectToml.match(/"(pyyaml)\s*(==|>=|~=|>|===)\s*(\d+(?:\.\d+)*)/i);
32768
+ if (pep621) {
32769
+ const version = pep621[3];
32770
+ return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
32771
+ }
32772
+ return null;
32773
+ }
32774
+ function isPyYamlVersionSafeByDefault(version) {
32775
+ const parts2 = version.split(".").map((s) => Number.parseInt(s, 10));
32776
+ if (parts2.length === 0 || !Number.isFinite(parts2[0]))
32777
+ return false;
32778
+ return parts2[0] >= 6;
32779
+ }
32780
+ function fileHasUnsafePyYamlLoader(sourceLines, sinkLine) {
32781
+ const start2 = Math.max(0, sinkLine - 1);
32782
+ const end = Math.min(sourceLines.length, start2 + 10);
32783
+ const unsafeRe = /\bLoader\s*=\s*(?:yaml\s*\.\s*)?(?:Loader|UnsafeLoader|FullLoader)\b/;
32784
+ for (let i2 = start2;i2 < end; i2++) {
32785
+ const ln = sourceLines[i2] ?? "";
32786
+ if (unsafeRe.test(ln))
32787
+ return true;
32788
+ const stripped = ln.trim();
32789
+ if (i2 > start2 && (stripped === ")" || stripped.endsWith(")"))) {
32790
+ return false;
32791
+ }
32792
+ }
32793
+ return false;
32794
+ }
32658
32795
  function fileReenablesFastjsonAutotype(source) {
32659
32796
  if (!source)
32660
32797
  return false;
@@ -32688,6 +32825,8 @@ var JACKSON_METHODS = new Set(["readValue", "convertValue", "treeToValue"]);
32688
32825
  var JACKSON_CLASSES = new Set(["ObjectMapper", "ObjectReader"]);
32689
32826
  var SNAKEYAML_METHODS = new Set(["load", "loadAs", "loadAll"]);
32690
32827
  var SNAKEYAML_CLASSES = new Set(["Yaml"]);
32828
+ var PYYAML_METHODS = new Set(["load"]);
32829
+ var PYYAML_CLASSES = new Set(["yaml"]);
32691
32830
 
32692
32831
  class DeserializationSafetyGatePass {
32693
32832
  dependencyContext;
@@ -32698,18 +32837,32 @@ class DeserializationSafetyGatePass {
32698
32837
  }
32699
32838
  run(ctx) {
32700
32839
  const { graph, language, code } = ctx;
32701
- if (language !== "java") {
32702
- return { droppedFastjson: 0, droppedJackson: 0, droppedSnakeYaml: 0 };
32840
+ if (language !== "java" && language !== "python") {
32841
+ return { droppedFastjson: 0, droppedJackson: 0, droppedSnakeYaml: 0, droppedPyYaml: 0 };
32703
32842
  }
32704
32843
  const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
32705
32844
  const pomXml = this.dependencyContext?.java?.pomXml;
32706
- const fastjson = pomXml ? resolveFastjsonFromPom(pomXml) : null;
32845
+ const buildGradle = this.dependencyContext?.java?.buildGradle;
32846
+ const libsVersionsToml = this.dependencyContext?.java?.libsVersionsToml;
32847
+ const fastjson = (pomXml ? resolveFastjsonFromPom(pomXml) : null) ?? (buildGradle ? resolveFastjsonFromGradle(buildGradle) : null) ?? (buildGradle && libsVersionsToml ? resolveFastjsonFromGradleCatalog(buildGradle, libsVersionsToml) : null);
32707
32848
  const fastjsonHardened = fastjson?.noneAutotype === true && !fileReenablesFastjsonAutotype(code);
32708
32849
  const jacksonSafe = !fileEnablesJacksonPolymorphism(code);
32709
32850
  const snakeYamlSafe = fileConfiguresSnakeYamlSafely(code);
32851
+ const requirementsTxt = this.dependencyContext?.python?.requirementsTxt;
32852
+ const pyprojectToml = this.dependencyContext?.python?.pyprojectToml;
32853
+ const pyYaml = (requirementsTxt ? resolvePyYamlFromRequirements(requirementsTxt) : null) ?? (pyprojectToml ? resolvePyYamlFromPyproject(pyprojectToml) : null);
32854
+ const pyYamlSafeByDefault = pyYaml?.safeByDefault === true;
32855
+ let sourceLines = null;
32856
+ const getSourceLines = () => {
32857
+ if (sourceLines === null)
32858
+ sourceLines = code.split(`
32859
+ `);
32860
+ return sourceLines;
32861
+ };
32710
32862
  let droppedFastjson = 0;
32711
32863
  let droppedJackson = 0;
32712
32864
  let droppedSnakeYaml = 0;
32865
+ let droppedPyYaml = 0;
32713
32866
  const kept = sinks.filter((sink) => {
32714
32867
  if (sink.type !== "deserialization")
32715
32868
  return true;
@@ -32727,14 +32880,18 @@ class DeserializationSafetyGatePass {
32727
32880
  droppedSnakeYaml++;
32728
32881
  return false;
32729
32882
  }
32883
+ if (language === "python" && pyYamlSafeByDefault && PYYAML_METHODS.has(sink.method) && (sink.class === undefined || PYYAML_CLASSES.has(sink.class)) && !fileHasUnsafePyYamlLoader(getSourceLines(), sink.line)) {
32884
+ droppedPyYaml++;
32885
+ return false;
32886
+ }
32730
32887
  return true;
32731
32888
  });
32732
- const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml;
32889
+ const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml + droppedPyYaml;
32733
32890
  if (totalDropped > 0) {
32734
32891
  sinks.length = 0;
32735
32892
  sinks.push(...kept);
32736
32893
  }
32737
- return { droppedFastjson, droppedJackson, droppedSnakeYaml };
32894
+ return { droppedFastjson, droppedJackson, droppedSnakeYaml, droppedPyYaml };
32738
32895
  }
32739
32896
  }
32740
32897
 
@@ -39624,6 +39781,47 @@ class ScanSecretsPass {
39624
39781
  }
39625
39782
  }
39626
39783
 
39784
+ // ../circle-ir/dist/analysis/passes/python-receiver-taint-format-pass.js
39785
+ class PythonReceiverTaintFormatPass {
39786
+ name = "python-receiver-taint-format";
39787
+ category = "security";
39788
+ run(ctx) {
39789
+ if (ctx.language !== "python") {
39790
+ return { findingsEmitted: 0 };
39791
+ }
39792
+ const { calls, meta } = ctx.graph.ir;
39793
+ const constProp = ctx.getResult("constant-propagation");
39794
+ const tainted = constProp.tainted;
39795
+ let count = 0;
39796
+ for (const call of calls) {
39797
+ if (call.method_name !== "format")
39798
+ continue;
39799
+ const receiver = call.receiver;
39800
+ if (!receiver)
39801
+ continue;
39802
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(receiver))
39803
+ continue;
39804
+ const scopedName = call.in_method ? `${call.in_method}:${receiver}` : receiver;
39805
+ if (!tainted.has(receiver) && !tainted.has(scopedName))
39806
+ continue;
39807
+ ctx.addFinding({
39808
+ id: `format_string-${meta.file}-${call.location.line}-${receiver}`,
39809
+ pass: this.name,
39810
+ category: "security",
39811
+ rule_id: "format_string",
39812
+ cwe: "CWE-134",
39813
+ severity: "medium",
39814
+ level: "warning",
39815
+ message: `Format-string vulnerability: the format template \`${receiver}\` ` + `is tainted (traces back to a taint source). Attacker can leak ` + `object attributes via \`{obj.__class__.__mro__[…]}\` chains or ` + `crash the process via unbounded specifiers. Use a literal ` + `format string and pass user input as an argument instead.`,
39816
+ file: meta.file,
39817
+ line: call.location.line
39818
+ });
39819
+ count++;
39820
+ }
39821
+ return { findingsEmitted: count };
39822
+ }
39823
+ }
39824
+
39627
39825
  // ../circle-ir/dist/analysis/passes/spring4shell-pass.js
39628
39826
  var CONTROLLER_ANNOTATIONS = new Set([
39629
39827
  "Controller",
@@ -44295,6 +44493,8 @@ async function analyze(code, filePath, language, options = {}) {
44295
44493
  pipeline.add(new LibraryProfileCwe22PathGatePass);
44296
44494
  if (!disabledPasses.has("scan-secrets"))
44297
44495
  pipeline.add(new ScanSecretsPass);
44496
+ if (!disabledPasses.has("python-receiver-taint-format"))
44497
+ pipeline.add(new PythonReceiverTaintFormatPass);
44298
44498
  if (!disabledPasses.has("dead-code"))
44299
44499
  pipeline.add(new DeadCodePass);
44300
44500
  if (!disabledPasses.has("missing-await"))
@@ -45224,7 +45424,7 @@ var colors = {
45224
45424
  };
45225
45425
 
45226
45426
  // src/version.ts
45227
- var version = "3.178.0";
45427
+ var version = "3.180.0";
45228
45428
 
45229
45429
  // src/formatters.ts
45230
45430
  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.178.0",
3
+ "version": "3.180.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.178.0"
69
+ "circle-ir": "^3.180.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^25.5.0",