cognium-dev 3.69.0 → 3.71.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 +394 -2
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -21228,6 +21228,13 @@ function findOopFieldReadSources(types, sourceCode, language) {
|
|
|
21228
21228
|
let returnedField = null;
|
|
21229
21229
|
let returnStatementCount = 0;
|
|
21230
21230
|
const returnRe = new RegExp(`\\breturn\\s+${SELF}\\.([A-Za-z_]\\w*)\\s*[;}]?`);
|
|
21231
|
+
const guardRePy = /\bif\s+[\w.]+\s+(?:not\s+)?in\s+(?:self\.)?[A-Z_][A-Z0-9_]*\s*:/;
|
|
21232
|
+
const guardThrowRePy = /^\s*(?:raise\b|abort\b|return\s+(?:None\b|''|""|\)?$))/;
|
|
21233
|
+
const guardReJv = /\bif\s*\(\s*!\s*(?:this\.)?[A-Z_][A-Z0-9_]*\s*\.\s*(?:contains|includes|has|matches)\s*\(/;
|
|
21234
|
+
const guardThrowReJv = /^\s*(?:throw\b|return\s+null\b)/;
|
|
21235
|
+
const guardRe = isPython ? guardRePy : guardReJv;
|
|
21236
|
+
const guardThrowRe = isPython ? guardThrowRePy : guardThrowReJv;
|
|
21237
|
+
let hasAllowlistGuard = false;
|
|
21231
21238
|
for (let i2 = mStart - 1;i2 < Math.min(mEnd, lines.length); i2++) {
|
|
21232
21239
|
const raw = lines[i2] ?? "";
|
|
21233
21240
|
const trimmed = raw.trim();
|
|
@@ -21243,8 +21250,17 @@ function findOopFieldReadSources(types, sourceCode, language) {
|
|
|
21243
21250
|
returnStatementCount = 99;
|
|
21244
21251
|
break;
|
|
21245
21252
|
}
|
|
21253
|
+
if (guardRe.test(trimmed)) {
|
|
21254
|
+
for (let j = i2 + 1;j < Math.min(i2 + 4, mEnd, lines.length); j++) {
|
|
21255
|
+
const next = lines[j] ?? "";
|
|
21256
|
+
if (guardThrowRe.test(next)) {
|
|
21257
|
+
hasAllowlistGuard = true;
|
|
21258
|
+
break;
|
|
21259
|
+
}
|
|
21260
|
+
}
|
|
21261
|
+
}
|
|
21246
21262
|
}
|
|
21247
|
-
if (returnStatementCount === 1 && returnedField && fieldTaint.has(returnedField)) {
|
|
21263
|
+
if (returnStatementCount === 1 && returnedField && fieldTaint.has(returnedField) && !hasAllowlistGuard) {
|
|
21248
21264
|
const fieldInfo = fieldTaint.get(returnedField);
|
|
21249
21265
|
const getterVar = isPython ? `${SELF}.${m.name}` : m.name;
|
|
21250
21266
|
sources.push({
|
|
@@ -22835,6 +22851,23 @@ class TaintPropagationPass {
|
|
|
22835
22851
|
return false;
|
|
22836
22852
|
});
|
|
22837
22853
|
}
|
|
22854
|
+
if (typeof ctx.code === "string") {
|
|
22855
|
+
const sinkByLine = new Map;
|
|
22856
|
+
for (const s of sinks) {
|
|
22857
|
+
if (s.type === "nosql_injection")
|
|
22858
|
+
sinkByLine.set(s.line, s);
|
|
22859
|
+
}
|
|
22860
|
+
if (sinkByLine.size > 0) {
|
|
22861
|
+
finalFlows = finalFlows.filter((f) => {
|
|
22862
|
+
if (f.sink_type !== "nosql_injection")
|
|
22863
|
+
return true;
|
|
22864
|
+
const sink = sinkByLine.get(f.sink_line);
|
|
22865
|
+
if (!sink || !sink.code || !sink.method)
|
|
22866
|
+
return true;
|
|
22867
|
+
return !isMongoValueBoundFilter(sink.code, sink.method);
|
|
22868
|
+
});
|
|
22869
|
+
}
|
|
22870
|
+
}
|
|
22838
22871
|
if (finalFlows.length > 1) {
|
|
22839
22872
|
const bestByKey = new Map;
|
|
22840
22873
|
for (const f of finalFlows) {
|
|
@@ -22852,6 +22885,76 @@ class TaintPropagationPass {
|
|
|
22852
22885
|
return { flows: finalFlows };
|
|
22853
22886
|
}
|
|
22854
22887
|
}
|
|
22888
|
+
function isMongoValueBoundFilter(sinkCode, sinkMethod) {
|
|
22889
|
+
if (!sinkCode || !sinkMethod)
|
|
22890
|
+
return false;
|
|
22891
|
+
const callIdx = sinkCode.indexOf(`${sinkMethod}(`);
|
|
22892
|
+
if (callIdx < 0)
|
|
22893
|
+
return false;
|
|
22894
|
+
const openIdx = callIdx + sinkMethod.length;
|
|
22895
|
+
let depth = 0;
|
|
22896
|
+
let braceDepth = 0;
|
|
22897
|
+
let bracketDepth = 0;
|
|
22898
|
+
let inString = null;
|
|
22899
|
+
let firstArgEnd = -1;
|
|
22900
|
+
let firstArgComma = -1;
|
|
22901
|
+
const limit = Math.min(sinkCode.length, openIdx + 4096);
|
|
22902
|
+
for (let i2 = openIdx;i2 < limit; i2++) {
|
|
22903
|
+
const ch = sinkCode[i2];
|
|
22904
|
+
if (inString) {
|
|
22905
|
+
if (ch === "\\" && i2 + 1 < limit) {
|
|
22906
|
+
i2++;
|
|
22907
|
+
continue;
|
|
22908
|
+
}
|
|
22909
|
+
if (ch === inString)
|
|
22910
|
+
inString = null;
|
|
22911
|
+
continue;
|
|
22912
|
+
}
|
|
22913
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
22914
|
+
inString = ch;
|
|
22915
|
+
continue;
|
|
22916
|
+
}
|
|
22917
|
+
if (ch === "(")
|
|
22918
|
+
depth++;
|
|
22919
|
+
else if (ch === ")") {
|
|
22920
|
+
depth--;
|
|
22921
|
+
if (depth === 0) {
|
|
22922
|
+
firstArgEnd = i2;
|
|
22923
|
+
break;
|
|
22924
|
+
}
|
|
22925
|
+
} else if (ch === "{")
|
|
22926
|
+
braceDepth++;
|
|
22927
|
+
else if (ch === "}")
|
|
22928
|
+
braceDepth--;
|
|
22929
|
+
else if (ch === "[")
|
|
22930
|
+
bracketDepth++;
|
|
22931
|
+
else if (ch === "]")
|
|
22932
|
+
bracketDepth--;
|
|
22933
|
+
else if (ch === "," && depth === 1 && braceDepth === 0 && bracketDepth === 0) {
|
|
22934
|
+
if (firstArgComma < 0)
|
|
22935
|
+
firstArgComma = i2;
|
|
22936
|
+
}
|
|
22937
|
+
}
|
|
22938
|
+
if (firstArgEnd < 0)
|
|
22939
|
+
return false;
|
|
22940
|
+
const argEnd = firstArgComma >= 0 ? firstArgComma : firstArgEnd;
|
|
22941
|
+
const firstArg = sinkCode.slice(openIdx + 1, argEnd).trim();
|
|
22942
|
+
if (!firstArg)
|
|
22943
|
+
return false;
|
|
22944
|
+
if (firstArg[0] !== "{" || firstArg[firstArg.length - 1] !== "}")
|
|
22945
|
+
return false;
|
|
22946
|
+
const body2 = firstArg.slice(1, -1).trim();
|
|
22947
|
+
if (!body2)
|
|
22948
|
+
return false;
|
|
22949
|
+
const stripped = body2.replace(/(['"`])(?:\\.|(?!\1).)*\1/g, '""');
|
|
22950
|
+
if (/\.\.\./.test(stripped))
|
|
22951
|
+
return false;
|
|
22952
|
+
if (/(^|[,{\s])\$[A-Za-z_]\w*\s*:/.test(stripped))
|
|
22953
|
+
return false;
|
|
22954
|
+
if (/(['"])\$[A-Za-z_]\w*\1\s*:/.test(body2))
|
|
22955
|
+
return false;
|
|
22956
|
+
return true;
|
|
22957
|
+
}
|
|
22855
22958
|
function isInJavaSanitizedMethod(code, types, sinkLine, sinkType) {
|
|
22856
22959
|
if (!types || types.length === 0)
|
|
22857
22960
|
return false;
|
|
@@ -29814,6 +29917,293 @@ class ModuleSideEffectPass {
|
|
|
29814
29917
|
}
|
|
29815
29918
|
}
|
|
29816
29919
|
|
|
29920
|
+
// ../circle-ir/dist/analysis/passes/cache-no-vary-pass.js
|
|
29921
|
+
function isSharedCacheable(value) {
|
|
29922
|
+
const v = value.toLowerCase();
|
|
29923
|
+
if (/\b(private|no-store|no-cache)\b/.test(v))
|
|
29924
|
+
return false;
|
|
29925
|
+
const pub = /\bpublic\b/.test(v);
|
|
29926
|
+
const maxMatch = /\b(?:s-maxage|max-age)\s*=\s*(\d+)/.exec(v);
|
|
29927
|
+
const positiveMax = maxMatch ? Number(maxMatch[1]) > 0 : false;
|
|
29928
|
+
return pub || positiveMax;
|
|
29929
|
+
}
|
|
29930
|
+
function isVaryCovering(value) {
|
|
29931
|
+
const v = value.toLowerCase();
|
|
29932
|
+
return /\b(cookie|authorization|\*)\b/.test(v);
|
|
29933
|
+
}
|
|
29934
|
+
var JS_AUTH_SIGNAL_RE = /\b(?:req|request)\s*\.\s*(?:cookies|session|user(?:Id|Name)?)\b|\b(?:req|request)\s*\.\s*headers\s*\.\s*(?:cookie|authorization)\b|\bres(?:ponse)?\s*\.\s*cookie\s*\(/i;
|
|
29935
|
+
var PY_AUTH_SIGNAL_RE = /\brequest\s*\.\s*cookies\b|\brequest\s*\.\s*headers\s*\.\s*get\s*\(\s*['"]Authorization['"]|\brequest\s*\.\s*authorization\b|\bsession\s*\[|\b(?:g\.user|current_user)\b|\bset_cookie\s*\(/i;
|
|
29936
|
+
var GO_AUTH_SIGNAL_RE = /\br\s*\.\s*Cookie\s*\(|\br\s*\.\s*Header\s*(?:\(\)|\.)\s*\.?\s*Get\s*\(\s*"(?:Cookie|Authorization)"|\br\s*\.\s*BasicAuth\s*\(|\bhttp\s*\.\s*SetCookie\s*\(|\bc\s*\.\s*(?:GetHeader|Cookie|SetCookie)\s*\(/;
|
|
29937
|
+
var JAVA_AUTH_SIGNAL_RE = /@CookieValue\b|@RequestHeader\s*\(\s*"(?:Cookie|Authorization)"|\brequest\s*\.\s*getCookies\s*\(|\brequest\s*\.\s*getHeader\s*\(\s*"(?:Cookie|Authorization)"|\bresponse\s*\.\s*addCookie\s*\(|\bSecurityContextHolder\b|\bPrincipal\s+\w+|\bAuthentication\s+\w+/;
|
|
29938
|
+
var PY_CACHE_HEADER_ASSIGN_RE = /\w+(?:\s*\.\s*\w+)*\s*\.\s*headers\s*\[\s*['"]Cache-Control['"]\s*\]\s*=\s*(['"])([^'"]*)\1/i;
|
|
29939
|
+
var PY_VARY_HEADER_ASSIGN_RE = /\w+(?:\s*\.\s*\w+)*\s*\.\s*headers\s*\[\s*['"]Vary['"]\s*\]\s*=\s*(['"])([^'"]*)\1/i;
|
|
29940
|
+
var PY_VARY_DECORATOR_RE = /^\s*@\s*(?:vary_on_cookie|vary_on_headers)\b/;
|
|
29941
|
+
var PY_CACHE_CONTROL_DECORATOR_RE = /^\s*@\s*cache_control\s*\(([^)]*)\)/;
|
|
29942
|
+
var JS_HEADER_METHODS = new Set(["setHeader", "set", "header"]);
|
|
29943
|
+
var GO_HEADER_METHODS = new Set(["Set", "Add"]);
|
|
29944
|
+
var JAVA_HEADER_METHODS = new Set(["setHeader", "addHeader"]);
|
|
29945
|
+
var JS_RES_RECEIVERS = new Set(["res", "response", "ctx"]);
|
|
29946
|
+
function classifyCall(call, language) {
|
|
29947
|
+
const method = call.method_name;
|
|
29948
|
+
const receiver = (call.receiver ?? "").trim();
|
|
29949
|
+
const arg0 = call.arguments[0]?.literal ?? null;
|
|
29950
|
+
const arg1 = call.arguments[1]?.literal ?? null;
|
|
29951
|
+
if (language === "javascript" || language === "typescript") {
|
|
29952
|
+
if (JS_RES_RECEIVERS.has(receiver) && JS_HEADER_METHODS.has(method)) {
|
|
29953
|
+
const header = (arg0 ?? "").toLowerCase();
|
|
29954
|
+
if (header === "cache-control" && arg1 && isSharedCacheable(arg1)) {
|
|
29955
|
+
return { kind: "cache-public", value: arg1 };
|
|
29956
|
+
}
|
|
29957
|
+
if (header === "vary" && arg1 && isVaryCovering(arg1)) {
|
|
29958
|
+
return { kind: "vary" };
|
|
29959
|
+
}
|
|
29960
|
+
}
|
|
29961
|
+
if (JS_RES_RECEIVERS.has(receiver) && method === "vary") {
|
|
29962
|
+
const v = arg0 ?? "";
|
|
29963
|
+
if (isVaryCovering(v) || v === "")
|
|
29964
|
+
return { kind: "vary" };
|
|
29965
|
+
}
|
|
29966
|
+
if (JS_RES_RECEIVERS.has(receiver) && method === "cookie") {
|
|
29967
|
+
return { kind: "auth" };
|
|
29968
|
+
}
|
|
29969
|
+
return null;
|
|
29970
|
+
}
|
|
29971
|
+
if (language === "python") {
|
|
29972
|
+
if (receiver === "request.cookies" || receiver === "request.session") {
|
|
29973
|
+
return { kind: "auth" };
|
|
29974
|
+
}
|
|
29975
|
+
if (receiver === "request.headers" && method === "get") {
|
|
29976
|
+
const v = (arg0 ?? "").toLowerCase();
|
|
29977
|
+
if (v === "authorization" || v === "cookie")
|
|
29978
|
+
return { kind: "auth" };
|
|
29979
|
+
}
|
|
29980
|
+
if ((receiver === "response" || receiver === "resp") && method === "set_cookie") {
|
|
29981
|
+
return { kind: "auth" };
|
|
29982
|
+
}
|
|
29983
|
+
if (method === "patch_vary_headers")
|
|
29984
|
+
return { kind: "vary" };
|
|
29985
|
+
if (method === "patch_cache_control") {
|
|
29986
|
+
const argTxt = call.arguments.map((a) => a.expression ?? "").join(",");
|
|
29987
|
+
if (/\bpublic\s*=\s*True\b/.test(argTxt)) {
|
|
29988
|
+
return { kind: "cache-public", value: argTxt };
|
|
29989
|
+
}
|
|
29990
|
+
}
|
|
29991
|
+
return null;
|
|
29992
|
+
}
|
|
29993
|
+
if (language === "go") {
|
|
29994
|
+
if ((receiver === "w.Header()" || receiver === "rw.Header()") && GO_HEADER_METHODS.has(method)) {
|
|
29995
|
+
const header = (arg0 ?? "").toLowerCase();
|
|
29996
|
+
if (header === "cache-control" && arg1 && isSharedCacheable(arg1)) {
|
|
29997
|
+
return { kind: "cache-public", value: arg1 };
|
|
29998
|
+
}
|
|
29999
|
+
if (header === "vary" && arg1 && isVaryCovering(arg1)) {
|
|
30000
|
+
return { kind: "vary" };
|
|
30001
|
+
}
|
|
30002
|
+
}
|
|
30003
|
+
if (receiver === "c" && method === "Header") {
|
|
30004
|
+
const header = (arg0 ?? "").toLowerCase();
|
|
30005
|
+
if (header === "cache-control" && arg1 && isSharedCacheable(arg1)) {
|
|
30006
|
+
return { kind: "cache-public", value: arg1 };
|
|
30007
|
+
}
|
|
30008
|
+
if (header === "vary" && arg1 && isVaryCovering(arg1)) {
|
|
30009
|
+
return { kind: "vary" };
|
|
30010
|
+
}
|
|
30011
|
+
}
|
|
30012
|
+
if (receiver === "r" && (method === "Cookie" || method === "BasicAuth")) {
|
|
30013
|
+
return { kind: "auth" };
|
|
30014
|
+
}
|
|
30015
|
+
if ((receiver === "r.Header" || receiver === "r.Header()") && method === "Get") {
|
|
30016
|
+
const v = (arg0 ?? "").toLowerCase();
|
|
30017
|
+
if (v === "cookie" || v === "authorization")
|
|
30018
|
+
return { kind: "auth" };
|
|
30019
|
+
}
|
|
30020
|
+
if (receiver === "http" && method === "SetCookie")
|
|
30021
|
+
return { kind: "auth" };
|
|
30022
|
+
if (receiver === "c" && (method === "Cookie" || method === "GetHeader" || method === "SetCookie")) {
|
|
30023
|
+
return { kind: "auth" };
|
|
30024
|
+
}
|
|
30025
|
+
return null;
|
|
30026
|
+
}
|
|
30027
|
+
if (language === "java") {
|
|
30028
|
+
if ((receiver === "response" || receiver === "resp") && JAVA_HEADER_METHODS.has(method)) {
|
|
30029
|
+
const header = (arg0 ?? "").toLowerCase();
|
|
30030
|
+
if (header === "cache-control" && arg1 && isSharedCacheable(arg1)) {
|
|
30031
|
+
return { kind: "cache-public", value: arg1 };
|
|
30032
|
+
}
|
|
30033
|
+
if (header === "vary" && arg1 && isVaryCovering(arg1)) {
|
|
30034
|
+
return { kind: "vary" };
|
|
30035
|
+
}
|
|
30036
|
+
}
|
|
30037
|
+
if ((receiver === "headers" || receiver === "httpHeaders") && method === "setCacheControl") {
|
|
30038
|
+
return { kind: "cache-public", value: "HttpHeaders.setCacheControl(...)" };
|
|
30039
|
+
}
|
|
30040
|
+
if ((receiver === "headers" || receiver === "httpHeaders") && method === "setVary") {
|
|
30041
|
+
return { kind: "vary" };
|
|
30042
|
+
}
|
|
30043
|
+
if (receiver === "request" && method === "getCookies") {
|
|
30044
|
+
return { kind: "auth" };
|
|
30045
|
+
}
|
|
30046
|
+
if (receiver === "request" && method === "getHeader") {
|
|
30047
|
+
const v = (arg0 ?? "").toLowerCase();
|
|
30048
|
+
if (v === "cookie" || v === "authorization")
|
|
30049
|
+
return { kind: "auth" };
|
|
30050
|
+
}
|
|
30051
|
+
if ((receiver === "response" || receiver === "resp") && method === "addCookie") {
|
|
30052
|
+
return { kind: "auth" };
|
|
30053
|
+
}
|
|
30054
|
+
return null;
|
|
30055
|
+
}
|
|
30056
|
+
return null;
|
|
30057
|
+
}
|
|
30058
|
+
function authSignalRegex(language) {
|
|
30059
|
+
switch (language) {
|
|
30060
|
+
case "javascript":
|
|
30061
|
+
case "typescript":
|
|
30062
|
+
return JS_AUTH_SIGNAL_RE;
|
|
30063
|
+
case "python":
|
|
30064
|
+
return PY_AUTH_SIGNAL_RE;
|
|
30065
|
+
case "go":
|
|
30066
|
+
return GO_AUTH_SIGNAL_RE;
|
|
30067
|
+
case "java":
|
|
30068
|
+
return JAVA_AUTH_SIGNAL_RE;
|
|
30069
|
+
default:
|
|
30070
|
+
return null;
|
|
30071
|
+
}
|
|
30072
|
+
}
|
|
30073
|
+
function scanWindow(code, language, startLine, endLine) {
|
|
30074
|
+
const lines = code.split(`
|
|
30075
|
+
`);
|
|
30076
|
+
const lo = Math.max(0, startLine - 1);
|
|
30077
|
+
const hi = Math.min(lines.length, endLine);
|
|
30078
|
+
const out2 = { vary: false, auth: false };
|
|
30079
|
+
const authRe = authSignalRegex(language);
|
|
30080
|
+
for (let i2 = lo;i2 < hi; i2++) {
|
|
30081
|
+
const ln = lines[i2];
|
|
30082
|
+
if (authRe && authRe.test(ln))
|
|
30083
|
+
out2.auth = true;
|
|
30084
|
+
if (language === "python") {
|
|
30085
|
+
if (!out2.cachePublic) {
|
|
30086
|
+
const mc = PY_CACHE_HEADER_ASSIGN_RE.exec(ln);
|
|
30087
|
+
if (mc && isSharedCacheable(mc[2])) {
|
|
30088
|
+
out2.cachePublic = { line: i2 + 1, value: mc[2] };
|
|
30089
|
+
}
|
|
30090
|
+
}
|
|
30091
|
+
if (!out2.cachePublic) {
|
|
30092
|
+
const md = PY_CACHE_CONTROL_DECORATOR_RE.exec(ln);
|
|
30093
|
+
if (md) {
|
|
30094
|
+
const argTxt = md[1];
|
|
30095
|
+
if (/\bpublic\s*=\s*True\b/.test(argTxt) && (/\bmax_age\s*=\s*[1-9]\d*\b/.test(argTxt) || !/max_age/.test(argTxt))) {
|
|
30096
|
+
out2.cachePublic = { line: i2 + 1, value: argTxt };
|
|
30097
|
+
}
|
|
30098
|
+
}
|
|
30099
|
+
}
|
|
30100
|
+
if (!out2.vary) {
|
|
30101
|
+
const mv = PY_VARY_HEADER_ASSIGN_RE.exec(ln);
|
|
30102
|
+
if (mv && isVaryCovering(mv[2]))
|
|
30103
|
+
out2.vary = true;
|
|
30104
|
+
if (PY_VARY_DECORATOR_RE.test(ln))
|
|
30105
|
+
out2.vary = true;
|
|
30106
|
+
}
|
|
30107
|
+
}
|
|
30108
|
+
}
|
|
30109
|
+
return out2;
|
|
30110
|
+
}
|
|
30111
|
+
|
|
30112
|
+
class CacheNoVaryPass {
|
|
30113
|
+
name = "cache-no-vary";
|
|
30114
|
+
category = "security";
|
|
30115
|
+
run(ctx) {
|
|
30116
|
+
const { graph, language, code } = ctx;
|
|
30117
|
+
const file = graph.ir.meta.file;
|
|
30118
|
+
const findings = [];
|
|
30119
|
+
const isSupported = language === "javascript" || language === "typescript" || language === "python" || language === "go" || language === "java";
|
|
30120
|
+
if (!isSupported)
|
|
30121
|
+
return { findings };
|
|
30122
|
+
if (/(?:\.test|\.spec)\.[jt]sx?$/i.test(file) || /__tests__\/|\/tests?\//i.test(file)) {
|
|
30123
|
+
return { findings };
|
|
30124
|
+
}
|
|
30125
|
+
const callsByHandler = new Map;
|
|
30126
|
+
for (const call of graph.ir.calls) {
|
|
30127
|
+
const key = call.in_method ?? "<top>";
|
|
30128
|
+
let arr = callsByHandler.get(key);
|
|
30129
|
+
if (!arr) {
|
|
30130
|
+
arr = [];
|
|
30131
|
+
callsByHandler.set(key, arr);
|
|
30132
|
+
}
|
|
30133
|
+
arr.push(call);
|
|
30134
|
+
}
|
|
30135
|
+
const emit = (line, handler, cacheValue) => {
|
|
30136
|
+
if (findings.some((f) => f.line === line && f.handler === handler))
|
|
30137
|
+
return;
|
|
30138
|
+
findings.push({ line, language, handler, cacheValue });
|
|
30139
|
+
ctx.addFinding({
|
|
30140
|
+
id: `${this.name}-${file}-${line}`,
|
|
30141
|
+
pass: this.name,
|
|
30142
|
+
category: this.category,
|
|
30143
|
+
rule_id: this.name,
|
|
30144
|
+
cwe: "CWE-524",
|
|
30145
|
+
severity: "medium",
|
|
30146
|
+
level: "warning",
|
|
30147
|
+
message: `Response sets a shared-cacheable Cache-Control ('${cacheValue}') in ` + `a handler that reads authenticated or user-scoped state, but does ` + `not set 'Vary: Cookie' or 'Vary: Authorization'. A shared cache ` + `(CDN, reverse proxy, ISP cache) keys the response by URL only and ` + `may serve one user's body to another. (CWE-524)`,
|
|
30148
|
+
file,
|
|
30149
|
+
line,
|
|
30150
|
+
fix: `Either add 'Vary: Cookie' (or 'Vary: Authorization') so caches key ` + `on the user identity, or change the directive to 'private' / ` + `'no-store' so the response is never shared-cached.`,
|
|
30151
|
+
evidence: {
|
|
30152
|
+
language,
|
|
30153
|
+
handler: handler ?? "<top>",
|
|
30154
|
+
cacheValue
|
|
30155
|
+
}
|
|
30156
|
+
});
|
|
30157
|
+
};
|
|
30158
|
+
for (const [handlerKey, calls] of callsByHandler) {
|
|
30159
|
+
const handler = handlerKey === "<top>" ? null : handlerKey;
|
|
30160
|
+
const cachePublicHits = [];
|
|
30161
|
+
let varyFromCalls = false;
|
|
30162
|
+
let authFromCalls = false;
|
|
30163
|
+
for (const call of calls) {
|
|
30164
|
+
const cls = classifyCall(call, language);
|
|
30165
|
+
if (!cls)
|
|
30166
|
+
continue;
|
|
30167
|
+
if (cls.kind === "cache-public") {
|
|
30168
|
+
cachePublicHits.push({
|
|
30169
|
+
line: call.location.line,
|
|
30170
|
+
value: cls.value ?? ""
|
|
30171
|
+
});
|
|
30172
|
+
} else if (cls.kind === "vary") {
|
|
30173
|
+
varyFromCalls = true;
|
|
30174
|
+
} else if (cls.kind === "auth") {
|
|
30175
|
+
authFromCalls = true;
|
|
30176
|
+
}
|
|
30177
|
+
}
|
|
30178
|
+
let minLine = Infinity;
|
|
30179
|
+
let maxLine = -Infinity;
|
|
30180
|
+
for (const c of calls) {
|
|
30181
|
+
if (c.location?.line) {
|
|
30182
|
+
minLine = Math.min(minLine, c.location.line);
|
|
30183
|
+
maxLine = Math.max(maxLine, c.location.line);
|
|
30184
|
+
}
|
|
30185
|
+
}
|
|
30186
|
+
if (minLine === Infinity)
|
|
30187
|
+
continue;
|
|
30188
|
+
const winStart = Math.max(1, minLine - 5);
|
|
30189
|
+
const winEnd = maxLine + 5;
|
|
30190
|
+
const winScan = scanWindow(code, language, winStart, winEnd);
|
|
30191
|
+
if (winScan.cachePublic)
|
|
30192
|
+
cachePublicHits.push(winScan.cachePublic);
|
|
30193
|
+
const vary = varyFromCalls || winScan.vary;
|
|
30194
|
+
const auth = authFromCalls || winScan.auth;
|
|
30195
|
+
if (cachePublicHits.length === 0)
|
|
30196
|
+
continue;
|
|
30197
|
+
if (vary)
|
|
30198
|
+
continue;
|
|
30199
|
+
if (!auth)
|
|
30200
|
+
continue;
|
|
30201
|
+
emit(cachePublicHits[0].line, handler, cachePublicHits[0].value);
|
|
30202
|
+
}
|
|
30203
|
+
return { findings };
|
|
30204
|
+
}
|
|
30205
|
+
}
|
|
30206
|
+
|
|
29817
30207
|
// ../circle-ir/dist/analysis/passes/jwt-verify-disabled-pass.js
|
|
29818
30208
|
var PY_VERIFY_SIGNATURE_FALSE_RE = /["']verify_signature["']\s*:\s*False\b/;
|
|
29819
30209
|
var PY_VERIFY_KW_FALSE_RE = /\bverify\s*=\s*False\b/;
|
|
@@ -31408,6 +31798,8 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
31408
31798
|
pipeline.add(new TlsVerifyDisabledPass);
|
|
31409
31799
|
if (!disabledPasses.has("module-side-effect"))
|
|
31410
31800
|
pipeline.add(new ModuleSideEffectPass);
|
|
31801
|
+
if (!disabledPasses.has("cache-no-vary"))
|
|
31802
|
+
pipeline.add(new CacheNoVaryPass);
|
|
31411
31803
|
if (!disabledPasses.has("jwt-verify-disabled"))
|
|
31412
31804
|
pipeline.add(new JwtVerifyDisabledPass);
|
|
31413
31805
|
if (!disabledPasses.has("csrf-protection-disabled"))
|
|
@@ -31609,7 +32001,7 @@ var colors = {
|
|
|
31609
32001
|
};
|
|
31610
32002
|
|
|
31611
32003
|
// src/version.ts
|
|
31612
|
-
var version = "3.
|
|
32004
|
+
var version = "3.71.0";
|
|
31613
32005
|
|
|
31614
32006
|
// src/formatters.ts
|
|
31615
32007
|
var SINK_SEVERITY = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cognium-dev",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.71.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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"registry": "https://registry.npmjs.org/"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"circle-ir": "^3.
|
|
68
|
+
"circle-ir": "^3.71.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.5.0",
|