cognium-dev 4.9.21 → 4.9.23
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/cli.js +373 -20
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -14400,6 +14400,35 @@ function classifyGoDestinationType(decl) {
|
|
|
14400
14400
|
return "unsafe";
|
|
14401
14401
|
return "safe";
|
|
14402
14402
|
}
|
|
14403
|
+
function isGoFprintToNonResponseWriter(call, pattern, language, sourceLines) {
|
|
14404
|
+
if (language !== "go")
|
|
14405
|
+
return false;
|
|
14406
|
+
if (pattern.type !== "xss" || pattern.class !== "fmt")
|
|
14407
|
+
return false;
|
|
14408
|
+
if (!/^Fprint(?:f|ln)?$/.test(call.method_name))
|
|
14409
|
+
return false;
|
|
14410
|
+
if (!sourceLines)
|
|
14411
|
+
return true;
|
|
14412
|
+
const writer = call.arguments.find((a) => a.position === 0);
|
|
14413
|
+
const name2 = (writer?.expression ?? "").trim();
|
|
14414
|
+
if (!/^[A-Za-z_]\w*$/.test(name2))
|
|
14415
|
+
return true;
|
|
14416
|
+
const rwRe = new RegExp(`\\b${name2}\\s+(?:net/)?http\\.ResponseWriter\\b`);
|
|
14417
|
+
for (let i2 = call.location.line - 1;i2 >= 0; i2--) {
|
|
14418
|
+
const line = sourceLines[i2] ?? "";
|
|
14419
|
+
const funcIdx = line.search(/\bfunc\b/);
|
|
14420
|
+
if (funcIdx < 0)
|
|
14421
|
+
continue;
|
|
14422
|
+
const header = sourceLines.slice(i2, i2 + 4).join(" ");
|
|
14423
|
+
const brace = header.indexOf("{");
|
|
14424
|
+
const params = header.slice(funcIdx, brace >= 0 ? brace : header.length);
|
|
14425
|
+
if (rwRe.test(params))
|
|
14426
|
+
return false;
|
|
14427
|
+
if (/^\s*func\b/.test(line))
|
|
14428
|
+
return true;
|
|
14429
|
+
}
|
|
14430
|
+
return true;
|
|
14431
|
+
}
|
|
14403
14432
|
function isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines) {
|
|
14404
14433
|
if (language !== "go")
|
|
14405
14434
|
return false;
|
|
@@ -14545,6 +14574,9 @@ function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types)
|
|
|
14545
14574
|
if (isSafeGoJsonUnmarshalCall(call, pattern, language, sourceLines)) {
|
|
14546
14575
|
continue;
|
|
14547
14576
|
}
|
|
14577
|
+
if (isGoFprintToNonResponseWriter(call, pattern, language, sourceLines)) {
|
|
14578
|
+
continue;
|
|
14579
|
+
}
|
|
14548
14580
|
if (isSafeJinjaRenderCall(call, pattern, language, sourceLines)) {
|
|
14549
14581
|
continue;
|
|
14550
14582
|
}
|
|
@@ -24126,7 +24158,23 @@ class GoPlugin extends BaseLanguagePlugin {
|
|
|
24126
24158
|
type: "xss",
|
|
24127
24159
|
cwe: "CWE-79",
|
|
24128
24160
|
severity: "high",
|
|
24129
|
-
argPositions: [1]
|
|
24161
|
+
argPositions: [1, 2, 3, 4, 5, 6]
|
|
24162
|
+
},
|
|
24163
|
+
{
|
|
24164
|
+
method: "Fprint",
|
|
24165
|
+
class: "fmt",
|
|
24166
|
+
type: "xss",
|
|
24167
|
+
cwe: "CWE-79",
|
|
24168
|
+
severity: "high",
|
|
24169
|
+
argPositions: [1, 2, 3, 4, 5, 6]
|
|
24170
|
+
},
|
|
24171
|
+
{
|
|
24172
|
+
method: "Fprintln",
|
|
24173
|
+
class: "fmt",
|
|
24174
|
+
type: "xss",
|
|
24175
|
+
cwe: "CWE-79",
|
|
24176
|
+
severity: "high",
|
|
24177
|
+
argPositions: [1, 2, 3, 4, 5, 6]
|
|
24130
24178
|
},
|
|
24131
24179
|
{
|
|
24132
24180
|
method: "Write",
|
|
@@ -24604,16 +24652,22 @@ class CSharpPlugin extends BaseLanguagePlugin {
|
|
|
24604
24652
|
}
|
|
24605
24653
|
|
|
24606
24654
|
// ../circle-ir/dist/languages/plugins/index.js
|
|
24655
|
+
function createBuiltinPlugins() {
|
|
24656
|
+
return [
|
|
24657
|
+
new JavaPlugin,
|
|
24658
|
+
new JavaScriptPlugin,
|
|
24659
|
+
new PythonPlugin,
|
|
24660
|
+
new RustPlugin,
|
|
24661
|
+
new BashPlugin,
|
|
24662
|
+
new HtmlPlugin,
|
|
24663
|
+
new VuePlugin,
|
|
24664
|
+
new GoPlugin,
|
|
24665
|
+
new CSharpPlugin
|
|
24666
|
+
];
|
|
24667
|
+
}
|
|
24607
24668
|
function registerBuiltinPlugins() {
|
|
24608
|
-
|
|
24609
|
-
|
|
24610
|
-
registerLanguage(new PythonPlugin);
|
|
24611
|
-
registerLanguage(new RustPlugin);
|
|
24612
|
-
registerLanguage(new BashPlugin);
|
|
24613
|
-
registerLanguage(new HtmlPlugin);
|
|
24614
|
-
registerLanguage(new VuePlugin);
|
|
24615
|
-
registerLanguage(new GoPlugin);
|
|
24616
|
-
registerLanguage(new CSharpPlugin);
|
|
24669
|
+
for (const plugin of createBuiltinPlugins())
|
|
24670
|
+
registerLanguage(plugin);
|
|
24617
24671
|
}
|
|
24618
24672
|
// ../circle-ir/dist/analysis/passes/cross-file-pass.js
|
|
24619
24673
|
class CrossFilePass {
|
|
@@ -25804,6 +25858,8 @@ class LanguageSourcesPass {
|
|
|
25804
25858
|
additionalSources.push(...findGoArgvSources(code, language));
|
|
25805
25859
|
additionalSources.push(...findGoArchiveEntrySources(code, language));
|
|
25806
25860
|
additionalSources.push(...findGoMultipartUploadSources(code, language));
|
|
25861
|
+
additionalSources.push(...findGoGrpcRequestSources(code, language));
|
|
25862
|
+
additionalSources.push(...findGoRequestBodySources(code, language));
|
|
25807
25863
|
const jsDOMSinks = findJavaScriptDOMSinks(code, language);
|
|
25808
25864
|
for (const s of jsDOMSinks) {
|
|
25809
25865
|
const alreadyExists = additionalSinks.some((x) => x.line === s.line && x.cwe === s.cwe);
|
|
@@ -26549,6 +26605,127 @@ function findGoMultipartUploadSources(sourceCode, language) {
|
|
|
26549
26605
|
}
|
|
26550
26606
|
return sources;
|
|
26551
26607
|
}
|
|
26608
|
+
function findGoGrpcRequestSources(sourceCode, language) {
|
|
26609
|
+
if (language !== "go")
|
|
26610
|
+
return [];
|
|
26611
|
+
if (!sourceCode.includes("context.Context"))
|
|
26612
|
+
return [];
|
|
26613
|
+
const sources = [];
|
|
26614
|
+
const lines = sourceCode.split(`
|
|
26615
|
+
`);
|
|
26616
|
+
const sigRe = /^func\s*\([^)]*\)\s*[A-Z]\w*\s*\(\s*\w+\s+context\.Context\s*,\s*([A-Za-z_]\w*)\s+\*(?:\w+\.)?\w*(?:Request|Req)\s*\)\s*\(\s*\*[\w.]+\s*,\s*error\s*\)\s*\{/;
|
|
26617
|
+
const controlRe = /^\s*(?:\}\s*else\s+)?(?:if|for|switch|case|return)\b/;
|
|
26618
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26619
|
+
const sig = sigRe.exec(lines[i2]);
|
|
26620
|
+
if (!sig)
|
|
26621
|
+
continue;
|
|
26622
|
+
const param = sig[1];
|
|
26623
|
+
if (param === "_")
|
|
26624
|
+
continue;
|
|
26625
|
+
const readRe = new RegExp(`(?<![\\w.])${param}\\s*\\.\\s*(?:Get[A-Z]\\w*\\s*\\(\\s*\\)|[A-Z]\\w*\\b(?!\\s*\\())`);
|
|
26626
|
+
const directReadRe = new RegExp(`^(?:(?:\\[\\]\\w+|string)\\s*\\(\\s*)*${param}(?:\\s*\\.\\s*(?:Get[A-Z]\\w*\\s*\\(\\s*\\)|[A-Z]\\w*\\b(?!\\s*\\()))+(?:\\s*\\))*$`);
|
|
26627
|
+
const numericParseRe = new RegExp(`\\bstrconv\\s*\\.\\s*(?:Atoi|ParseInt|ParseUint|ParseFloat|ParseBool)\\s*\\(\\s*${param}\\s*\\.`);
|
|
26628
|
+
let end = i2 + 1;
|
|
26629
|
+
while (end < lines.length && !/^\}/.test(lines[end]))
|
|
26630
|
+
end++;
|
|
26631
|
+
for (let j = i2 + 1;j < end; j++) {
|
|
26632
|
+
const line = lines[j];
|
|
26633
|
+
const read = readRe.exec(line);
|
|
26634
|
+
if (!read)
|
|
26635
|
+
continue;
|
|
26636
|
+
if (numericParseRe.test(line))
|
|
26637
|
+
continue;
|
|
26638
|
+
let variable;
|
|
26639
|
+
const unmarshal = /\bUnmarshal\w*\s*\([^&]*&\s*([A-Za-z_]\w*)/.exec(line);
|
|
26640
|
+
const assign = /^\s*(?:var\s+)?([A-Za-z_][\w\s,]*?)\s*:?=(?!=)\s*(.+)$/.exec(line);
|
|
26641
|
+
if (unmarshal && line.indexOf(read[0]) < line.indexOf("&")) {
|
|
26642
|
+
variable = unmarshal[1];
|
|
26643
|
+
} else if (assign && directReadRe.test(assign[2].trim()) && !controlRe.test(line)) {
|
|
26644
|
+
variable = assign[1].split(",").map((v) => v.trim()).find((v) => v !== "_" && v !== "err" && v !== "ok" && /^[A-Za-z_]\w*$/.test(v));
|
|
26645
|
+
if (!variable)
|
|
26646
|
+
continue;
|
|
26647
|
+
} else if (controlRe.test(line)) {
|
|
26648
|
+
continue;
|
|
26649
|
+
} else {
|
|
26650
|
+
variable = param;
|
|
26651
|
+
}
|
|
26652
|
+
const lineNumber = j + 1;
|
|
26653
|
+
if (sources.some((sc) => sc.line === lineNumber && sc.variable === variable))
|
|
26654
|
+
continue;
|
|
26655
|
+
sources.push({
|
|
26656
|
+
type: "http_body",
|
|
26657
|
+
location: `${read[0].replace(/\s+/g, "")} — field of the gRPC request message \`${param}\``,
|
|
26658
|
+
severity: "high",
|
|
26659
|
+
line: lineNumber,
|
|
26660
|
+
confidence: 0.85,
|
|
26661
|
+
variable
|
|
26662
|
+
});
|
|
26663
|
+
}
|
|
26664
|
+
i2 = end;
|
|
26665
|
+
}
|
|
26666
|
+
return sources;
|
|
26667
|
+
}
|
|
26668
|
+
function findGoRequestBodySources(sourceCode, language) {
|
|
26669
|
+
if (language !== "go")
|
|
26670
|
+
return [];
|
|
26671
|
+
if (!sourceCode.includes(".Body"))
|
|
26672
|
+
return [];
|
|
26673
|
+
const lines = sourceCode.split(`
|
|
26674
|
+
`);
|
|
26675
|
+
const reqNames = new Set;
|
|
26676
|
+
const paramRe = /([A-Za-z_]\w*)\s+\*http\.Request\b/g;
|
|
26677
|
+
for (const line of lines) {
|
|
26678
|
+
let m;
|
|
26679
|
+
paramRe.lastIndex = 0;
|
|
26680
|
+
while ((m = paramRe.exec(line)) !== null)
|
|
26681
|
+
reqNames.add(m[1]);
|
|
26682
|
+
}
|
|
26683
|
+
if (reqNames.size === 0)
|
|
26684
|
+
return [];
|
|
26685
|
+
const bodyAlt = [...reqNames].map((n) => n.replace(/\$/g, "\\$")).join("|");
|
|
26686
|
+
const bodyRef = `(?<![\\w.])(?:${bodyAlt})\\s*\\.\\s*Body\\b`;
|
|
26687
|
+
const sources = [];
|
|
26688
|
+
const push = (variable, line, how) => {
|
|
26689
|
+
if (variable === "_" || variable === "")
|
|
26690
|
+
return;
|
|
26691
|
+
if (sources.some((s) => s.line === line && s.variable === variable))
|
|
26692
|
+
return;
|
|
26693
|
+
sources.push({
|
|
26694
|
+
type: "http_body",
|
|
26695
|
+
location: `${how} — HTTP request body`,
|
|
26696
|
+
severity: "high",
|
|
26697
|
+
line,
|
|
26698
|
+
confidence: 0.85,
|
|
26699
|
+
...variable ? { variable } : {}
|
|
26700
|
+
});
|
|
26701
|
+
};
|
|
26702
|
+
const bodyRe = new RegExp(bodyRef);
|
|
26703
|
+
const decodeRe = new RegExp(`(?:json|xml)\\s*\\.\\s*NewDecoder\\s*\\(\\s*(?:io\\.NopCloser\\s*\\(\\s*)?(?:${bodyAlt})\\s*\\.\\s*Body\\b[^)]*\\)\\s*\\)?\\s*\\.\\s*Decode\\s*\\(\\s*&\\s*([A-Za-z_]\\w*)`);
|
|
26704
|
+
const readAllRe = new RegExp(`\\b([A-Za-z_]\\w*)\\s*,\\s*\\w+\\s*:?=\\s*(?:io|ioutil)\\s*\\.\\s*ReadAll\\s*\\(\\s*(?:${bodyAlt})\\s*\\.\\s*Body\\b`);
|
|
26705
|
+
const copyRe = new RegExp(`\\bio\\s*\\.\\s*Copy\\s*\\(\\s*([A-Za-z_]\\w*|&\\s*[A-Za-z_]\\w*)\\s*,\\s*(?:${bodyAlt})\\s*\\.\\s*Body\\b`);
|
|
26706
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26707
|
+
const line = lines[i2];
|
|
26708
|
+
if (!bodyRe.test(line))
|
|
26709
|
+
continue;
|
|
26710
|
+
const n = i2 + 1;
|
|
26711
|
+
const dec = decodeRe.exec(line);
|
|
26712
|
+
if (dec) {
|
|
26713
|
+
push(dec[1], n, `json/xml decode of ${dec[0].split(".")[0]}.Body`);
|
|
26714
|
+
continue;
|
|
26715
|
+
}
|
|
26716
|
+
const ra = readAllRe.exec(line);
|
|
26717
|
+
if (ra) {
|
|
26718
|
+
push(ra[1], n, "io.ReadAll(req.Body)");
|
|
26719
|
+
continue;
|
|
26720
|
+
}
|
|
26721
|
+
const cp = copyRe.exec(line);
|
|
26722
|
+
if (cp) {
|
|
26723
|
+
push(cp[1].replace(/^&\s*/, ""), n, "io.Copy(dst, req.Body)");
|
|
26724
|
+
continue;
|
|
26725
|
+
}
|
|
26726
|
+
}
|
|
26727
|
+
return sources;
|
|
26728
|
+
}
|
|
26552
26729
|
function findCSharpRequestSources(sourceCode, language) {
|
|
26553
26730
|
if (language !== "csharp")
|
|
26554
26731
|
return [];
|
|
@@ -37784,6 +37961,116 @@ function pickScopedSource(sources, sinkLine, methodName, types, taintedVar) {
|
|
|
37784
37961
|
}
|
|
37785
37962
|
|
|
37786
37963
|
// ../circle-ir/dist/analysis/interprocedural.js
|
|
37964
|
+
var GO_BUILTIN_CALLS = new Set([
|
|
37965
|
+
"len",
|
|
37966
|
+
"cap",
|
|
37967
|
+
"make",
|
|
37968
|
+
"new",
|
|
37969
|
+
"append",
|
|
37970
|
+
"copy",
|
|
37971
|
+
"delete",
|
|
37972
|
+
"close",
|
|
37973
|
+
"panic",
|
|
37974
|
+
"recover",
|
|
37975
|
+
"print",
|
|
37976
|
+
"println",
|
|
37977
|
+
"min",
|
|
37978
|
+
"max",
|
|
37979
|
+
"clear",
|
|
37980
|
+
"string",
|
|
37981
|
+
"int",
|
|
37982
|
+
"int8",
|
|
37983
|
+
"int16",
|
|
37984
|
+
"int32",
|
|
37985
|
+
"int64",
|
|
37986
|
+
"uint",
|
|
37987
|
+
"uint8",
|
|
37988
|
+
"uint16",
|
|
37989
|
+
"uint32",
|
|
37990
|
+
"uint64",
|
|
37991
|
+
"float32",
|
|
37992
|
+
"float64",
|
|
37993
|
+
"bool",
|
|
37994
|
+
"byte",
|
|
37995
|
+
"rune",
|
|
37996
|
+
"error"
|
|
37997
|
+
]);
|
|
37998
|
+
var GO_ERROR_AND_LOG_CALLS = new Set([
|
|
37999
|
+
"Errorf",
|
|
38000
|
+
"Sprintf",
|
|
38001
|
+
"Sprint",
|
|
38002
|
+
"Sprintln",
|
|
38003
|
+
"Print",
|
|
38004
|
+
"Printf",
|
|
38005
|
+
"Println",
|
|
38006
|
+
"Fatal",
|
|
38007
|
+
"Fatalf",
|
|
38008
|
+
"Fatalln",
|
|
38009
|
+
"Panic",
|
|
38010
|
+
"Panicf",
|
|
38011
|
+
"Panicln",
|
|
38012
|
+
"Debug",
|
|
38013
|
+
"Debugf",
|
|
38014
|
+
"Debugln",
|
|
38015
|
+
"Debugw",
|
|
38016
|
+
"Info",
|
|
38017
|
+
"Infof",
|
|
38018
|
+
"Infoln",
|
|
38019
|
+
"Infow",
|
|
38020
|
+
"InfoS",
|
|
38021
|
+
"Warn",
|
|
38022
|
+
"Warnf",
|
|
38023
|
+
"Warnln",
|
|
38024
|
+
"Warnw",
|
|
38025
|
+
"Warning",
|
|
38026
|
+
"Warningf",
|
|
38027
|
+
"Warningln",
|
|
38028
|
+
"Error",
|
|
38029
|
+
"Errorln",
|
|
38030
|
+
"Errorw",
|
|
38031
|
+
"ErrorS",
|
|
38032
|
+
"Trace",
|
|
38033
|
+
"Tracef",
|
|
38034
|
+
"V",
|
|
38035
|
+
"WithError",
|
|
38036
|
+
"WithField",
|
|
38037
|
+
"WithFields",
|
|
38038
|
+
"WithValues",
|
|
38039
|
+
"Msg",
|
|
38040
|
+
"Msgf"
|
|
38041
|
+
]);
|
|
38042
|
+
var GO_ERROR_PACKAGES = new Set(["errors", "status", "xerrors", "multierr", "multierror"]);
|
|
38043
|
+
var GO_PURE_PACKAGES = new Set([
|
|
38044
|
+
"strings",
|
|
38045
|
+
"strconv",
|
|
38046
|
+
"bytes",
|
|
38047
|
+
"unicode",
|
|
38048
|
+
"utf8",
|
|
38049
|
+
"path",
|
|
38050
|
+
"filepath",
|
|
38051
|
+
"math",
|
|
38052
|
+
"sort",
|
|
38053
|
+
"slices",
|
|
38054
|
+
"maps",
|
|
38055
|
+
"time",
|
|
38056
|
+
"regexp",
|
|
38057
|
+
"url",
|
|
38058
|
+
"hex",
|
|
38059
|
+
"base64"
|
|
38060
|
+
]);
|
|
38061
|
+
function isGoNonEscapeCall(call) {
|
|
38062
|
+
const name2 = call.method_name;
|
|
38063
|
+
const recv = (call.receiver ?? "").split(".").pop() ?? "";
|
|
38064
|
+
if (!call.receiver && GO_BUILTIN_CALLS.has(name2))
|
|
38065
|
+
return true;
|
|
38066
|
+
if (GO_ERROR_PACKAGES.has(recv))
|
|
38067
|
+
return true;
|
|
38068
|
+
if (GO_PURE_PACKAGES.has(recv))
|
|
38069
|
+
return true;
|
|
38070
|
+
if (GO_ERROR_AND_LOG_CALLS.has(name2))
|
|
38071
|
+
return true;
|
|
38072
|
+
return false;
|
|
38073
|
+
}
|
|
37787
38074
|
function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourcesOrSanitizers, sinksOrOptions, sanitizersArg, optionsArg = {}) {
|
|
37788
38075
|
let graph;
|
|
37789
38076
|
let sources;
|
|
@@ -38015,6 +38302,9 @@ function analyzeInterprocedural2(graphOrTypes, callsOrSources, dfgOrSinks, sourc
|
|
|
38015
38302
|
if (!targetMethod) {
|
|
38016
38303
|
if (taintedArgPositions.length > 0 && !collectionMethods.has(call.method_name) && !sanitizerMethods.has(call.method_name) && !safeUtilityMethods.has(call.method_name) && !ormQueryBuilderMethods.has(call.method_name)) {
|
|
38017
38304
|
const isBash = graph.ir.meta.language === "bash";
|
|
38305
|
+
if (graph.ir.meta.language === "go" && isGoNonEscapeCall(call)) {
|
|
38306
|
+
continue;
|
|
38307
|
+
}
|
|
38018
38308
|
const bashSafeBuiltins = new Set([
|
|
38019
38309
|
"echo",
|
|
38020
38310
|
"printf",
|
|
@@ -46991,6 +47281,8 @@ var JAVA_XML_OUTPUT_ONLY_RE = /\bnew\s+(?:DOMSource|StreamResult)\s*\(/;
|
|
|
46991
47281
|
var JAVA_XML_PARSE_INPUT_RE = /\bnew\s+(?:StreamSource|SAXSource|InputSource)\s*\(/;
|
|
46992
47282
|
var JAVA_DOC_BUILDER_NEW_DOCUMENT_RE = /\.\s*newDocument\s*\(\s*\)/;
|
|
46993
47283
|
var JAVA_DOC_BUILDER_PARSE_RE = /(?:DocumentBuilder|builder)\s*\.\s*parse\s*\(/;
|
|
47284
|
+
var JAVA_HARDENING_SETTER_RE = /\.\s*(?:setFeature|setAttribute|setProperty|setXIncludeAware|setExpandEntityReferences)\s*\(/;
|
|
47285
|
+
var JAVA_FACTORY_CHAINED_RE = /\bnewInstance\s*\([^()]*\)\s*\.\s*(?:newDocumentBuilder|newSAXParser|newTransformer|newTemplates|newSchema|createXMLStreamReader|createXMLEventReader)\s*\(/;
|
|
46994
47286
|
var PY_LXML_PARSER_INSECURE_DEFAULT_RE = /\bresolve_entities\s*=\s*False\b/;
|
|
46995
47287
|
|
|
46996
47288
|
class XmlEntityExpansionPass {
|
|
@@ -47003,8 +47295,10 @@ class XmlEntityExpansionPass {
|
|
|
47003
47295
|
const code = ctx.code ?? "";
|
|
47004
47296
|
if (language === "java") {
|
|
47005
47297
|
const safeInFile = JAVA_SAFE_EVIDENCE_RE.test(code);
|
|
47006
|
-
|
|
47007
|
-
|
|
47298
|
+
const processWide = safeInFile && /jdk\.xml\./.test(code);
|
|
47299
|
+
const lines = safeInFile ? code.split(`
|
|
47300
|
+
`) : [];
|
|
47301
|
+
const methodRanges = safeInFile ? this.methodRanges(graph.ir.types) : [];
|
|
47008
47302
|
const isXmlOutputOnly = JAVA_XML_OUTPUT_ONLY_RE.test(code) && !JAVA_XML_PARSE_INPUT_RE.test(code);
|
|
47009
47303
|
const isDocumentBuilderEmptyOnly = JAVA_DOC_BUILDER_NEW_DOCUMENT_RE.test(code) && !JAVA_DOC_BUILDER_PARSE_RE.test(code);
|
|
47010
47304
|
for (const call of graph.ir.calls) {
|
|
@@ -47016,6 +47310,8 @@ class XmlEntityExpansionPass {
|
|
|
47016
47310
|
if (det.api === "DocumentBuilderFactory" && isDocumentBuilderEmptyOnly)
|
|
47017
47311
|
continue;
|
|
47018
47312
|
const line = call.location.line;
|
|
47313
|
+
if (safeInFile && (processWide || !this.isProvablyUnhardened(lines, line, methodRanges, det.api)))
|
|
47314
|
+
continue;
|
|
47019
47315
|
findings.push({ line, language, ...det });
|
|
47020
47316
|
ctx.addFinding({
|
|
47021
47317
|
id: `${this.name}-${file}-${line}-${det.api}`,
|
|
@@ -47025,11 +47321,11 @@ class XmlEntityExpansionPass {
|
|
|
47025
47321
|
cwe: det.cwe,
|
|
47026
47322
|
severity: "high",
|
|
47027
47323
|
level: "error",
|
|
47028
|
-
message: `${det.api} created without disabling DTD / external-entity ` + "processing. Vulnerable to
|
|
47324
|
+
message: `${det.api} created without disabling DTD / external-entity ` + "processing. Vulnerable to external-entity disclosure " + "(CWE-611) and billion-laughs / quadratic blow-up DoS " + '(CWE-776). Add `setFeature("http://apache.org/xml/features/' + 'disallow-doctype-decl", true)` (or the equivalent) before ' + "parsing.",
|
|
47029
47325
|
file,
|
|
47030
47326
|
line,
|
|
47031
47327
|
fix: this.fixForJava(det.api),
|
|
47032
|
-
evidence: { ...det, language, safeFeatureInFile:
|
|
47328
|
+
evidence: { ...det, language, safeFeatureInFile: safeInFile }
|
|
47033
47329
|
});
|
|
47034
47330
|
}
|
|
47035
47331
|
return { findings };
|
|
@@ -47052,7 +47348,7 @@ class XmlEntityExpansionPass {
|
|
|
47052
47348
|
cwe: det.cwe,
|
|
47053
47349
|
severity: "high",
|
|
47054
47350
|
level: "error",
|
|
47055
|
-
message: `${det.api} called without an entity-safe parser. Vulnerable ` + "to billion-laughs / quadratic blow-up DoS (CWE-776)
|
|
47351
|
+
message: `${det.api} called without an entity-safe parser. Vulnerable ` + (det.cwe === "CWE-611" ? "to external-entity disclosure (CWE-611) and billion-laughs / " + "quadratic blow-up DoS (CWE-776). " : "to billion-laughs / quadratic blow-up DoS (CWE-776). ") + "Use `defusedxml` or pass " + "an `XMLParser(resolve_entities=False)` to lxml.",
|
|
47056
47352
|
file,
|
|
47057
47353
|
line,
|
|
47058
47354
|
fix: this.fixForPython(det.api),
|
|
@@ -47063,6 +47359,63 @@ class XmlEntityExpansionPass {
|
|
|
47063
47359
|
}
|
|
47064
47360
|
return { findings };
|
|
47065
47361
|
}
|
|
47362
|
+
methodRanges(types) {
|
|
47363
|
+
const out2 = [];
|
|
47364
|
+
for (const t of types ?? []) {
|
|
47365
|
+
for (const m of t.methods ?? []) {
|
|
47366
|
+
if (typeof m.start_line === "number" && typeof m.end_line === "number")
|
|
47367
|
+
out2.push([m.start_line, m.end_line]);
|
|
47368
|
+
}
|
|
47369
|
+
}
|
|
47370
|
+
return out2;
|
|
47371
|
+
}
|
|
47372
|
+
isProvablyUnhardened(lines, line, methodRanges, api) {
|
|
47373
|
+
let range = null;
|
|
47374
|
+
for (const r of methodRanges) {
|
|
47375
|
+
if (line < r[0] || line > r[1])
|
|
47376
|
+
continue;
|
|
47377
|
+
if (!range || r[1] - r[0] < range[1] - range[0])
|
|
47378
|
+
range = r;
|
|
47379
|
+
}
|
|
47380
|
+
if (!range)
|
|
47381
|
+
return false;
|
|
47382
|
+
let decl = "";
|
|
47383
|
+
let declEnd = line;
|
|
47384
|
+
for (;declEnd <= Math.min(line + 3, range[1]); declEnd++) {
|
|
47385
|
+
decl += " " + (lines[declEnd - 1] ?? "").trim();
|
|
47386
|
+
if (decl.includes(";"))
|
|
47387
|
+
break;
|
|
47388
|
+
}
|
|
47389
|
+
const body2 = lines.slice(line - 1, range[1]).join(`
|
|
47390
|
+
`);
|
|
47391
|
+
if (api === "TransformerFactory" && !/\bnew\s+StreamSource\s*\(/.test(body2))
|
|
47392
|
+
return false;
|
|
47393
|
+
if (JAVA_HARDENING_SETTER_RE.test(body2))
|
|
47394
|
+
return false;
|
|
47395
|
+
const chained = JAVA_FACTORY_CHAINED_RE.exec(decl);
|
|
47396
|
+
if (chained) {
|
|
47397
|
+
const prod = /[\w>\]]\s+([A-Za-z_$][\w$]*)\s*=/.exec(decl);
|
|
47398
|
+
if (!prod)
|
|
47399
|
+
return true;
|
|
47400
|
+
const pn = prod[1].replace(/\$/g, "\\$");
|
|
47401
|
+
const after = lines.slice(declEnd, range[1]).join(`
|
|
47402
|
+
`);
|
|
47403
|
+
return !new RegExp(`[(,]\\s*${pn}\\s*[,)]|\\breturn\\s+${pn}\\s*;`).test(after);
|
|
47404
|
+
}
|
|
47405
|
+
const m = /[\w>\]]\s+([A-Za-z_$][\w$]*)\s*=\s*[\w.]*\bnewInstance\s*\([^()]*\)\s*;/.exec(decl);
|
|
47406
|
+
if (!m)
|
|
47407
|
+
return false;
|
|
47408
|
+
const name2 = m[1].replace(/\$/g, "\\$");
|
|
47409
|
+
const rest = lines.slice(declEnd, range[1]).join(`
|
|
47410
|
+
`);
|
|
47411
|
+
const builds = new RegExp(`\\b${name2}\\s*\\.\\s*(?:newDocumentBuilder|newSAXParser|newTransformer|newTemplates|newSchema|createXMLStreamReader|createXMLEventReader)\\s*\\(`);
|
|
47412
|
+
if (!builds.test(rest))
|
|
47413
|
+
return false;
|
|
47414
|
+
const escapes = new RegExp(`[(,]\\s*${name2}\\s*[,)]|\\breturn\\s+${name2}\\s*;`);
|
|
47415
|
+
if (escapes.test(rest))
|
|
47416
|
+
return false;
|
|
47417
|
+
return true;
|
|
47418
|
+
}
|
|
47066
47419
|
detectJavaCall(call) {
|
|
47067
47420
|
if (call.method_name !== "newInstance")
|
|
47068
47421
|
return null;
|
|
@@ -47073,7 +47426,7 @@ class XmlEntityExpansionPass {
|
|
|
47073
47426
|
return {
|
|
47074
47427
|
pattern: `${factory}.newInstance()`,
|
|
47075
47428
|
api: factory,
|
|
47076
|
-
cwe: "CWE-
|
|
47429
|
+
cwe: "CWE-611"
|
|
47077
47430
|
};
|
|
47078
47431
|
}
|
|
47079
47432
|
}
|
|
@@ -47086,7 +47439,7 @@ class XmlEntityExpansionPass {
|
|
|
47086
47439
|
return {
|
|
47087
47440
|
pattern: `etree.${method}`,
|
|
47088
47441
|
api: `lxml.etree.${method}`,
|
|
47089
|
-
cwe: "CWE-
|
|
47442
|
+
cwe: "CWE-611"
|
|
47090
47443
|
};
|
|
47091
47444
|
}
|
|
47092
47445
|
if ((method === "parse" || method === "fromstring") && (recv === "ET" || recv === "ElementTree" || recv.endsWith(".ElementTree"))) {
|
|
@@ -49777,7 +50130,7 @@ var colors = {
|
|
|
49777
50130
|
};
|
|
49778
50131
|
|
|
49779
50132
|
// src/version.ts
|
|
49780
|
-
var version = "4.9.
|
|
50133
|
+
var version = "4.9.23";
|
|
49781
50134
|
|
|
49782
50135
|
// src/formatters.ts
|
|
49783
50136
|
var LIBRARY_API_SURFACE_TAG2 = "library-api-surface:caller-responsibility";
|
|
@@ -50322,8 +50675,8 @@ function generateSarifResults(results, crossFileData) {
|
|
|
50322
50675
|
|
|
50323
50676
|
// src/build-info.ts
|
|
50324
50677
|
var buildInfo = {
|
|
50325
|
-
gitSha: "
|
|
50326
|
-
builtAt: "2026-09-
|
|
50678
|
+
gitSha: "e7e65f8",
|
|
50679
|
+
builtAt: "2026-09-22T22:15:34.355Z"
|
|
50327
50680
|
};
|
|
50328
50681
|
|
|
50329
50682
|
// src/utils/args.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.23",
|
|
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",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@cognium/project-profile-detect": "1.1.1",
|
|
70
|
-
"circle-ir": "4.9.
|
|
70
|
+
"circle-ir": "4.9.23"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@types/node": "^26.6.1",
|