cognium-dev 3.131.0 → 3.132.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/cli.js +309 -14
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -10902,8 +10902,8 @@ var DEFAULT_SINKS = [
|
|
|
10902
10902
|
{ method: "unmarshal", class: "Unmarshaller", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
10903
10903
|
{ method: "newTransformer", class: "TransformerFactory", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
10904
10904
|
{ method: "transform", class: "Transformer", type: "xxe", cwe: "CWE-611", severity: "high", arg_positions: [0] },
|
|
10905
|
-
{ method: "sendRedirect", class: "HttpServletResponse", type: "
|
|
10906
|
-
{ method: "sendRedirect", type: "
|
|
10905
|
+
{ method: "sendRedirect", class: "HttpServletResponse", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
|
|
10906
|
+
{ method: "sendRedirect", type: "open_redirect", cwe: "CWE-601", severity: "high", arg_positions: [0] },
|
|
10907
10907
|
{ method: "openConnection", class: "URL", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [] },
|
|
10908
10908
|
{ method: "openStream", class: "URL", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [] },
|
|
10909
10909
|
{ method: "execute", class: "HttpClient", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -13400,19 +13400,19 @@ function formatCallCode(call) {
|
|
|
13400
13400
|
// ../circle-ir/dist/analysis/findings.js
|
|
13401
13401
|
function canSourceReachSink(sourceType, sinkType) {
|
|
13402
13402
|
const sourceToSinkMapping = {
|
|
13403
|
-
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
|
|
13404
|
-
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
|
|
13405
|
-
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf"],
|
|
13406
|
-
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf"],
|
|
13407
|
-
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call"],
|
|
13408
|
-
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment"],
|
|
13403
|
+
http_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect"],
|
|
13404
|
+
http_body: ["sql_injection", "command_injection", "deserialization", "xxe", "xss", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect"],
|
|
13405
|
+
http_header: ["sql_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect"],
|
|
13406
|
+
http_cookie: ["sql_injection", "xss", "mybatis_mapper_call", "code_injection", "crlf", "open_redirect"],
|
|
13407
|
+
http_path: ["path_traversal", "sql_injection", "ssrf", "mybatis_mapper_call", "open_redirect"],
|
|
13408
|
+
http_query: ["sql_injection", "command_injection", "xss", "ssrf", "mybatis_mapper_call", "code_injection", "crlf", "mass_assignment", "open_redirect"],
|
|
13409
13409
|
io_input: ["command_injection", "path_traversal", "deserialization", "xxe", "code_injection", "xss", "ssrf"],
|
|
13410
13410
|
env_input: ["command_injection", "path_traversal"],
|
|
13411
13411
|
db_input: ["xss", "sql_injection"],
|
|
13412
13412
|
file_input: ["deserialization", "xxe", "path_traversal", "command_injection", "code_injection"],
|
|
13413
13413
|
network_input: ["sql_injection", "command_injection", "xss", "ssrf"],
|
|
13414
13414
|
config_param: ["sql_injection", "command_injection", "path_traversal", "xss", "ssrf"],
|
|
13415
|
-
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment"],
|
|
13415
|
+
interprocedural_param: ["sql_injection", "command_injection", "path_traversal", "xss", "xpath_injection", "ldap_injection", "ssrf", "code_injection", "mybatis_mapper_call", "crlf", "mass_assignment", "open_redirect"],
|
|
13416
13416
|
plugin_param: ["sql_injection", "command_injection", "path_traversal", "xss", "code_injection"]
|
|
13417
13417
|
};
|
|
13418
13418
|
const validSinks = sourceToSinkMapping[sourceType];
|
|
@@ -22607,6 +22607,9 @@ class LanguageSourcesPass {
|
|
|
22607
22607
|
for (const finding of findGoXssFindings(code, graph.ir.meta.file)) {
|
|
22608
22608
|
ctx.addFinding(finding);
|
|
22609
22609
|
}
|
|
22610
|
+
for (const finding of findGoLocationHeaderOpenRedirectFindings(code, graph.ir.meta.file)) {
|
|
22611
|
+
ctx.addFinding(finding);
|
|
22612
|
+
}
|
|
22610
22613
|
}
|
|
22611
22614
|
if (language === "python") {
|
|
22612
22615
|
additionalSanitizers.push(...findPythonNetlocAllowlistGuardSanitizers(code));
|
|
@@ -22625,6 +22628,9 @@ class LanguageSourcesPass {
|
|
|
22625
22628
|
for (const finding of findPythonJinjaMarkupXssFindings(code, graph.ir.meta.file)) {
|
|
22626
22629
|
ctx.addFinding(finding);
|
|
22627
22630
|
}
|
|
22631
|
+
for (const finding of findPythonHeadersSubscriptOpenRedirectFindings(code, graph.ir.meta.file)) {
|
|
22632
|
+
ctx.addFinding(finding);
|
|
22633
|
+
}
|
|
22628
22634
|
}
|
|
22629
22635
|
if (language === "rust") {
|
|
22630
22636
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
@@ -22646,6 +22652,9 @@ class LanguageSourcesPass {
|
|
|
22646
22652
|
for (const finding of findRustWeakCryptoEcbFindings(code, graph.ir.meta.file)) {
|
|
22647
22653
|
ctx.addFinding(finding);
|
|
22648
22654
|
}
|
|
22655
|
+
for (const finding of findRustAppendHeaderTupleOpenRedirectFindings(code, graph.ir.meta.file)) {
|
|
22656
|
+
ctx.addFinding(finding);
|
|
22657
|
+
}
|
|
22649
22658
|
}
|
|
22650
22659
|
if (language === "javascript" || language === "typescript" || language === "htmljs") {
|
|
22651
22660
|
additionalSanitizers.push(...findJsSafeJsonParseSanitizers(code));
|
|
@@ -22664,6 +22673,9 @@ class LanguageSourcesPass {
|
|
|
22664
22673
|
for (const finding of findTsAngularBypassXssFindings(code, graph.ir.meta.file)) {
|
|
22665
22674
|
ctx.addFinding(finding);
|
|
22666
22675
|
}
|
|
22676
|
+
for (const finding of findJsDomOpenRedirectFindings(code, graph.ir.meta.file)) {
|
|
22677
|
+
ctx.addFinding(finding);
|
|
22678
|
+
}
|
|
22667
22679
|
}
|
|
22668
22680
|
if (language === "java") {
|
|
22669
22681
|
additionalSanitizers.push(...findJavaSafeJsonParseSanitizers(code));
|
|
@@ -25907,6 +25919,289 @@ function findPythonJinjaMarkupXssFindings(code, file) {
|
|
|
25907
25919
|
}
|
|
25908
25920
|
return out2;
|
|
25909
25921
|
}
|
|
25922
|
+
function findGoLocationHeaderOpenRedirectFindings(code, file) {
|
|
25923
|
+
const out2 = [];
|
|
25924
|
+
const lines = code.split(`
|
|
25925
|
+
`);
|
|
25926
|
+
const rwNames = new Set;
|
|
25927
|
+
const sigRe = /\(\s*([A-Za-z_]\w*)\s+http\.ResponseWriter\b/g;
|
|
25928
|
+
for (const line of lines) {
|
|
25929
|
+
let m;
|
|
25930
|
+
sigRe.lastIndex = 0;
|
|
25931
|
+
while ((m = sigRe.exec(line)) !== null)
|
|
25932
|
+
rwNames.add(m[1]);
|
|
25933
|
+
}
|
|
25934
|
+
if (rwNames.size === 0)
|
|
25935
|
+
return out2;
|
|
25936
|
+
const safeWrapRe = /\b(?:net\/url|url)\.Parse\b[\s\S]{0,120}?\b(?:IsAbs|Host)\b|\bstrings\.HasPrefix\s*\(/;
|
|
25937
|
+
const callRe = /\b([A-Za-z_]\w*)\s*\.\s*Header\s*\(\s*\)\s*\.\s*(?:Set|Add)\s*\(\s*(['"])([^'"]+)\2\s*,\s*([\s\S]*)\)\s*(?:\/\/.*)?$/;
|
|
25938
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25939
|
+
const raw = lines[i2];
|
|
25940
|
+
const trimmed = raw.trim();
|
|
25941
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
25942
|
+
continue;
|
|
25943
|
+
const m = trimmed.match(callRe);
|
|
25944
|
+
if (!m)
|
|
25945
|
+
continue;
|
|
25946
|
+
const recv = m[1];
|
|
25947
|
+
if (!rwNames.has(recv))
|
|
25948
|
+
continue;
|
|
25949
|
+
const key = m[3];
|
|
25950
|
+
if (key.toLowerCase() !== "location")
|
|
25951
|
+
continue;
|
|
25952
|
+
const valExpr = m[4];
|
|
25953
|
+
const valTrim = valExpr.trim();
|
|
25954
|
+
if (/^"(?:\\.|[^"\\])*"$/.test(valTrim))
|
|
25955
|
+
continue;
|
|
25956
|
+
if (safeWrapRe.test(valExpr))
|
|
25957
|
+
continue;
|
|
25958
|
+
out2.push({
|
|
25959
|
+
id: `open_redirect-${file}-${i2 + 1}`,
|
|
25960
|
+
pass: "language-sources",
|
|
25961
|
+
category: "security",
|
|
25962
|
+
rule_id: "open_redirect",
|
|
25963
|
+
cwe: "CWE-601",
|
|
25964
|
+
severity: "high",
|
|
25965
|
+
level: "error",
|
|
25966
|
+
message: 'Open redirect: ResponseWriter.Header().Set("Location", ...) ' + "writes a user-controlled value into the Location header without " + "validating the target. Restrict to an allow-list of hosts/paths " + "or compare against a known-safe set before issuing the redirect.",
|
|
25967
|
+
file,
|
|
25968
|
+
line: i2 + 1,
|
|
25969
|
+
snippet: trimmed.substring(0, 100)
|
|
25970
|
+
});
|
|
25971
|
+
}
|
|
25972
|
+
return out2;
|
|
25973
|
+
}
|
|
25974
|
+
function findPythonHeadersSubscriptOpenRedirectFindings(code, file) {
|
|
25975
|
+
const out2 = [];
|
|
25976
|
+
const lines = code.split(`
|
|
25977
|
+
`);
|
|
25978
|
+
const requestSourceRe = /\b([A-Za-z_]\w*)\s*=\s*request\s*\.\s*(?:args|form|values|files|json|cookies|headers)(?:\s*\.\s*get\s*\(|\s*\[)/;
|
|
25979
|
+
const taintedVars = new Set;
|
|
25980
|
+
for (const line of lines) {
|
|
25981
|
+
const m = line.match(requestSourceRe);
|
|
25982
|
+
if (m)
|
|
25983
|
+
taintedVars.add(m[1]);
|
|
25984
|
+
}
|
|
25985
|
+
const safeWrapRe = /\b(?:urllib\.parse\.urlparse|urlparse)\s*\([\s\S]{0,120}?\bnetloc\b|\b(?:startswith)\s*\(/;
|
|
25986
|
+
const subscriptRe = /\b([A-Za-z_]\w*)\s*\.\s*headers\s*\[\s*(['"])([^'"]+)\2\s*\]\s*=\s*(.+)$/;
|
|
25987
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25988
|
+
const raw = lines[i2];
|
|
25989
|
+
const trimmed = raw.trim();
|
|
25990
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
25991
|
+
continue;
|
|
25992
|
+
const m = trimmed.match(subscriptRe);
|
|
25993
|
+
if (!m)
|
|
25994
|
+
continue;
|
|
25995
|
+
const key = m[3];
|
|
25996
|
+
if (key.toLowerCase() !== "location")
|
|
25997
|
+
continue;
|
|
25998
|
+
const rhs = m[4].replace(/\s*(?:#.*)?$/, "").trim();
|
|
25999
|
+
if (/^['"](?:\\.|[^'"\\])*['"]$/.test(rhs))
|
|
26000
|
+
continue;
|
|
26001
|
+
if (safeWrapRe.test(rhs))
|
|
26002
|
+
continue;
|
|
26003
|
+
const hasTaintedVar = [...taintedVars].some((v) => new RegExp(`\\b${v}\\b`).test(rhs));
|
|
26004
|
+
const inlineSource = /\brequest\s*\.\s*(?:args|form|values|files|cookies|headers|json)(?:\s*\.\s*get\s*\(|\s*\[)/.test(rhs);
|
|
26005
|
+
if (!hasTaintedVar && !inlineSource)
|
|
26006
|
+
continue;
|
|
26007
|
+
out2.push({
|
|
26008
|
+
id: `open_redirect-${file}-${i2 + 1}`,
|
|
26009
|
+
pass: "language-sources",
|
|
26010
|
+
category: "security",
|
|
26011
|
+
rule_id: "open_redirect",
|
|
26012
|
+
cwe: "CWE-601",
|
|
26013
|
+
severity: "high",
|
|
26014
|
+
level: "error",
|
|
26015
|
+
message: 'Open redirect: response.headers["Location"] = ... assigns a ' + "user-controlled value to the Location header. Validate the URL " + "against an allow-list of trusted hosts before sending.",
|
|
26016
|
+
file,
|
|
26017
|
+
line: i2 + 1,
|
|
26018
|
+
snippet: trimmed.substring(0, 100)
|
|
26019
|
+
});
|
|
26020
|
+
}
|
|
26021
|
+
return out2;
|
|
26022
|
+
}
|
|
26023
|
+
function findRustAppendHeaderTupleOpenRedirectFindings(code, file) {
|
|
26024
|
+
const out2 = [];
|
|
26025
|
+
const lines = code.split(`
|
|
26026
|
+
`);
|
|
26027
|
+
const extractorRe = /\b([A-Za-z_]\w*)\s*:\s*(?:web\s*::\s*)?(?:Query|Path|Form|Json)\s*<|\b([A-Za-z_]\w*)\s*:\s*&?\s*HttpRequest\b/g;
|
|
26028
|
+
const extractorParams = new Set;
|
|
26029
|
+
for (const line of lines) {
|
|
26030
|
+
let m;
|
|
26031
|
+
extractorRe.lastIndex = 0;
|
|
26032
|
+
while ((m = extractorRe.exec(line)) !== null) {
|
|
26033
|
+
const name2 = m[1] ?? m[2];
|
|
26034
|
+
if (name2)
|
|
26035
|
+
extractorParams.add(name2);
|
|
26036
|
+
}
|
|
26037
|
+
}
|
|
26038
|
+
if (extractorParams.size === 0)
|
|
26039
|
+
return out2;
|
|
26040
|
+
const taintedVars = new Set(extractorParams);
|
|
26041
|
+
const letRe = /\blet\s+(?:mut\s+)?([A-Za-z_]\w*)\s*(?::[^=]+)?=\s*([^;]+);/g;
|
|
26042
|
+
for (let pass = 0;pass < 4; pass++) {
|
|
26043
|
+
const before = taintedVars.size;
|
|
26044
|
+
let lm;
|
|
26045
|
+
letRe.lastIndex = 0;
|
|
26046
|
+
while ((lm = letRe.exec(code)) !== null) {
|
|
26047
|
+
const name2 = lm[1];
|
|
26048
|
+
const rhs = lm[2];
|
|
26049
|
+
if (taintedVars.has(name2))
|
|
26050
|
+
continue;
|
|
26051
|
+
for (const tv of taintedVars) {
|
|
26052
|
+
if (new RegExp(`\\b${tv}\\b`).test(rhs)) {
|
|
26053
|
+
taintedVars.add(name2);
|
|
26054
|
+
break;
|
|
26055
|
+
}
|
|
26056
|
+
}
|
|
26057
|
+
}
|
|
26058
|
+
if (taintedVars.size === before)
|
|
26059
|
+
break;
|
|
26060
|
+
}
|
|
26061
|
+
const safeWrapRe = /\b(?:Url\s*::\s*parse|url\s*::\s*Url\s*::\s*parse)\s*\([\s\S]{0,160}?\b(?:host_str|host|origin)\b/;
|
|
26062
|
+
const tupleCallRe = /\.\s*(?:append_header|insert_header)\s*\(\s*\(\s*(['"])([^'"]+)\1\s*,\s*([^)]*?)\s*\)\s*\)/g;
|
|
26063
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26064
|
+
const raw = lines[i2];
|
|
26065
|
+
const trimmed = raw.trim();
|
|
26066
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
26067
|
+
continue;
|
|
26068
|
+
let cm;
|
|
26069
|
+
tupleCallRe.lastIndex = 0;
|
|
26070
|
+
while ((cm = tupleCallRe.exec(raw)) !== null) {
|
|
26071
|
+
const key = cm[2];
|
|
26072
|
+
if (key.toLowerCase() !== "location")
|
|
26073
|
+
continue;
|
|
26074
|
+
const valExpr = cm[3];
|
|
26075
|
+
if (/^['"](?:\\.|[^'"\\])*['"]$/.test(valExpr.trim()))
|
|
26076
|
+
continue;
|
|
26077
|
+
if (safeWrapRe.test(valExpr))
|
|
26078
|
+
continue;
|
|
26079
|
+
const hasTainted = [...taintedVars].some((v) => new RegExp(`\\b${v}\\b`).test(valExpr));
|
|
26080
|
+
if (!hasTainted)
|
|
26081
|
+
continue;
|
|
26082
|
+
out2.push({
|
|
26083
|
+
id: `open_redirect-${file}-${i2 + 1}`,
|
|
26084
|
+
pass: "language-sources",
|
|
26085
|
+
category: "security",
|
|
26086
|
+
rule_id: "open_redirect",
|
|
26087
|
+
cwe: "CWE-601",
|
|
26088
|
+
severity: "high",
|
|
26089
|
+
level: "error",
|
|
26090
|
+
message: "Open redirect: HttpResponse builder " + '.append_header(("Location", ...)) sets the Location header from ' + "a request-derived value without allow-list validation. Parse " + "with url::Url::parse and compare host_str against an allow-list.",
|
|
26091
|
+
file,
|
|
26092
|
+
line: i2 + 1,
|
|
26093
|
+
snippet: trimmed.substring(0, 100)
|
|
26094
|
+
});
|
|
26095
|
+
break;
|
|
26096
|
+
}
|
|
26097
|
+
}
|
|
26098
|
+
return out2;
|
|
26099
|
+
}
|
|
26100
|
+
function findJsDomOpenRedirectFindings(code, file) {
|
|
26101
|
+
const out2 = [];
|
|
26102
|
+
const lines = code.split(`
|
|
26103
|
+
`);
|
|
26104
|
+
const domSourceRe = /\blocation\s*\.\s*(?:search|hash|href|pathname)\b|\bwindow\s*\.\s*location\s*\.\s*(?:search|hash|href|pathname)\b|\bURLSearchParams\b|\bdocument\s*\.\s*(?:referrer|URL|location\s*\.\s*(?:search|hash|href|pathname))\b|\bwindow\s*\.\s*name\b/;
|
|
26105
|
+
const taintedVars = new Set;
|
|
26106
|
+
const assignRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g;
|
|
26107
|
+
let am;
|
|
26108
|
+
assignRe.lastIndex = 0;
|
|
26109
|
+
while ((am = assignRe.exec(code)) !== null) {
|
|
26110
|
+
if (domSourceRe.test(am[2]))
|
|
26111
|
+
taintedVars.add(am[1]);
|
|
26112
|
+
}
|
|
26113
|
+
for (let pass = 0;pass < 4; pass++) {
|
|
26114
|
+
const before = taintedVars.size;
|
|
26115
|
+
assignRe.lastIndex = 0;
|
|
26116
|
+
while ((am = assignRe.exec(code)) !== null) {
|
|
26117
|
+
if (taintedVars.has(am[1]))
|
|
26118
|
+
continue;
|
|
26119
|
+
for (const tv of taintedVars) {
|
|
26120
|
+
if (new RegExp(`\\b${tv}\\b`).test(am[2])) {
|
|
26121
|
+
taintedVars.add(am[1]);
|
|
26122
|
+
break;
|
|
26123
|
+
}
|
|
26124
|
+
}
|
|
26125
|
+
}
|
|
26126
|
+
if (taintedVars.size === before)
|
|
26127
|
+
break;
|
|
26128
|
+
}
|
|
26129
|
+
const safeWrapRe = /\b(?:startsWith|includes)\s*\(\s*['"`]\/[^'"`]*['"`]\s*\)|\bnew\s+URL\s*\([\s\S]{0,80}?\)\s*\.\s*(?:origin|hostname)\s*===/;
|
|
26130
|
+
const containsTaint = (expr) => {
|
|
26131
|
+
if (domSourceRe.test(expr))
|
|
26132
|
+
return true;
|
|
26133
|
+
for (const tv of taintedVars) {
|
|
26134
|
+
if (new RegExp(`\\b${tv}\\b`).test(expr))
|
|
26135
|
+
return true;
|
|
26136
|
+
}
|
|
26137
|
+
return false;
|
|
26138
|
+
};
|
|
26139
|
+
const hrefSinkRe = /\b(?:(?:window|document|self|top|parent)\s*\.\s*)?location\s*\.\s*href\s*=\s*([^;\n]+?)\s*;?\s*(?:\/\/.*)?$/;
|
|
26140
|
+
const locSinkRe = /\b(?:window|document|self|top|parent)\s*\.\s*location\s*=\s*([^;\n]+?)\s*;?\s*(?:\/\/.*)?$/;
|
|
26141
|
+
const contentSinkRe = /\.\s*content\s*=\s*([^\n]+?)\s*$/;
|
|
26142
|
+
const callSinkRe = /\b(?:(?:window|document|self|top|parent)\s*\.\s*)?location\s*\.\s*(?:assign|replace)\s*\(\s*([^)]+)\)/;
|
|
26143
|
+
const emit = (line, msg, snippet) => {
|
|
26144
|
+
out2.push({
|
|
26145
|
+
id: `open_redirect-${file}-${line}`,
|
|
26146
|
+
pass: "language-sources",
|
|
26147
|
+
category: "security",
|
|
26148
|
+
rule_id: "open_redirect",
|
|
26149
|
+
cwe: "CWE-601",
|
|
26150
|
+
severity: "high",
|
|
26151
|
+
level: "error",
|
|
26152
|
+
message: msg,
|
|
26153
|
+
file,
|
|
26154
|
+
line,
|
|
26155
|
+
snippet: snippet.substring(0, 100)
|
|
26156
|
+
});
|
|
26157
|
+
};
|
|
26158
|
+
const isLiteralOnly = (expr) => /^['"`](?:\\.|[^'"`\\])*['"`]$/.test(expr.trim());
|
|
26159
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26160
|
+
const raw = lines[i2];
|
|
26161
|
+
const trimmed = raw.trim();
|
|
26162
|
+
if (!trimmed || trimmed.startsWith("//"))
|
|
26163
|
+
continue;
|
|
26164
|
+
let matched = false;
|
|
26165
|
+
const hm = trimmed.match(hrefSinkRe);
|
|
26166
|
+
if (hm) {
|
|
26167
|
+
const rhs = hm[1].trim();
|
|
26168
|
+
if (!isLiteralOnly(rhs) && containsTaint(rhs) && !safeWrapRe.test(rhs)) {
|
|
26169
|
+
emit(i2 + 1, "Open redirect: assignment to location.href uses a value " + "derived from a URL query/hash without allow-list validation. " + "Compare the target origin to a known-safe set before navigating.", trimmed);
|
|
26170
|
+
matched = true;
|
|
26171
|
+
}
|
|
26172
|
+
}
|
|
26173
|
+
if (!matched) {
|
|
26174
|
+
const lm = trimmed.match(locSinkRe);
|
|
26175
|
+
if (lm) {
|
|
26176
|
+
const rhs = lm[1].trim();
|
|
26177
|
+
if (!isLiteralOnly(rhs) && containsTaint(rhs) && !safeWrapRe.test(rhs)) {
|
|
26178
|
+
emit(i2 + 1, "Open redirect: assignment to window.location uses a value " + "derived from a URL query/hash without allow-list validation. " + "Compare the target origin to a known-safe set before navigating.", trimmed);
|
|
26179
|
+
matched = true;
|
|
26180
|
+
}
|
|
26181
|
+
}
|
|
26182
|
+
}
|
|
26183
|
+
if (!matched) {
|
|
26184
|
+
const cm = trimmed.match(contentSinkRe);
|
|
26185
|
+
if (cm) {
|
|
26186
|
+
const rhs = cm[1].replace(/\s*\/\/.*$/, "").replace(/\s*;\s*$/, "").trim();
|
|
26187
|
+
if (!isLiteralOnly(rhs) && /['"`][^'"`]*\burl\s*=/i.test(rhs) && containsTaint(rhs) && !safeWrapRe.test(rhs)) {
|
|
26188
|
+
emit(i2 + 1, "Open redirect: DOM assignment to <meta>.content with a " + "meta-refresh URL built from a URL query/hash without " + "allow-list validation. Validate origin before navigating.", trimmed);
|
|
26189
|
+
matched = true;
|
|
26190
|
+
}
|
|
26191
|
+
}
|
|
26192
|
+
}
|
|
26193
|
+
if (!matched) {
|
|
26194
|
+
const callm = trimmed.match(callSinkRe);
|
|
26195
|
+
if (callm) {
|
|
26196
|
+
const arg = callm[1].trim();
|
|
26197
|
+
if (!isLiteralOnly(arg) && containsTaint(arg) && !safeWrapRe.test(arg)) {
|
|
26198
|
+
emit(i2 + 1, "Open redirect: location.assign / location.replace invoked " + "with a value derived from a URL query/hash without allow-" + "list validation. Validate origin before navigating.", trimmed);
|
|
26199
|
+
}
|
|
26200
|
+
}
|
|
26201
|
+
}
|
|
26202
|
+
}
|
|
26203
|
+
return out2;
|
|
26204
|
+
}
|
|
25910
26205
|
|
|
25911
26206
|
// ../circle-ir/dist/analysis/passes/sink-filter-pass.js
|
|
25912
26207
|
var JS_XSS_SANITIZERS = [
|
|
@@ -27371,13 +27666,13 @@ class TaintPropagationPass {
|
|
|
27371
27666
|
}));
|
|
27372
27667
|
const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines, types) ?? [];
|
|
27373
27668
|
for (const f of arrayFlows) {
|
|
27374
|
-
if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
|
|
27669
|
+
if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type)) {
|
|
27375
27670
|
flows.push(f);
|
|
27376
27671
|
}
|
|
27377
27672
|
}
|
|
27378
27673
|
const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
|
|
27379
27674
|
for (const f of collectionFlows) {
|
|
27380
|
-
if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line))
|
|
27675
|
+
if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type))
|
|
27381
27676
|
continue;
|
|
27382
27677
|
const flowForCheck = {
|
|
27383
27678
|
source: { line: f.source_line },
|
|
@@ -27399,7 +27694,7 @@ class TaintPropagationPass {
|
|
|
27399
27694
|
}
|
|
27400
27695
|
const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
|
|
27401
27696
|
for (const f of paramFlows) {
|
|
27402
|
-
if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) {
|
|
27697
|
+
if (!flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type)) {
|
|
27403
27698
|
flows.push(f);
|
|
27404
27699
|
}
|
|
27405
27700
|
}
|
|
@@ -27837,7 +28132,7 @@ function detectParameterSinkFlows(types, calls, sources, sinks, unreachableLines
|
|
|
27837
28132
|
}
|
|
27838
28133
|
const paramSource = methodParamSources.get(arg.variable);
|
|
27839
28134
|
if (paramSource) {
|
|
27840
|
-
const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line);
|
|
28135
|
+
const exists = flows.some((f) => f.source_line === paramSource.line && f.sink_line === sink.line && f.sink_type === sink.type);
|
|
27841
28136
|
if (!exists) {
|
|
27842
28137
|
if (typeof code === "string" && isReassignedToLiteralBetween(code, arg.variable, paramSource.line, sink.line)) {
|
|
27843
28138
|
continue;
|
|
@@ -39034,7 +39329,7 @@ var colors = {
|
|
|
39034
39329
|
};
|
|
39035
39330
|
|
|
39036
39331
|
// src/version.ts
|
|
39037
|
-
var version = "3.
|
|
39332
|
+
var version = "3.132.0";
|
|
39038
39333
|
|
|
39039
39334
|
// src/formatters.ts
|
|
39040
39335
|
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.
|
|
3
|
+
"version": "3.132.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.
|
|
69
|
+
"circle-ir": "^3.132.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|