cognium-dev 3.195.0 → 3.198.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 +417 -4
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -24573,6 +24573,9 @@ class LanguageSourcesPass {
|
|
|
24573
24573
|
additionalSanitizers.push(...findPythonRegexAllowlistWrapperSanitizers(code));
|
|
24574
24574
|
additionalSanitizers.push(...findPythonSetMembershipXssGuardSanitizers(code));
|
|
24575
24575
|
additionalSanitizers.push(...findPythonTraversalRejectGuardSanitizers(code));
|
|
24576
|
+
additionalSanitizers.push(...findPythonStringLiteralGuardSanitizers(code));
|
|
24577
|
+
additionalSanitizers.push(...findPythonDefaultSaxParserXxeSanitizers(code));
|
|
24578
|
+
additionalSanitizers.push(...findPythonUrlAllowlistRedirectSanitizers(code));
|
|
24576
24579
|
additionalSanitizers.push(...findPythonDefusedXmlSanitizers(code));
|
|
24577
24580
|
additionalSanitizers.push(...findPythonJinjaAutoescapeSanitizers(code));
|
|
24578
24581
|
const pyMisconfigFindings = findPythonPatternFindings(code, graph.ir.meta.file);
|
|
@@ -25167,15 +25170,165 @@ function findPythonAssignmentSources(sourceCode, language) {
|
|
|
25167
25170
|
}
|
|
25168
25171
|
return sources;
|
|
25169
25172
|
}
|
|
25173
|
+
function evaluatePythonConstExpression(expr, consts) {
|
|
25174
|
+
const COMPARISON = /(<=|>=|==|!=|<|>)/;
|
|
25175
|
+
const parts2 = expr.split(COMPARISON);
|
|
25176
|
+
if (parts2.length === 3) {
|
|
25177
|
+
const left = evaluatePythonConstExpression(parts2[0], consts);
|
|
25178
|
+
const right = evaluatePythonConstExpression(parts2[2], consts);
|
|
25179
|
+
if (typeof left !== "number" || typeof right !== "number")
|
|
25180
|
+
return;
|
|
25181
|
+
switch (parts2[1]) {
|
|
25182
|
+
case "<":
|
|
25183
|
+
return left < right;
|
|
25184
|
+
case ">":
|
|
25185
|
+
return left > right;
|
|
25186
|
+
case "<=":
|
|
25187
|
+
return left <= right;
|
|
25188
|
+
case ">=":
|
|
25189
|
+
return left >= right;
|
|
25190
|
+
case "==":
|
|
25191
|
+
return left === right;
|
|
25192
|
+
case "!=":
|
|
25193
|
+
return left !== right;
|
|
25194
|
+
default:
|
|
25195
|
+
return;
|
|
25196
|
+
}
|
|
25197
|
+
}
|
|
25198
|
+
if (parts2.length !== 1)
|
|
25199
|
+
return;
|
|
25200
|
+
const matched = expr.match(/\d+|[A-Za-z_][A-Za-z0-9_]*|\/\/|[-+*/%()]|\S/g);
|
|
25201
|
+
if (!matched)
|
|
25202
|
+
return;
|
|
25203
|
+
const tokens = matched;
|
|
25204
|
+
let pos = 0;
|
|
25205
|
+
const peek = () => tokens[pos];
|
|
25206
|
+
const parseFactor = () => {
|
|
25207
|
+
const tok = tokens[pos++];
|
|
25208
|
+
if (tok === undefined)
|
|
25209
|
+
return;
|
|
25210
|
+
if (tok === "(") {
|
|
25211
|
+
const inner = parseSum();
|
|
25212
|
+
if (tokens[pos++] !== ")")
|
|
25213
|
+
return;
|
|
25214
|
+
return inner;
|
|
25215
|
+
}
|
|
25216
|
+
if (tok === "-") {
|
|
25217
|
+
const operand = parseFactor();
|
|
25218
|
+
return operand === undefined ? undefined : -operand;
|
|
25219
|
+
}
|
|
25220
|
+
if (/^\d+$/.test(tok))
|
|
25221
|
+
return Number(tok);
|
|
25222
|
+
if (/^[A-Za-z_]/.test(tok))
|
|
25223
|
+
return consts.get(tok);
|
|
25224
|
+
return;
|
|
25225
|
+
};
|
|
25226
|
+
const parseTerm = () => {
|
|
25227
|
+
let value = parseFactor();
|
|
25228
|
+
if (value === undefined)
|
|
25229
|
+
return;
|
|
25230
|
+
while (peek() === "*" || peek() === "/" || peek() === "//" || peek() === "%") {
|
|
25231
|
+
const op = tokens[pos++];
|
|
25232
|
+
const rhs = parseFactor();
|
|
25233
|
+
if (rhs === undefined)
|
|
25234
|
+
return;
|
|
25235
|
+
if ((op === "/" || op === "//" || op === "%") && rhs === 0)
|
|
25236
|
+
return;
|
|
25237
|
+
if (op === "*")
|
|
25238
|
+
value *= rhs;
|
|
25239
|
+
else if (op === "/")
|
|
25240
|
+
value /= rhs;
|
|
25241
|
+
else if (op === "//")
|
|
25242
|
+
value = Math.floor(value / rhs);
|
|
25243
|
+
else
|
|
25244
|
+
value = (value % rhs + rhs) % rhs;
|
|
25245
|
+
}
|
|
25246
|
+
return value;
|
|
25247
|
+
};
|
|
25248
|
+
function parseSum() {
|
|
25249
|
+
let value = parseTerm();
|
|
25250
|
+
if (value === undefined)
|
|
25251
|
+
return;
|
|
25252
|
+
while (peek() === "+" || peek() === "-") {
|
|
25253
|
+
const op = tokens[pos++];
|
|
25254
|
+
const rhs = parseTerm();
|
|
25255
|
+
if (rhs === undefined)
|
|
25256
|
+
return;
|
|
25257
|
+
value = op === "+" ? value + rhs : value - rhs;
|
|
25258
|
+
}
|
|
25259
|
+
return value;
|
|
25260
|
+
}
|
|
25261
|
+
const result = parseSum();
|
|
25262
|
+
return pos === tokens.length ? result : undefined;
|
|
25263
|
+
}
|
|
25264
|
+
function normalizeListIndex(index, length) {
|
|
25265
|
+
const resolved = index < 0 ? length + index : index;
|
|
25266
|
+
return resolved >= 0 && resolved < length ? resolved : undefined;
|
|
25267
|
+
}
|
|
25268
|
+
function computeDeadPythonBranchLines(lines) {
|
|
25269
|
+
const dead = new Set;
|
|
25270
|
+
const consts = new Map;
|
|
25271
|
+
const indentOf = (line) => line.length - line.trimStart().length;
|
|
25272
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25273
|
+
const line = lines[i2];
|
|
25274
|
+
const assign = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=(?!=)\s*(.+)$/);
|
|
25275
|
+
if (assign) {
|
|
25276
|
+
const value = evaluatePythonConstExpression(assign[2].trim(), consts);
|
|
25277
|
+
if (typeof value === "number")
|
|
25278
|
+
consts.set(assign[1], value);
|
|
25279
|
+
else
|
|
25280
|
+
consts.delete(assign[1]);
|
|
25281
|
+
}
|
|
25282
|
+
const ifMatch = line.match(/^(\s*)if\s+(.+?)\s*:\s*$/);
|
|
25283
|
+
if (!ifMatch)
|
|
25284
|
+
continue;
|
|
25285
|
+
const indent = ifMatch[1].length;
|
|
25286
|
+
const decided = evaluatePythonConstExpression(ifMatch[2].trim(), consts);
|
|
25287
|
+
if (decided !== true && decided !== false)
|
|
25288
|
+
continue;
|
|
25289
|
+
const thenStart = i2 + 1;
|
|
25290
|
+
let cursor = thenStart;
|
|
25291
|
+
while (cursor < lines.length && (lines[cursor].trim() === "" || indentOf(lines[cursor]) > indent)) {
|
|
25292
|
+
cursor++;
|
|
25293
|
+
}
|
|
25294
|
+
const thenEnd = cursor - 1;
|
|
25295
|
+
let elseStart = -1;
|
|
25296
|
+
let elseEnd = -1;
|
|
25297
|
+
if (cursor < lines.length && indentOf(lines[cursor]) === indent) {
|
|
25298
|
+
if (/^\s*elif\b/.test(lines[cursor]))
|
|
25299
|
+
continue;
|
|
25300
|
+
if (/^\s*else\s*:\s*$/.test(lines[cursor])) {
|
|
25301
|
+
elseStart = cursor + 1;
|
|
25302
|
+
let k = elseStart;
|
|
25303
|
+
while (k < lines.length && (lines[k].trim() === "" || indentOf(lines[k]) > indent))
|
|
25304
|
+
k++;
|
|
25305
|
+
elseEnd = k - 1;
|
|
25306
|
+
}
|
|
25307
|
+
}
|
|
25308
|
+
if (decided === true) {
|
|
25309
|
+
for (let l = elseStart;l >= 0 && l <= elseEnd; l++)
|
|
25310
|
+
dead.add(l);
|
|
25311
|
+
} else {
|
|
25312
|
+
for (let l = thenStart;l <= thenEnd; l++)
|
|
25313
|
+
dead.add(l);
|
|
25314
|
+
}
|
|
25315
|
+
}
|
|
25316
|
+
return dead;
|
|
25317
|
+
}
|
|
25170
25318
|
function buildPythonTaintedVars(sourceCode) {
|
|
25171
25319
|
const tainted = new Map;
|
|
25172
25320
|
const containerTainted = new Map;
|
|
25173
25321
|
const lines = sourceCode.split(`
|
|
25174
25322
|
`);
|
|
25323
|
+
const deadBranchLines = computeDeadPythonBranchLines(lines);
|
|
25324
|
+
const listElems = new Map;
|
|
25325
|
+
const intConsts = new Map;
|
|
25175
25326
|
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
25176
25327
|
const line = lines[i2];
|
|
25177
25328
|
if (line.trimStart().startsWith("#"))
|
|
25178
25329
|
continue;
|
|
25330
|
+
if (deadBranchLines.has(i2))
|
|
25331
|
+
continue;
|
|
25179
25332
|
const subscriptAssign = line.match(/^\s*([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]\s*=\s*(.+)$/u);
|
|
25180
25333
|
if (subscriptAssign) {
|
|
25181
25334
|
const [, container, , key, rhs2] = subscriptAssign;
|
|
@@ -25194,11 +25347,54 @@ function buildPythonTaintedVars(sourceCode) {
|
|
|
25194
25347
|
}
|
|
25195
25348
|
const containerAppendMatch = line.match(/^\s*([\p{L}\p{N}_]+)\.(append|extend|insert|add|push|put|appendleft)\s*\(\s*(.+?)\s*\)\s*$/u);
|
|
25196
25349
|
if (containerAppendMatch) {
|
|
25197
|
-
const [, receiver, , argExpr] = containerAppendMatch;
|
|
25350
|
+
const [, receiver, method, argExpr] = containerAppendMatch;
|
|
25198
25351
|
const argIsTainted = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(argExpr));
|
|
25199
25352
|
const argIsDirectSource = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(argExpr));
|
|
25200
25353
|
if (argIsTainted || argIsDirectSource)
|
|
25201
25354
|
tainted.set(receiver, tainted.get(receiver) ?? i2 + 1);
|
|
25355
|
+
const elems = listElems.get(receiver);
|
|
25356
|
+
if (elems) {
|
|
25357
|
+
if (method === "append") {
|
|
25358
|
+
elems.push(argIsTainted || argIsDirectSource);
|
|
25359
|
+
} else if (method === "insert") {
|
|
25360
|
+
const insertAt = argExpr.match(/^(-?\d+)\s*,\s*(.+)$/);
|
|
25361
|
+
if (insertAt) {
|
|
25362
|
+
const idx = normalizeListIndex(Number(insertAt[1]), elems.length);
|
|
25363
|
+
const valueExpr = insertAt[2];
|
|
25364
|
+
const valueTainted = [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(valueExpr)) || PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(valueExpr));
|
|
25365
|
+
if (idx !== undefined)
|
|
25366
|
+
elems.splice(idx, 0, valueTainted);
|
|
25367
|
+
else
|
|
25368
|
+
listElems.delete(receiver);
|
|
25369
|
+
} else {
|
|
25370
|
+
listElems.delete(receiver);
|
|
25371
|
+
}
|
|
25372
|
+
} else {
|
|
25373
|
+
listElems.delete(receiver);
|
|
25374
|
+
}
|
|
25375
|
+
}
|
|
25376
|
+
continue;
|
|
25377
|
+
}
|
|
25378
|
+
const popMatch = line.match(/^\s*(?:([\p{L}\p{N}_]+)\s*=\s*)?([\p{L}\p{N}_]+)\.pop\s*\(\s*(-?\d+)?\s*\)\s*$/u);
|
|
25379
|
+
const delMatch = line.match(/^\s*del\s+([\p{L}\p{N}_]+)\s*\[\s*(-?\d+)\s*\]\s*$/u);
|
|
25380
|
+
if (popMatch || delMatch) {
|
|
25381
|
+
const receiver = popMatch ? popMatch[2] : delMatch[1];
|
|
25382
|
+
const rawIndex = popMatch ? popMatch[3] !== undefined ? Number(popMatch[3]) : -1 : Number(delMatch[2]);
|
|
25383
|
+
const elems = listElems.get(receiver);
|
|
25384
|
+
if (elems) {
|
|
25385
|
+
const idx = normalizeListIndex(rawIndex, elems.length);
|
|
25386
|
+
if (idx === undefined)
|
|
25387
|
+
listElems.delete(receiver);
|
|
25388
|
+
else {
|
|
25389
|
+
const [removed] = elems.splice(idx, 1);
|
|
25390
|
+
if (popMatch && popMatch[1]) {
|
|
25391
|
+
if (removed)
|
|
25392
|
+
tainted.set(popMatch[1], i2 + 1);
|
|
25393
|
+
else
|
|
25394
|
+
tainted.delete(popMatch[1]);
|
|
25395
|
+
}
|
|
25396
|
+
}
|
|
25397
|
+
}
|
|
25202
25398
|
continue;
|
|
25203
25399
|
}
|
|
25204
25400
|
const augAssign = line.match(/^\s*([\p{L}\p{N}_]+)\s*\+=\s*(.+)$/u);
|
|
@@ -25221,7 +25417,51 @@ function buildPythonTaintedVars(sourceCode) {
|
|
|
25221
25417
|
const assignMatch = line.match(/^\s*([\p{L}\p{N}_]+)\s*=\s*(.+)$/u);
|
|
25222
25418
|
if (!assignMatch)
|
|
25223
25419
|
continue;
|
|
25224
|
-
const [, lhs,
|
|
25420
|
+
const [, lhs, rhsRaw] = assignMatch;
|
|
25421
|
+
const constValue = evaluatePythonConstExpression(rhsRaw.trim(), intConsts);
|
|
25422
|
+
if (typeof constValue === "number")
|
|
25423
|
+
intConsts.set(lhs, constValue);
|
|
25424
|
+
else
|
|
25425
|
+
intConsts.delete(lhs);
|
|
25426
|
+
let rhs = rhsRaw;
|
|
25427
|
+
const ternary = rhsRaw.match(/^(.+?)\s+if\s+(.+?)\s+else\s+(.+)$/);
|
|
25428
|
+
if (ternary) {
|
|
25429
|
+
const decided = evaluatePythonConstExpression(ternary[2].trim(), intConsts);
|
|
25430
|
+
if (decided === true)
|
|
25431
|
+
rhs = ternary[1].trim();
|
|
25432
|
+
else if (decided === false)
|
|
25433
|
+
rhs = ternary[3].trim();
|
|
25434
|
+
}
|
|
25435
|
+
const listLiteral = rhs.trim().match(/^\[\s*(.*?)\s*\]$/);
|
|
25436
|
+
if (listLiteral) {
|
|
25437
|
+
const inner = listLiteral[1];
|
|
25438
|
+
if (inner === "") {
|
|
25439
|
+
listElems.set(lhs, []);
|
|
25440
|
+
} else if (!inner.includes("[") && !inner.includes("(")) {
|
|
25441
|
+
listElems.set(lhs, inner.split(",").map((part) => {
|
|
25442
|
+
const expr = part.trim();
|
|
25443
|
+
return [...tainted.keys()].some((v) => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, "u").test(expr)) || PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(expr));
|
|
25444
|
+
}));
|
|
25445
|
+
} else {
|
|
25446
|
+
listElems.delete(lhs);
|
|
25447
|
+
}
|
|
25448
|
+
} else {
|
|
25449
|
+
listElems.delete(lhs);
|
|
25450
|
+
}
|
|
25451
|
+
const indexRead = rhs.trim().match(/^([\p{L}\p{N}_]+)\s*\[\s*(-?\d+)\s*\]$/u);
|
|
25452
|
+
if (indexRead) {
|
|
25453
|
+
const elems = listElems.get(indexRead[1]);
|
|
25454
|
+
if (elems) {
|
|
25455
|
+
const idx = normalizeListIndex(Number(indexRead[2]), elems.length);
|
|
25456
|
+
if (idx !== undefined) {
|
|
25457
|
+
if (elems[idx])
|
|
25458
|
+
tainted.set(lhs, i2 + 1);
|
|
25459
|
+
else
|
|
25460
|
+
tainted.delete(lhs);
|
|
25461
|
+
continue;
|
|
25462
|
+
}
|
|
25463
|
+
}
|
|
25464
|
+
}
|
|
25225
25465
|
const isDirectSource = PYTHON_TAINTED_PATTERNS2.some((p) => p.pattern.test(rhs));
|
|
25226
25466
|
let propagatedFrom;
|
|
25227
25467
|
const dictAccessMatch = rhs.trim().match(/^([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]$/u);
|
|
@@ -26539,6 +26779,147 @@ function findPythonTraversalRejectGuardSanitizers(code) {
|
|
|
26539
26779
|
}
|
|
26540
26780
|
return sanitizers;
|
|
26541
26781
|
}
|
|
26782
|
+
function findPythonStringLiteralGuardSanitizers(code) {
|
|
26783
|
+
const sanitizers = [];
|
|
26784
|
+
const lines = code.split(`
|
|
26785
|
+
`);
|
|
26786
|
+
const terminator = /\b(return|raise|abort\s*\(|sys\.exit\s*\()/;
|
|
26787
|
+
const Q = `(?:'(?:\\\\')'|"'"|'"'|"(?:\\\\")")`;
|
|
26788
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26789
|
+
const guard = lines[i2].match(new RegExp(`^(\\s*)if\\s+not\\s+([A-Za-z_][A-Za-z0-9_]*)\\.startswith\\(\\s*${Q}\\s*\\)` + `\\s+or\\s+not\\s+\\2\\.endswith\\(\\s*${Q}\\s*\\)` + `\\s+or\\s+${Q}\\s+in\\s+\\2\\[1:-1\\]\\s*:\\s*$`));
|
|
26790
|
+
if (!guard)
|
|
26791
|
+
continue;
|
|
26792
|
+
const guardIndent = guard[1].length;
|
|
26793
|
+
const guardedVar = guard[2];
|
|
26794
|
+
let bodyHasTerminator = false;
|
|
26795
|
+
let blockEnd = -1;
|
|
26796
|
+
const maxScan = Math.min(lines.length, i2 + 26);
|
|
26797
|
+
for (let j = i2 + 1;j < maxScan; j++) {
|
|
26798
|
+
const line = lines[j];
|
|
26799
|
+
if (line.trim() === "")
|
|
26800
|
+
continue;
|
|
26801
|
+
const indent = line.length - line.trimStart().length;
|
|
26802
|
+
if (indent <= guardIndent) {
|
|
26803
|
+
blockEnd = j - 1;
|
|
26804
|
+
break;
|
|
26805
|
+
}
|
|
26806
|
+
if (terminator.test(line))
|
|
26807
|
+
bodyHasTerminator = true;
|
|
26808
|
+
}
|
|
26809
|
+
if (blockEnd === -1)
|
|
26810
|
+
blockEnd = Math.min(lines.length - 1, i2 + 25);
|
|
26811
|
+
if (!bodyHasTerminator)
|
|
26812
|
+
continue;
|
|
26813
|
+
const varRe = new RegExp(`\\b${guardedVar}\\b`);
|
|
26814
|
+
for (let l = blockEnd + 2;l <= lines.length; l++) {
|
|
26815
|
+
if (!varRe.test(lines[l - 1]))
|
|
26816
|
+
continue;
|
|
26817
|
+
sanitizers.push({
|
|
26818
|
+
type: "python_string_literal_guard",
|
|
26819
|
+
method: "if",
|
|
26820
|
+
line: l,
|
|
26821
|
+
sanitizes: ["code_injection"]
|
|
26822
|
+
});
|
|
26823
|
+
}
|
|
26824
|
+
}
|
|
26825
|
+
return sanitizers;
|
|
26826
|
+
}
|
|
26827
|
+
function findPythonDefaultSaxParserXxeSanitizers(code) {
|
|
26828
|
+
const sanitizers = [];
|
|
26829
|
+
const lines = code.split(`
|
|
26830
|
+
`);
|
|
26831
|
+
const parserVars = new Set;
|
|
26832
|
+
for (const line of lines) {
|
|
26833
|
+
const made = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:xml\.)?sax\.make_parser\s*\(/);
|
|
26834
|
+
if (made)
|
|
26835
|
+
parserVars.add(made[1]);
|
|
26836
|
+
}
|
|
26837
|
+
if (parserVars.size === 0)
|
|
26838
|
+
return sanitizers;
|
|
26839
|
+
for (const line of lines) {
|
|
26840
|
+
for (const parser of [...parserVars]) {
|
|
26841
|
+
const enabled = new RegExp(`\\b${parser}\\.setFeature\\s*\\([^,]*external_(?:ges|pes)[^,]*,\\s*True\\s*\\)`);
|
|
26842
|
+
const resolver = new RegExp(`\\b${parser}\\.setEntityResolver\\s*\\(`);
|
|
26843
|
+
const rebound = new RegExp(`^\\s*${parser}\\s*=(?!=)`);
|
|
26844
|
+
if (enabled.test(line) || resolver.test(line))
|
|
26845
|
+
parserVars.delete(parser);
|
|
26846
|
+
else if (rebound.test(line) && !/make_parser\s*\(/.test(line))
|
|
26847
|
+
parserVars.delete(parser);
|
|
26848
|
+
}
|
|
26849
|
+
}
|
|
26850
|
+
if (parserVars.size === 0)
|
|
26851
|
+
return sanitizers;
|
|
26852
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26853
|
+
const line = lines[i2];
|
|
26854
|
+
const parseCall = /\b(?:minidom\.)?parseString\s*\(|\b(?:minidom\.)?parse\s*\(|\bsax\.parse(?:String)?\s*\(/;
|
|
26855
|
+
if (!parseCall.test(line))
|
|
26856
|
+
continue;
|
|
26857
|
+
const usesDefaultParser = [...parserVars].some((p) => new RegExp(`\\b${p}\\b`).test(line));
|
|
26858
|
+
if (!usesDefaultParser)
|
|
26859
|
+
continue;
|
|
26860
|
+
sanitizers.push({
|
|
26861
|
+
type: "python_default_sax_parser",
|
|
26862
|
+
method: "make_parser",
|
|
26863
|
+
line: i2 + 1,
|
|
26864
|
+
sanitizes: ["xxe"]
|
|
26865
|
+
});
|
|
26866
|
+
}
|
|
26867
|
+
return sanitizers;
|
|
26868
|
+
}
|
|
26869
|
+
function findPythonUrlAllowlistRedirectSanitizers(code) {
|
|
26870
|
+
const sanitizers = [];
|
|
26871
|
+
const lines = code.split(`
|
|
26872
|
+
`);
|
|
26873
|
+
const parsedFrom = new Map;
|
|
26874
|
+
for (const line of lines) {
|
|
26875
|
+
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:urllib\.parse\.)?urlparse\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/);
|
|
26876
|
+
if (m)
|
|
26877
|
+
parsedFrom.set(m[1], m[2]);
|
|
26878
|
+
}
|
|
26879
|
+
if (parsedFrom.size === 0)
|
|
26880
|
+
return sanitizers;
|
|
26881
|
+
const terminator = /\b(return|raise|abort\s*\(|sys\.exit\s*\()/;
|
|
26882
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
26883
|
+
const guard = lines[i2].match(/^(\s*)if\s+([A-Za-z_][A-Za-z0-9_]*)\.netloc\s+not\s+in\s+[[({]/);
|
|
26884
|
+
if (!guard)
|
|
26885
|
+
continue;
|
|
26886
|
+
const guardIndent = guard[1].length;
|
|
26887
|
+
const rawVar = parsedFrom.get(guard[2]);
|
|
26888
|
+
if (!rawVar)
|
|
26889
|
+
continue;
|
|
26890
|
+
let bodyHasTerminator = false;
|
|
26891
|
+
let blockEnd = -1;
|
|
26892
|
+
const maxScan = Math.min(lines.length, i2 + 26);
|
|
26893
|
+
for (let j = i2 + 1;j < maxScan; j++) {
|
|
26894
|
+
const line = lines[j];
|
|
26895
|
+
if (line.trim() === "")
|
|
26896
|
+
continue;
|
|
26897
|
+
const indent = line.length - line.trimStart().length;
|
|
26898
|
+
if (indent <= guardIndent) {
|
|
26899
|
+
blockEnd = j - 1;
|
|
26900
|
+
break;
|
|
26901
|
+
}
|
|
26902
|
+
if (terminator.test(line))
|
|
26903
|
+
bodyHasTerminator = true;
|
|
26904
|
+
}
|
|
26905
|
+
if (blockEnd === -1)
|
|
26906
|
+
blockEnd = Math.min(lines.length - 1, i2 + 25);
|
|
26907
|
+
if (!bodyHasTerminator)
|
|
26908
|
+
continue;
|
|
26909
|
+
const varRe = new RegExp(`\\b${rawVar}\\b`);
|
|
26910
|
+
for (let l = blockEnd + 2;l <= lines.length; l++) {
|
|
26911
|
+
if (!varRe.test(lines[l - 1]))
|
|
26912
|
+
continue;
|
|
26913
|
+
sanitizers.push({
|
|
26914
|
+
type: "python_url_allowlist_guard",
|
|
26915
|
+
method: "if",
|
|
26916
|
+
line: l,
|
|
26917
|
+
sanitizes: ["open_redirect"]
|
|
26918
|
+
});
|
|
26919
|
+
}
|
|
26920
|
+
}
|
|
26921
|
+
return sanitizers;
|
|
26922
|
+
}
|
|
26542
26923
|
function findPythonDefusedXmlSanitizers(code) {
|
|
26543
26924
|
const sanitizers = [];
|
|
26544
26925
|
const lines = code.split(`
|
|
@@ -31559,14 +31940,20 @@ class SinkFilterPass {
|
|
|
31559
31940
|
if (sink.type !== "xpath_injection")
|
|
31560
31941
|
return true;
|
|
31561
31942
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
31562
|
-
const
|
|
31943
|
+
const taintedValueVars = [...pyTaintedVars.keys()].filter((v) => new RegExp(`(?<![\\w.])${v}\\b(?!\\s*=(?!=))`).test(sinkLineText));
|
|
31944
|
+
const taintedVarOnLine = taintedValueVars[0];
|
|
31563
31945
|
const oopVarOnLine = [...oopFieldVars].find((v) => sinkLineText.includes(v));
|
|
31564
31946
|
if (oopVarOnLine)
|
|
31565
31947
|
return true;
|
|
31566
31948
|
if (!taintedVarOnLine)
|
|
31567
31949
|
return false;
|
|
31950
|
+
const neutralised = (v) => pySanitizedVars.has(v) || new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${v}\\b`).test(sinkLineText) || isXPathQuoteEscapedOnLine(sinkLineText, v);
|
|
31951
|
+
if (taintedValueVars.every(neutralised))
|
|
31952
|
+
return false;
|
|
31568
31953
|
if (pySanitizedVars.has(taintedVarOnLine))
|
|
31569
31954
|
return false;
|
|
31955
|
+
if (isXPathQuoteEscapedOnLine(sinkLineText, taintedVarOnLine))
|
|
31956
|
+
return false;
|
|
31570
31957
|
if (new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${taintedVarOnLine}\\b`).test(sinkLineText))
|
|
31571
31958
|
return false;
|
|
31572
31959
|
return true;
|
|
@@ -32670,6 +33057,28 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
|
|
|
32670
33057
|
return true;
|
|
32671
33058
|
});
|
|
32672
33059
|
}
|
|
33060
|
+
function isXPathQuoteEscapedOnLine(lineText, varName) {
|
|
33061
|
+
const STRING_LITERAL = `'(?:[^'\\\\]|\\\\.)*'|"(?:[^"\\\\]|\\\\.)*"`;
|
|
33062
|
+
const replaceCall = new RegExp(`^${varName}\\.replace\\s*\\(\\s*(${STRING_LITERAL})\\s*,\\s*(${STRING_LITERAL})\\s*\\)`);
|
|
33063
|
+
const decode = (literal) => literal.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
33064
|
+
const occurrence = new RegExp(`(?<![\\w.])${varName}\\b`, "g");
|
|
33065
|
+
let match;
|
|
33066
|
+
let seen = 0;
|
|
33067
|
+
while ((match = occurrence.exec(lineText)) !== null) {
|
|
33068
|
+
seen++;
|
|
33069
|
+
const rest = lineText.slice(match.index);
|
|
33070
|
+
const call = replaceCall.exec(rest);
|
|
33071
|
+
if (!call)
|
|
33072
|
+
return false;
|
|
33073
|
+
const searched = decode(call[1]);
|
|
33074
|
+
const replacement = decode(call[2]);
|
|
33075
|
+
if (searched !== "'" && searched !== '"')
|
|
33076
|
+
return false;
|
|
33077
|
+
if (replacement.includes("'") || replacement.includes('"'))
|
|
33078
|
+
return false;
|
|
33079
|
+
}
|
|
33080
|
+
return seen > 0;
|
|
33081
|
+
}
|
|
32673
33082
|
|
|
32674
33083
|
// ../circle-ir/dist/analysis/passes/sink-semantics-pass.js
|
|
32675
33084
|
function buildRegistry(entries) {
|
|
@@ -44959,6 +45368,10 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
44959
45368
|
return analyzeMarkupFile(code, filePath, options, language);
|
|
44960
45369
|
}
|
|
44961
45370
|
let parseGrammar = language;
|
|
45371
|
+
if (language === "tsx") {
|
|
45372
|
+
parseGrammar = "tsx";
|
|
45373
|
+
language = "typescript";
|
|
45374
|
+
}
|
|
44962
45375
|
if (language === "javascript" || language === "typescript") {
|
|
44963
45376
|
const lower = filePath.toLowerCase();
|
|
44964
45377
|
if (lower.endsWith(".tsx") || lower.endsWith(".jsx")) {
|
|
@@ -46007,7 +46420,7 @@ var colors = {
|
|
|
46007
46420
|
};
|
|
46008
46421
|
|
|
46009
46422
|
// src/version.ts
|
|
46010
|
-
var version = "3.
|
|
46423
|
+
var version = "3.198.0";
|
|
46011
46424
|
|
|
46012
46425
|
// src/formatters.ts
|
|
46013
46426
|
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.198.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.198.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^25.5.0",
|