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.
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +30 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/dependency-versions.d.ts +171 -0
- package/dist/analysis/dependency-versions.d.ts.map +1 -1
- package/dist/analysis/dependency-versions.js +348 -0
- package/dist/analysis/dependency-versions.js.map +1 -1
- package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts +2 -0
- package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/deserialization-safety-gate-pass.js +56 -9
- package/dist/analysis/passes/deserialization-safety-gate-pass.js.map +1 -1
- package/dist/analysis/passes/python-receiver-taint-format-pass.d.ts +48 -0
- package/dist/analysis/passes/python-receiver-taint-format-pass.d.ts.map +1 -0
- package/dist/analysis/passes/python-receiver-taint-format-pass.js +91 -0
- package/dist/analysis/passes/python-receiver-taint-format-pass.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +18 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +46 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +9 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +229 -5
- package/dist/core/circle-ir-core.cjs +35 -0
- package/dist/core/circle-ir-core.js +35 -0
- package/package.json +1 -1
|
@@ -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
|
|
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.
|
|
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",
|