circle-ir 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.
@@ -12277,6 +12277,20 @@ var DEFAULT_SINKS = [
12277
12277
  // Sandbox/script security
12278
12278
  { method: "onNewInstance", class: "SandboxInterceptor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
12279
12279
  // Java Log Injection (slf4j / logback / java.util.logging) — CWE-117
12280
+ //
12281
+ // Classification decision — cognium-dev #264: Logger receivers stay
12282
+ // as log_injection (CWE-117), NOT format_string (CWE-134). The real
12283
+ // vulnerability with tainted data flowing into Logger.log(fmt, ...)
12284
+ // is log-forgery via CRLF injection, not format-string exploitation.
12285
+ // A genuine format_string vulnerability requires the FORMAT STRING
12286
+ // itself to be attacker-controlled, which is virtually never the
12287
+ // case in Logger APIs (the format string is a literal at the call
12288
+ // site). Adding format_string sinks for these same receivers would
12289
+ // duplicate the existing log_injection findings on every call site
12290
+ // without adding real signal. The rare edge case where a caller
12291
+ // passes `taintedFmt` at arg[0] is still caught by these entries
12292
+ // (via arg_positions[0] on the SLF4J-family methods), classified as
12293
+ // log_injection.
12280
12294
  // Issue #44: log.info/warn/error/debug emit the message argument and any
12281
12295
  // {} format arguments to the log stream. Untrusted input forwarded into
12282
12296
  // these calls allows log forging (newline injection) and downstream log
@@ -12874,6 +12888,16 @@ var DEFAULT_SINKS = [
12874
12888
  { method: "format", class: "String", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12875
12889
  { method: "format", class: "Formatter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12876
12890
  { method: "printf", class: "System.out", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12891
+ // cognium-dev #264 — extend the Java format-string surface.
12892
+ // MessageFormat.format(pattern, args) — same receiver-family as String.format
12893
+ // but on the java.text side. PrintStream/PrintWriter.{printf,format} —
12894
+ // arbitrary streams (not just System.out) also honour format-string
12895
+ // specifiers, so a tainted first arg is a genuine CWE-134 sink.
12896
+ { method: "format", class: "MessageFormat", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12897
+ { method: "printf", class: "PrintStream", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12898
+ { method: "format", class: "PrintStream", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12899
+ { method: "printf", class: "PrintWriter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12900
+ { method: "format", class: "PrintWriter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12877
12901
  // NOTE: Python `userFmt.format(...)` and `userFmt % args` require
12878
12902
  // receiver-taint or operator-LHS-taint tracking — the format string is the
12879
12903
  // receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
@@ -12885,6 +12909,12 @@ var DEFAULT_SINKS = [
12885
12909
  { method: "Printf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12886
12910
  { method: "Errorf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12887
12911
  { method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] },
12912
+ // cognium-dev #264 — Go stdlib `log` package format-string entry points.
12913
+ // log.Printf / Fatalf / Panicf take the format string at arg[0]; tainted
12914
+ // format string reaches the same fmt.Sprintf machinery internally.
12915
+ { method: "Printf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12916
+ { method: "Fatalf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12917
+ { method: "Panicf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12888
12918
  // CRLF / HTTP response splitting (CWE-113) — Sprint 6, #86.
12889
12919
  // Node.js / Express response header / cookie sinks. The header *name* (arg 0)
12890
12920
  // is also CRLF-sensitive but is almost always a string literal; we model
@@ -15072,6 +15102,11 @@ function calculateSinkConfidence(call, pattern) {
15072
15102
  if (pattern.class && call.receiver) {
15073
15103
  if (receiverMightBeClass(call.receiver, pattern.class)) {
15074
15104
  confidence += 0.1;
15105
+ const receiverLc = call.receiver.toLowerCase();
15106
+ const classLc = pattern.class.toLowerCase();
15107
+ if (call.receiver === pattern.class || receiverLc === classLc) {
15108
+ confidence += 0.05;
15109
+ }
15075
15110
  }
15076
15111
  }
15077
15112
  if (pattern.severity === "critical") {
@@ -32863,6 +32898,136 @@ function resolveFastjsonFromPom(pomXml) {
32863
32898
  }
32864
32899
  return null;
32865
32900
  }
32901
+ function resolveFastjsonFromGradle(buildGradle) {
32902
+ if (!buildGradle) return null;
32903
+ const directRe = /['"(]\s*com\.alibaba:fastjson:([^'"$\s)]+)\s*['")]/;
32904
+ const direct = buildGradle.match(directRe);
32905
+ if (direct) {
32906
+ const version2 = direct[1];
32907
+ return { version: version2, noneAutotype: /_noneautotype/i.test(version2) };
32908
+ }
32909
+ const constraintsBlockRe = /['"(]\s*com\.alibaba:fastjson\s*['")]\s*\)?\s*\{[\s\S]*?\bversion\s*\{[\s\S]*?\b(?:strictly|require|prefer)\s+['"]([^'"\s]+)['"]/;
32910
+ const constraintsBlock = buildGradle.match(constraintsBlockRe);
32911
+ if (constraintsBlock) {
32912
+ const version2 = constraintsBlock[1];
32913
+ return { version: version2, noneAutotype: /_noneautotype/i.test(version2) };
32914
+ }
32915
+ const propRefRe = /['"(]\s*com\.alibaba:fastjson:\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?\s*['")]/;
32916
+ const propRef = buildGradle.match(propRefRe);
32917
+ if (!propRef) return null;
32918
+ const propName = propRef[1];
32919
+ const defRe = new RegExp(
32920
+ `\\b${propName}\\s*[=:]\\s*['"]([^'"\\s]+)['"]`
32921
+ );
32922
+ const def = buildGradle.match(defRe);
32923
+ if (!def) return null;
32924
+ const version = def[1];
32925
+ return { version, noneAutotype: /_noneautotype/i.test(version) };
32926
+ }
32927
+ function resolveFastjsonFromGradleCatalog(buildGradle, libsVersionsToml) {
32928
+ if (!buildGradle || !libsVersionsToml) return null;
32929
+ const librariesMatch = libsVersionsToml.match(
32930
+ /\[libraries\]([\s\S]*?)(?=\n\[|$)/
32931
+ );
32932
+ if (!librariesMatch) return null;
32933
+ const librariesBlock = librariesMatch[1];
32934
+ const moduleForm = librariesBlock.match(
32935
+ /^\s*([\w.-]+)\s*=\s*\{[^}]*\bmodule\s*=\s*"com\.alibaba:fastjson"[^}]*\}/m
32936
+ );
32937
+ const groupNameForm = librariesBlock.match(
32938
+ /^\s*([\w.-]+)\s*=\s*\{[^}]*\bgroup\s*=\s*"com\.alibaba"[^}]*\bname\s*=\s*"fastjson"[^}]*\}/m
32939
+ );
32940
+ const entry = moduleForm ?? groupNameForm;
32941
+ if (!entry) return null;
32942
+ const alias = entry[1];
32943
+ const entryBody = entry[0];
32944
+ const aliasEsc = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32945
+ const aliasRefRe = new RegExp(`\\blibs\\s*\\.\\s*${aliasEsc}\\b`);
32946
+ if (!aliasRefRe.test(buildGradle)) return null;
32947
+ const inlineVersionM = entryBody.match(
32948
+ /\bversion\s*=\s*"([^"$][^"]*)"/
32949
+ );
32950
+ if (inlineVersionM) {
32951
+ const version2 = inlineVersionM[1];
32952
+ return { version: version2, noneAutotype: /_noneautotype/i.test(version2) };
32953
+ }
32954
+ const versionRefM = entryBody.match(/\bversion\.ref\s*=\s*"([^"]+)"/);
32955
+ if (!versionRefM) return null;
32956
+ const versionAlias = versionRefM[1];
32957
+ const versionsMatch = libsVersionsToml.match(
32958
+ /\[versions\]([\s\S]*?)(?=\n\[|$)/
32959
+ );
32960
+ if (!versionsMatch) return null;
32961
+ const versionsBlock = versionsMatch[1];
32962
+ const versionAliasEsc = versionAlias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32963
+ const versionLineRe = new RegExp(
32964
+ `^\\s*${versionAliasEsc}\\s*=\\s*"([^"]+)"`,
32965
+ "m"
32966
+ );
32967
+ const versionLine = versionsBlock.match(versionLineRe);
32968
+ if (!versionLine) return null;
32969
+ const version = versionLine[1];
32970
+ return { version, noneAutotype: /_noneautotype/i.test(version) };
32971
+ }
32972
+ function resolvePyYamlFromRequirements(requirementsTxt) {
32973
+ if (!requirementsTxt) return null;
32974
+ for (const rawLine of requirementsTxt.split(/\r?\n/)) {
32975
+ const line = rawLine.replace(/#.*$/, "").trim();
32976
+ if (!line) continue;
32977
+ if (line.startsWith("-")) continue;
32978
+ const m = line.match(
32979
+ /^(pyyaml)\s*(==|>=|~=|>|===)\s*(\d+(?:\.\d+)*)/i
32980
+ );
32981
+ if (!m) continue;
32982
+ const version = m[3];
32983
+ return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
32984
+ }
32985
+ return null;
32986
+ }
32987
+ function resolvePyYamlFromPyproject(pyprojectToml) {
32988
+ if (!pyprojectToml) return null;
32989
+ const poetryLine = pyprojectToml.match(
32990
+ /^\s*(pyyaml)\s*=\s*"([\^~=<>]*)(\d+(?:\.\d+)*)"/im
32991
+ );
32992
+ if (poetryLine) {
32993
+ const version = poetryLine[3];
32994
+ return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
32995
+ }
32996
+ const poetryTable = pyprojectToml.match(
32997
+ /^\s*(pyyaml)\s*=\s*\{[^}]*\bversion\s*=\s*"([\^~=<>]*)(\d+(?:\.\d+)*)"/im
32998
+ );
32999
+ if (poetryTable) {
33000
+ const version = poetryTable[3];
33001
+ return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
33002
+ }
33003
+ const pep621 = pyprojectToml.match(
33004
+ /"(pyyaml)\s*(==|>=|~=|>|===)\s*(\d+(?:\.\d+)*)/i
33005
+ );
33006
+ if (pep621) {
33007
+ const version = pep621[3];
33008
+ return { version, safeByDefault: isPyYamlVersionSafeByDefault(version) };
33009
+ }
33010
+ return null;
33011
+ }
33012
+ function isPyYamlVersionSafeByDefault(version) {
33013
+ const parts2 = version.split(".").map((s) => Number.parseInt(s, 10));
33014
+ if (parts2.length === 0 || !Number.isFinite(parts2[0])) return false;
33015
+ return parts2[0] >= 6;
33016
+ }
33017
+ function fileHasUnsafePyYamlLoader(sourceLines, sinkLine) {
33018
+ const start2 = Math.max(0, sinkLine - 1);
33019
+ const end = Math.min(sourceLines.length, start2 + 10);
33020
+ const unsafeRe = /\bLoader\s*=\s*(?:yaml\s*\.\s*)?(?:Loader|UnsafeLoader|FullLoader)\b/;
33021
+ for (let i2 = start2; i2 < end; i2++) {
33022
+ const ln = sourceLines[i2] ?? "";
33023
+ if (unsafeRe.test(ln)) return true;
33024
+ const stripped = ln.trim();
33025
+ if (i2 > start2 && (stripped === ")" || stripped.endsWith(")"))) {
33026
+ return false;
33027
+ }
33028
+ }
33029
+ return false;
33030
+ }
32866
33031
  function fileReenablesFastjsonAutotype(source) {
32867
33032
  if (!source) return false;
32868
33033
  return /\bsetAutoTypeSupport\s*\(\s*true\b/.test(source);
@@ -32888,6 +33053,8 @@ var JACKSON_METHODS = /* @__PURE__ */ new Set(["readValue", "convertValue", "tre
32888
33053
  var JACKSON_CLASSES = /* @__PURE__ */ new Set(["ObjectMapper", "ObjectReader"]);
32889
33054
  var SNAKEYAML_METHODS = /* @__PURE__ */ new Set(["load", "loadAs", "loadAll"]);
32890
33055
  var SNAKEYAML_CLASSES = /* @__PURE__ */ new Set(["Yaml"]);
33056
+ var PYYAML_METHODS = /* @__PURE__ */ new Set(["load"]);
33057
+ var PYYAML_CLASSES = /* @__PURE__ */ new Set(["yaml"]);
32891
33058
  var DeserializationSafetyGatePass = class {
32892
33059
  constructor(dependencyContext) {
32893
33060
  this.dependencyContext = dependencyContext;
@@ -32897,18 +33064,30 @@ var DeserializationSafetyGatePass = class {
32897
33064
  category = "security";
32898
33065
  run(ctx) {
32899
33066
  const { graph, language, code } = ctx;
32900
- if (language !== "java") {
32901
- return { droppedFastjson: 0, droppedJackson: 0, droppedSnakeYaml: 0 };
33067
+ if (language !== "java" && language !== "python") {
33068
+ return { droppedFastjson: 0, droppedJackson: 0, droppedSnakeYaml: 0, droppedPyYaml: 0 };
32902
33069
  }
32903
33070
  const sinks = ctx.hasResult("sink-filter") ? ctx.getResult("sink-filter").sinks : graph.ir.taint.sinks;
32904
33071
  const pomXml = this.dependencyContext?.java?.pomXml;
32905
- const fastjson = pomXml ? resolveFastjsonFromPom(pomXml) : null;
33072
+ const buildGradle = this.dependencyContext?.java?.buildGradle;
33073
+ const libsVersionsToml = this.dependencyContext?.java?.libsVersionsToml;
33074
+ const fastjson = (pomXml ? resolveFastjsonFromPom(pomXml) : null) ?? (buildGradle ? resolveFastjsonFromGradle(buildGradle) : null) ?? (buildGradle && libsVersionsToml ? resolveFastjsonFromGradleCatalog(buildGradle, libsVersionsToml) : null);
32906
33075
  const fastjsonHardened = fastjson?.noneAutotype === true && !fileReenablesFastjsonAutotype(code);
32907
33076
  const jacksonSafe = !fileEnablesJacksonPolymorphism(code);
32908
33077
  const snakeYamlSafe = fileConfiguresSnakeYamlSafely(code);
33078
+ const requirementsTxt = this.dependencyContext?.python?.requirementsTxt;
33079
+ const pyprojectToml = this.dependencyContext?.python?.pyprojectToml;
33080
+ const pyYaml = (requirementsTxt ? resolvePyYamlFromRequirements(requirementsTxt) : null) ?? (pyprojectToml ? resolvePyYamlFromPyproject(pyprojectToml) : null);
33081
+ const pyYamlSafeByDefault = pyYaml?.safeByDefault === true;
33082
+ let sourceLines = null;
33083
+ const getSourceLines = () => {
33084
+ if (sourceLines === null) sourceLines = code.split("\n");
33085
+ return sourceLines;
33086
+ };
32909
33087
  let droppedFastjson = 0;
32910
33088
  let droppedJackson = 0;
32911
33089
  let droppedSnakeYaml = 0;
33090
+ let droppedPyYaml = 0;
32912
33091
  const kept = sinks.filter((sink) => {
32913
33092
  if (sink.type !== "deserialization") return true;
32914
33093
  if (!sink.method) return true;
@@ -32924,14 +33103,18 @@ var DeserializationSafetyGatePass = class {
32924
33103
  droppedSnakeYaml++;
32925
33104
  return false;
32926
33105
  }
33106
+ if (language === "python" && pyYamlSafeByDefault && PYYAML_METHODS.has(sink.method) && (sink.class === void 0 || PYYAML_CLASSES.has(sink.class)) && !fileHasUnsafePyYamlLoader(getSourceLines(), sink.line)) {
33107
+ droppedPyYaml++;
33108
+ return false;
33109
+ }
32927
33110
  return true;
32928
33111
  });
32929
- const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml;
33112
+ const totalDropped = droppedFastjson + droppedJackson + droppedSnakeYaml + droppedPyYaml;
32930
33113
  if (totalDropped > 0) {
32931
33114
  sinks.length = 0;
32932
33115
  sinks.push(...kept);
32933
33116
  }
32934
- return { droppedFastjson, droppedJackson, droppedSnakeYaml };
33117
+ return { droppedFastjson, droppedJackson, droppedSnakeYaml, droppedPyYaml };
32935
33118
  }
32936
33119
  };
32937
33120
 
@@ -38705,6 +38888,45 @@ var ScanSecretsPass = class {
38705
38888
  }
38706
38889
  };
38707
38890
 
38891
+ // src/analysis/passes/python-receiver-taint-format-pass.ts
38892
+ var PythonReceiverTaintFormatPass = class {
38893
+ name = "python-receiver-taint-format";
38894
+ category = "security";
38895
+ run(ctx) {
38896
+ if (ctx.language !== "python") {
38897
+ return { findingsEmitted: 0 };
38898
+ }
38899
+ const { calls, meta } = ctx.graph.ir;
38900
+ const constProp = ctx.getResult(
38901
+ "constant-propagation"
38902
+ );
38903
+ const tainted = constProp.tainted;
38904
+ let count = 0;
38905
+ for (const call of calls) {
38906
+ if (call.method_name !== "format") continue;
38907
+ const receiver = call.receiver;
38908
+ if (!receiver) continue;
38909
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(receiver)) continue;
38910
+ const scopedName = call.in_method ? `${call.in_method}:${receiver}` : receiver;
38911
+ if (!tainted.has(receiver) && !tainted.has(scopedName)) continue;
38912
+ ctx.addFinding({
38913
+ id: `format_string-${meta.file}-${call.location.line}-${receiver}`,
38914
+ pass: this.name,
38915
+ category: "security",
38916
+ rule_id: "format_string",
38917
+ cwe: "CWE-134",
38918
+ severity: "medium",
38919
+ level: "warning",
38920
+ 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__[\u2026]}\` chains or crash the process via unbounded specifiers. Use a literal format string and pass user input as an argument instead.`,
38921
+ file: meta.file,
38922
+ line: call.location.line
38923
+ });
38924
+ count++;
38925
+ }
38926
+ return { findingsEmitted: count };
38927
+ }
38928
+ };
38929
+
38708
38930
  // src/analysis/passes/spring4shell-pass.ts
38709
38931
  var CONTROLLER_ANNOTATIONS = /* @__PURE__ */ new Set([
38710
38932
  "Controller",
@@ -43045,6 +43267,8 @@ async function analyze(code, filePath, language, options = {}) {
43045
43267
  if (!disabledPasses.has("library-profile-cwe22-path-gate"))
43046
43268
  pipeline.add(new LibraryProfileCwe22PathGatePass());
43047
43269
  if (!disabledPasses.has("scan-secrets")) pipeline.add(new ScanSecretsPass());
43270
+ if (!disabledPasses.has("python-receiver-taint-format"))
43271
+ pipeline.add(new PythonReceiverTaintFormatPass());
43048
43272
  if (!disabledPasses.has("dead-code")) pipeline.add(new DeadCodePass());
43049
43273
  if (!disabledPasses.has("missing-await")) pipeline.add(new MissingAwaitPass());
43050
43274
  if (!disabledPasses.has("n-plus-one")) pipeline.add(new NPlusOnePass());
@@ -11672,6 +11672,20 @@ var DEFAULT_SINKS = [
11672
11672
  // Sandbox/script security
11673
11673
  { method: "onNewInstance", class: "SandboxInterceptor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
11674
11674
  // Java Log Injection (slf4j / logback / java.util.logging) — CWE-117
11675
+ //
11676
+ // Classification decision — cognium-dev #264: Logger receivers stay
11677
+ // as log_injection (CWE-117), NOT format_string (CWE-134). The real
11678
+ // vulnerability with tainted data flowing into Logger.log(fmt, ...)
11679
+ // is log-forgery via CRLF injection, not format-string exploitation.
11680
+ // A genuine format_string vulnerability requires the FORMAT STRING
11681
+ // itself to be attacker-controlled, which is virtually never the
11682
+ // case in Logger APIs (the format string is a literal at the call
11683
+ // site). Adding format_string sinks for these same receivers would
11684
+ // duplicate the existing log_injection findings on every call site
11685
+ // without adding real signal. The rare edge case where a caller
11686
+ // passes `taintedFmt` at arg[0] is still caught by these entries
11687
+ // (via arg_positions[0] on the SLF4J-family methods), classified as
11688
+ // log_injection.
11675
11689
  // Issue #44: log.info/warn/error/debug emit the message argument and any
11676
11690
  // {} format arguments to the log stream. Untrusted input forwarded into
11677
11691
  // these calls allows log forging (newline injection) and downstream log
@@ -12269,6 +12283,16 @@ var DEFAULT_SINKS = [
12269
12283
  { method: "format", class: "String", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12270
12284
  { method: "format", class: "Formatter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12271
12285
  { method: "printf", class: "System.out", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12286
+ // cognium-dev #264 — extend the Java format-string surface.
12287
+ // MessageFormat.format(pattern, args) — same receiver-family as String.format
12288
+ // but on the java.text side. PrintStream/PrintWriter.{printf,format} —
12289
+ // arbitrary streams (not just System.out) also honour format-string
12290
+ // specifiers, so a tainted first arg is a genuine CWE-134 sink.
12291
+ { method: "format", class: "MessageFormat", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12292
+ { method: "printf", class: "PrintStream", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12293
+ { method: "format", class: "PrintStream", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12294
+ { method: "printf", class: "PrintWriter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12295
+ { method: "format", class: "PrintWriter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12272
12296
  // NOTE: Python `userFmt.format(...)` and `userFmt % args` require
12273
12297
  // receiver-taint or operator-LHS-taint tracking — the format string is the
12274
12298
  // receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
@@ -12280,6 +12304,12 @@ var DEFAULT_SINKS = [
12280
12304
  { method: "Printf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12281
12305
  { method: "Errorf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12282
12306
  { method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] },
12307
+ // cognium-dev #264 — Go stdlib `log` package format-string entry points.
12308
+ // log.Printf / Fatalf / Panicf take the format string at arg[0]; tainted
12309
+ // format string reaches the same fmt.Sprintf machinery internally.
12310
+ { method: "Printf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12311
+ { method: "Fatalf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12312
+ { method: "Panicf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12283
12313
  // CRLF / HTTP response splitting (CWE-113) — Sprint 6, #86.
12284
12314
  // Node.js / Express response header / cookie sinks. The header *name* (arg 0)
12285
12315
  // is also CRLF-sensitive but is almost always a string literal; we model
@@ -14372,6 +14402,11 @@ function calculateSinkConfidence(call, pattern) {
14372
14402
  if (pattern.class && call.receiver) {
14373
14403
  if (receiverMightBeClass(call.receiver, pattern.class)) {
14374
14404
  confidence += 0.1;
14405
+ const receiverLc = call.receiver.toLowerCase();
14406
+ const classLc = pattern.class.toLowerCase();
14407
+ if (call.receiver === pattern.class || receiverLc === classLc) {
14408
+ confidence += 0.05;
14409
+ }
14375
14410
  }
14376
14411
  }
14377
14412
  if (pattern.severity === "critical") {
@@ -11606,6 +11606,20 @@ var DEFAULT_SINKS = [
11606
11606
  // Sandbox/script security
11607
11607
  { method: "onNewInstance", class: "SandboxInterceptor", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
11608
11608
  // Java Log Injection (slf4j / logback / java.util.logging) — CWE-117
11609
+ //
11610
+ // Classification decision — cognium-dev #264: Logger receivers stay
11611
+ // as log_injection (CWE-117), NOT format_string (CWE-134). The real
11612
+ // vulnerability with tainted data flowing into Logger.log(fmt, ...)
11613
+ // is log-forgery via CRLF injection, not format-string exploitation.
11614
+ // A genuine format_string vulnerability requires the FORMAT STRING
11615
+ // itself to be attacker-controlled, which is virtually never the
11616
+ // case in Logger APIs (the format string is a literal at the call
11617
+ // site). Adding format_string sinks for these same receivers would
11618
+ // duplicate the existing log_injection findings on every call site
11619
+ // without adding real signal. The rare edge case where a caller
11620
+ // passes `taintedFmt` at arg[0] is still caught by these entries
11621
+ // (via arg_positions[0] on the SLF4J-family methods), classified as
11622
+ // log_injection.
11609
11623
  // Issue #44: log.info/warn/error/debug emit the message argument and any
11610
11624
  // {} format arguments to the log stream. Untrusted input forwarded into
11611
11625
  // these calls allows log forging (newline injection) and downstream log
@@ -12203,6 +12217,16 @@ var DEFAULT_SINKS = [
12203
12217
  { method: "format", class: "String", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12204
12218
  { method: "format", class: "Formatter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12205
12219
  { method: "printf", class: "System.out", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12220
+ // cognium-dev #264 — extend the Java format-string surface.
12221
+ // MessageFormat.format(pattern, args) — same receiver-family as String.format
12222
+ // but on the java.text side. PrintStream/PrintWriter.{printf,format} —
12223
+ // arbitrary streams (not just System.out) also honour format-string
12224
+ // specifiers, so a tainted first arg is a genuine CWE-134 sink.
12225
+ { method: "format", class: "MessageFormat", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12226
+ { method: "printf", class: "PrintStream", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12227
+ { method: "format", class: "PrintStream", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12228
+ { method: "printf", class: "PrintWriter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12229
+ { method: "format", class: "PrintWriter", type: "format_string", cwe: "CWE-134", severity: "high", arg_positions: [0], languages: ["java"] },
12206
12230
  // NOTE: Python `userFmt.format(...)` and `userFmt % args` require
12207
12231
  // receiver-taint or operator-LHS-taint tracking — the format string is the
12208
12232
  // receiver, not an argument. Deferred to Sprint 6 (#86 follow-up).
@@ -12214,6 +12238,12 @@ var DEFAULT_SINKS = [
12214
12238
  { method: "Printf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12215
12239
  { method: "Errorf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12216
12240
  { method: "Fprintf", class: "fmt", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [1], languages: ["go"] },
12241
+ // cognium-dev #264 — Go stdlib `log` package format-string entry points.
12242
+ // log.Printf / Fatalf / Panicf take the format string at arg[0]; tainted
12243
+ // format string reaches the same fmt.Sprintf machinery internally.
12244
+ { method: "Printf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12245
+ { method: "Fatalf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12246
+ { method: "Panicf", class: "log", type: "format_string", cwe: "CWE-134", severity: "medium", arg_positions: [0], languages: ["go"] },
12217
12247
  // CRLF / HTTP response splitting (CWE-113) — Sprint 6, #86.
12218
12248
  // Node.js / Express response header / cookie sinks. The header *name* (arg 0)
12219
12249
  // is also CRLF-sensitive but is almost always a string literal; we model
@@ -14306,6 +14336,11 @@ function calculateSinkConfidence(call, pattern) {
14306
14336
  if (pattern.class && call.receiver) {
14307
14337
  if (receiverMightBeClass(call.receiver, pattern.class)) {
14308
14338
  confidence += 0.1;
14339
+ const receiverLc = call.receiver.toLowerCase();
14340
+ const classLc = pattern.class.toLowerCase();
14341
+ if (call.receiver === pattern.class || receiverLc === classLc) {
14342
+ confidence += 0.05;
14343
+ }
14309
14344
  }
14310
14345
  }
14311
14346
  if (pattern.severity === "critical") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "3.178.0",
3
+ "version": "3.180.0",
4
4
  "description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",