circle-ir 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.
@@ -1 +1 @@
1
- {"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"language-sources-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/language-sources-pass.ts"],"names":[],"mappings":"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"}
@@ -313,6 +313,9 @@ export class LanguageSourcesPass {
313
313
  additionalSanitizers.push(...findPythonRegexAllowlistWrapperSanitizers(code));
314
314
  additionalSanitizers.push(...findPythonSetMembershipXssGuardSanitizers(code));
315
315
  additionalSanitizers.push(...findPythonTraversalRejectGuardSanitizers(code));
316
+ additionalSanitizers.push(...findPythonStringLiteralGuardSanitizers(code));
317
+ additionalSanitizers.push(...findPythonDefaultSaxParserXxeSanitizers(code));
318
+ additionalSanitizers.push(...findPythonUrlAllowlistRedirectSanitizers(code));
316
319
  additionalSanitizers.push(...findPythonDefusedXmlSanitizers(code));
317
320
  // Sprint 77a (#216 Pattern X): Jinja2 Environment(autoescape=...) +
318
321
  // .render(...) sanitizer.
@@ -1165,14 +1168,205 @@ function findPythonAssignmentSources(sourceCode, language) {
1165
1168
  }
1166
1169
  return sources;
1167
1170
  }
1171
+ /**
1172
+ * Evaluate an integer arithmetic / comparison expression built only from
1173
+ * literals, known integer constants, `+ - * / // %`, parentheses and one
1174
+ * comparison operator. Returns `undefined` for anything it cannot decide —
1175
+ * an unknown name, a non-integer, a call, a string, division by zero.
1176
+ *
1177
+ * Deliberately hand-rolled rather than `eval`-ed: the input is untrusted
1178
+ * source text, and the analyzer must stay browser-safe.
1179
+ */
1180
+ function evaluatePythonConstExpression(expr, consts) {
1181
+ const COMPARISON = /(<=|>=|==|!=|<|>)/;
1182
+ const parts = expr.split(COMPARISON);
1183
+ if (parts.length === 3) {
1184
+ const left = evaluatePythonConstExpression(parts[0], consts);
1185
+ const right = evaluatePythonConstExpression(parts[2], consts);
1186
+ if (typeof left !== 'number' || typeof right !== 'number')
1187
+ return undefined;
1188
+ switch (parts[1]) {
1189
+ case '<': return left < right;
1190
+ case '>': return left > right;
1191
+ case '<=': return left <= right;
1192
+ case '>=': return left >= right;
1193
+ case '==': return left === right;
1194
+ case '!=': return left !== right;
1195
+ default: return undefined;
1196
+ }
1197
+ }
1198
+ if (parts.length !== 1)
1199
+ return undefined;
1200
+ const matched = expr.match(/\d+|[A-Za-z_][A-Za-z0-9_]*|\/\/|[-+*/%()]|\S/g);
1201
+ if (!matched)
1202
+ return undefined;
1203
+ // Explicit binding so the hoisted `parseSum` declaration keeps the
1204
+ // non-null narrowing.
1205
+ const tokens = matched;
1206
+ let pos = 0;
1207
+ const peek = () => tokens[pos];
1208
+ // term := factor (('*' | '/' | '//' | '%') factor)*
1209
+ // sum := term (('+' | '-') term)*
1210
+ const parseFactor = () => {
1211
+ const tok = tokens[pos++];
1212
+ if (tok === undefined)
1213
+ return undefined;
1214
+ if (tok === '(') {
1215
+ const inner = parseSum();
1216
+ if (tokens[pos++] !== ')')
1217
+ return undefined;
1218
+ return inner;
1219
+ }
1220
+ if (tok === '-') {
1221
+ const operand = parseFactor();
1222
+ return operand === undefined ? undefined : -operand;
1223
+ }
1224
+ if (/^\d+$/.test(tok))
1225
+ return Number(tok);
1226
+ if (/^[A-Za-z_]/.test(tok))
1227
+ return consts.get(tok);
1228
+ return undefined;
1229
+ };
1230
+ const parseTerm = () => {
1231
+ let value = parseFactor();
1232
+ if (value === undefined)
1233
+ return undefined;
1234
+ while (peek() === '*' || peek() === '/' || peek() === '//' || peek() === '%') {
1235
+ const op = tokens[pos++];
1236
+ const rhs = parseFactor();
1237
+ if (rhs === undefined)
1238
+ return undefined;
1239
+ if ((op === '/' || op === '//' || op === '%') && rhs === 0)
1240
+ return undefined;
1241
+ if (op === '*')
1242
+ value *= rhs;
1243
+ else if (op === '/')
1244
+ value /= rhs;
1245
+ else if (op === '//')
1246
+ value = Math.floor(value / rhs);
1247
+ else
1248
+ value = ((value % rhs) + rhs) % rhs;
1249
+ }
1250
+ return value;
1251
+ };
1252
+ function parseSum() {
1253
+ let value = parseTerm();
1254
+ if (value === undefined)
1255
+ return undefined;
1256
+ while (peek() === '+' || peek() === '-') {
1257
+ const op = tokens[pos++];
1258
+ const rhs = parseTerm();
1259
+ if (rhs === undefined)
1260
+ return undefined;
1261
+ value = op === '+' ? value + rhs : value - rhs;
1262
+ }
1263
+ return value;
1264
+ }
1265
+ const result = parseSum();
1266
+ return pos === tokens.length ? result : undefined;
1267
+ }
1268
+ /**
1269
+ * Resolve a Python list index (which may be negative) against a known length.
1270
+ * Returns `undefined` when it falls outside the list, in which case the caller
1271
+ * stops tracking that list rather than guessing.
1272
+ */
1273
+ function normalizeListIndex(index, length) {
1274
+ const resolved = index < 0 ? length + index : index;
1275
+ return resolved >= 0 && resolved < length ? resolved : undefined;
1276
+ }
1277
+ /**
1278
+ * Line indices (0-based) belonging to a branch that cannot execute, because
1279
+ * its `if` condition is decidable from tracked integer constants:
1280
+ *
1281
+ * num = 86
1282
+ * if 7 * 42 - num > 200: # 208 > 200 → always true
1283
+ * bar = 'safe'
1284
+ * else:
1285
+ * bar = param # dead — must not seed taint
1286
+ *
1287
+ * The statement-level sibling of the conditional-expression folding in
1288
+ * `buildPythonTaintedVars`. Same evaluator, same conservatism: an undecidable
1289
+ * condition marks nothing dead.
1290
+ *
1291
+ * `elif` chains are skipped entirely — deciding one arm says nothing about the
1292
+ * rest without evaluating every preceding condition, and the corpus shapes
1293
+ * this targets are plain if/else.
1294
+ */
1295
+ function computeDeadPythonBranchLines(lines) {
1296
+ const dead = new Set();
1297
+ const consts = new Map();
1298
+ const indentOf = (line) => line.length - line.trimStart().length;
1299
+ for (let i = 0; i < lines.length; i++) {
1300
+ const line = lines[i];
1301
+ const assign = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=(?!=)\s*(.+)$/);
1302
+ if (assign) {
1303
+ const value = evaluatePythonConstExpression(assign[2].trim(), consts);
1304
+ if (typeof value === 'number')
1305
+ consts.set(assign[1], value);
1306
+ else
1307
+ consts.delete(assign[1]);
1308
+ }
1309
+ const ifMatch = line.match(/^(\s*)if\s+(.+?)\s*:\s*$/);
1310
+ if (!ifMatch)
1311
+ continue;
1312
+ const indent = ifMatch[1].length;
1313
+ const decided = evaluatePythonConstExpression(ifMatch[2].trim(), consts);
1314
+ if (decided !== true && decided !== false)
1315
+ continue;
1316
+ // Then-block: everything more deeply indented than the `if`.
1317
+ const thenStart = i + 1;
1318
+ let cursor = thenStart;
1319
+ while (cursor < lines.length && (lines[cursor].trim() === '' || indentOf(lines[cursor]) > indent)) {
1320
+ cursor++;
1321
+ }
1322
+ const thenEnd = cursor - 1;
1323
+ let elseStart = -1;
1324
+ let elseEnd = -1;
1325
+ if (cursor < lines.length && indentOf(lines[cursor]) === indent) {
1326
+ if (/^\s*elif\b/.test(lines[cursor]))
1327
+ continue;
1328
+ if (/^\s*else\s*:\s*$/.test(lines[cursor])) {
1329
+ elseStart = cursor + 1;
1330
+ let k = elseStart;
1331
+ while (k < lines.length && (lines[k].trim() === '' || indentOf(lines[k]) > indent))
1332
+ k++;
1333
+ elseEnd = k - 1;
1334
+ }
1335
+ }
1336
+ if (decided === true) {
1337
+ for (let l = elseStart; l >= 0 && l <= elseEnd; l++)
1338
+ dead.add(l);
1339
+ }
1340
+ else {
1341
+ for (let l = thenStart; l <= thenEnd; l++)
1342
+ dead.add(l);
1343
+ }
1344
+ }
1345
+ return dead;
1346
+ }
1168
1347
  export function buildPythonTaintedVars(sourceCode) {
1169
1348
  const tainted = new Map();
1170
1349
  const containerTainted = new Map();
1171
1350
  const lines = sourceCode.split('\n');
1351
+ const deadBranchLines = computeDeadPythonBranchLines(lines);
1352
+ // Per-element taint for lists followed since construction (`x = []` or a
1353
+ // literal). Only append / insert / pop / del are modelled; any other
1354
+ // mutation removes the list from the map and the coarse
1355
+ // whole-container behaviour takes over.
1356
+ const listElems = new Map();
1357
+ // Integer constants in scope, for folding `X = A if <const cond> else B`.
1358
+ // Only literal-valued and constant-derived integers are tracked; anything
1359
+ // else (call result, string, tainted read) removes the name from the map so
1360
+ // a later condition mentioning it stays undecided.
1361
+ const intConsts = new Map();
1172
1362
  for (let i = 0; i < lines.length; i++) {
1173
1363
  const line = lines[i];
1174
1364
  if (line.trimStart().startsWith('#'))
1175
1365
  continue;
1366
+ // A branch whose `if` condition is decidably false cannot assign anything,
1367
+ // so its lines must not seed or clobber taint.
1368
+ if (deadBranchLines.has(i))
1369
+ continue;
1176
1370
  const subscriptAssign = line.match(/^\s*([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]\s*=\s*(.+)$/u);
1177
1371
  if (subscriptAssign) {
1178
1372
  const [, container, , key, rhs2] = subscriptAssign;
@@ -1197,11 +1391,67 @@ export function buildPythonTaintedVars(sourceCode) {
1197
1391
  // word-boundary scan below.
1198
1392
  const containerAppendMatch = line.match(/^\s*([\p{L}\p{N}_]+)\.(append|extend|insert|add|push|put|appendleft)\s*\(\s*(.+?)\s*\)\s*$/u);
1199
1393
  if (containerAppendMatch) {
1200
- const [, receiver, , argExpr] = containerAppendMatch;
1394
+ const [, receiver, method, argExpr] = containerAppendMatch;
1201
1395
  const argIsTainted = [...tainted.keys()].some(v => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, 'u').test(argExpr));
1202
1396
  const argIsDirectSource = PYTHON_TAINTED_PATTERNS.some(p => p.pattern.test(argExpr));
1203
1397
  if (argIsTainted || argIsDirectSource)
1204
1398
  tainted.set(receiver, tainted.get(receiver) ?? (i + 1));
1399
+ // Per-element tracking for lists we have followed since construction.
1400
+ // `append` pushes; `insert(i, x)` splices at a literal index; anything
1401
+ // else (extend, an unparsable index) makes the element map unreliable,
1402
+ // so the list drops back to the coarse whole-container behaviour above.
1403
+ const elems = listElems.get(receiver);
1404
+ if (elems) {
1405
+ if (method === 'append') {
1406
+ elems.push(argIsTainted || argIsDirectSource);
1407
+ }
1408
+ else if (method === 'insert') {
1409
+ const insertAt = argExpr.match(/^(-?\d+)\s*,\s*(.+)$/);
1410
+ if (insertAt) {
1411
+ const idx = normalizeListIndex(Number(insertAt[1]), elems.length);
1412
+ const valueExpr = insertAt[2];
1413
+ const valueTainted = [...tainted.keys()].some(v => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, 'u').test(valueExpr)) ||
1414
+ PYTHON_TAINTED_PATTERNS.some(p => p.pattern.test(valueExpr));
1415
+ if (idx !== undefined)
1416
+ elems.splice(idx, 0, valueTainted);
1417
+ else
1418
+ listElems.delete(receiver);
1419
+ }
1420
+ else {
1421
+ listElems.delete(receiver);
1422
+ }
1423
+ }
1424
+ else {
1425
+ listElems.delete(receiver);
1426
+ }
1427
+ }
1428
+ continue;
1429
+ }
1430
+ // `lst.pop(i)` / `lst.pop()` / `del lst[i]` — removal shifts every later
1431
+ // element down one, which is exactly what makes `lst[1]` safe in
1432
+ // lst.append('safe'); lst.append(param); lst.append('moresafe')
1433
+ // lst.pop(0); bar = lst[1] # 'moresafe', not param
1434
+ const popMatch = line.match(/^\s*(?:([\p{L}\p{N}_]+)\s*=\s*)?([\p{L}\p{N}_]+)\.pop\s*\(\s*(-?\d+)?\s*\)\s*$/u);
1435
+ const delMatch = line.match(/^\s*del\s+([\p{L}\p{N}_]+)\s*\[\s*(-?\d+)\s*\]\s*$/u);
1436
+ if (popMatch || delMatch) {
1437
+ const receiver = popMatch ? popMatch[2] : delMatch[1];
1438
+ const rawIndex = popMatch ? (popMatch[3] !== undefined ? Number(popMatch[3]) : -1) : Number(delMatch[2]);
1439
+ const elems = listElems.get(receiver);
1440
+ if (elems) {
1441
+ const idx = normalizeListIndex(rawIndex, elems.length);
1442
+ if (idx === undefined)
1443
+ listElems.delete(receiver);
1444
+ else {
1445
+ const [removed] = elems.splice(idx, 1);
1446
+ // `x = lst.pop(i)` binds the removed element's taint to x.
1447
+ if (popMatch && popMatch[1]) {
1448
+ if (removed)
1449
+ tainted.set(popMatch[1], i + 1);
1450
+ else
1451
+ tainted.delete(popMatch[1]);
1452
+ }
1453
+ }
1454
+ }
1205
1455
  continue;
1206
1456
  }
1207
1457
  const augAssign = line.match(/^\s*([\p{L}\p{N}_]+)\s*\+=\s*(.+)$/u);
@@ -1224,7 +1474,70 @@ export function buildPythonTaintedVars(sourceCode) {
1224
1474
  const assignMatch = line.match(/^\s*([\p{L}\p{N}_]+)\s*=\s*(.+)$/u);
1225
1475
  if (!assignMatch)
1226
1476
  continue;
1227
- const [, lhs, rhs] = assignMatch;
1477
+ const [, lhs, rhsRaw] = assignMatch;
1478
+ // Maintain the integer-constant map used to fold conditional expressions.
1479
+ const constValue = evaluatePythonConstExpression(rhsRaw.trim(), intConsts);
1480
+ if (typeof constValue === 'number')
1481
+ intConsts.set(lhs, constValue);
1482
+ else
1483
+ intConsts.delete(lhs);
1484
+ // Fold `X = <then> if <condition> else <else>` when the condition is
1485
+ // decidable from those constants. The dead branch cannot contribute taint,
1486
+ // so scanning the whole RHS for tainted names — as the generic path below
1487
+ // does — reports a flow that can never execute. This is the second-largest
1488
+ // FP shape on OWASP BenchmarkPython (`bar = "safe" if 7 * 18 + num > 200
1489
+ // else param`, where num is 106 and the safe literal always wins).
1490
+ //
1491
+ // Only the *condition* is folded, never the branches: `bar = a if c else b`
1492
+ // keeps whichever branch is live, taint and all. An undecidable condition
1493
+ // leaves the RHS untouched, so unknown values stay conservative.
1494
+ let rhs = rhsRaw;
1495
+ const ternary = rhsRaw.match(/^(.+?)\s+if\s+(.+?)\s+else\s+(.+)$/);
1496
+ if (ternary) {
1497
+ const decided = evaluatePythonConstExpression(ternary[2].trim(), intConsts);
1498
+ if (decided === true)
1499
+ rhs = ternary[1].trim();
1500
+ else if (decided === false)
1501
+ rhs = ternary[3].trim();
1502
+ }
1503
+ // Track list construction so the element map has a starting point, and
1504
+ // invalidate it whenever the name is rebound to something else.
1505
+ const listLiteral = rhs.trim().match(/^\[\s*(.*?)\s*\]$/);
1506
+ if (listLiteral) {
1507
+ const inner = listLiteral[1];
1508
+ if (inner === '') {
1509
+ listElems.set(lhs, []);
1510
+ }
1511
+ else if (!inner.includes('[') && !inner.includes('(')) {
1512
+ listElems.set(lhs, inner.split(',').map(part => {
1513
+ const expr = part.trim();
1514
+ return ([...tainted.keys()].some(v => new RegExp(`(?<![\\p{L}\\p{N}_])${v}(?![\\p{L}\\p{N}_])`, 'u').test(expr)) ||
1515
+ PYTHON_TAINTED_PATTERNS.some(p => p.pattern.test(expr)));
1516
+ }));
1517
+ }
1518
+ else {
1519
+ listElems.delete(lhs);
1520
+ }
1521
+ }
1522
+ else {
1523
+ listElems.delete(lhs);
1524
+ }
1525
+ // Indexed read of a tracked list: `bar = lst[1]` takes that element's
1526
+ // taint precisely instead of inheriting the whole container's.
1527
+ const indexRead = rhs.trim().match(/^([\p{L}\p{N}_]+)\s*\[\s*(-?\d+)\s*\]$/u);
1528
+ if (indexRead) {
1529
+ const elems = listElems.get(indexRead[1]);
1530
+ if (elems) {
1531
+ const idx = normalizeListIndex(Number(indexRead[2]), elems.length);
1532
+ if (idx !== undefined) {
1533
+ if (elems[idx])
1534
+ tainted.set(lhs, i + 1);
1535
+ else
1536
+ tainted.delete(lhs);
1537
+ continue;
1538
+ }
1539
+ }
1540
+ }
1228
1541
  const isDirectSource = PYTHON_TAINTED_PATTERNS.some(p => p.pattern.test(rhs));
1229
1542
  let propagatedFrom;
1230
1543
  const dictAccessMatch = rhs.trim().match(/^([\p{L}\p{N}_]+)\[(['"])([^'"]+)\2\]$/u);
@@ -3055,6 +3368,214 @@ function findPythonTraversalRejectGuardSanitizers(code) {
3055
3368
  }
3056
3369
  return sanitizers;
3057
3370
  }
3371
+ /**
3372
+ * Detect the Python "must be a plain string literal" validation guard
3373
+ * (cognium-dev #4 residual-FP audit):
3374
+ *
3375
+ * if not bar.startswith("'") or not bar.endswith("'") or "'" in bar[1:-1]:
3376
+ * return "Exec argument must be a plain string literal."
3377
+ * exec(bar)
3378
+ *
3379
+ * Past that guard `bar` is a quoted literal whose interior contains no quote,
3380
+ * so `exec`/`eval` sees a string-literal expression and cannot be steered into
3381
+ * executing attacker code — there is no way to close the quote and append a
3382
+ * statement. This is the whole codeinj FP bucket on OWASP BenchmarkPython.
3383
+ *
3384
+ * All three clauses are required. Any two of them leave an escape: without the
3385
+ * interior check, `'a' + evil() + '` satisfies startswith/endswith while still
3386
+ * breaking out of the literal. Only `code_injection` is sanitized — proving a
3387
+ * value is a quoted literal says nothing about SQL, a path, or HTML, where the
3388
+ * quote characters themselves are the payload.
3389
+ */
3390
+ function findPythonStringLiteralGuardSanitizers(code) {
3391
+ const sanitizers = [];
3392
+ const lines = code.split('\n');
3393
+ const terminator = /\b(return|raise|abort\s*\(|sys\.exit\s*\()/;
3394
+ // Quote literal in Python source: '\'' , "'" , '"' , "\"".
3395
+ const Q = `(?:'(?:\\\\')'|"'"|'"'|"(?:\\\\")")`;
3396
+ for (let i = 0; i < lines.length; i++) {
3397
+ const guard = lines[i].match(new RegExp(`^(\\s*)if\\s+not\\s+([A-Za-z_][A-Za-z0-9_]*)\\.startswith\\(\\s*${Q}\\s*\\)` +
3398
+ `\\s+or\\s+not\\s+\\2\\.endswith\\(\\s*${Q}\\s*\\)` +
3399
+ `\\s+or\\s+${Q}\\s+in\\s+\\2\\[1:-1\\]\\s*:\\s*$`));
3400
+ if (!guard)
3401
+ continue;
3402
+ const guardIndent = guard[1].length;
3403
+ const guardedVar = guard[2];
3404
+ let bodyHasTerminator = false;
3405
+ let blockEnd = -1;
3406
+ const maxScan = Math.min(lines.length, i + 26);
3407
+ for (let j = i + 1; j < maxScan; j++) {
3408
+ const line = lines[j];
3409
+ if (line.trim() === '')
3410
+ continue;
3411
+ const indent = line.length - line.trimStart().length;
3412
+ if (indent <= guardIndent) {
3413
+ blockEnd = j - 1;
3414
+ break;
3415
+ }
3416
+ if (terminator.test(line))
3417
+ bodyHasTerminator = true;
3418
+ }
3419
+ if (blockEnd === -1)
3420
+ blockEnd = Math.min(lines.length - 1, i + 25);
3421
+ if (!bodyHasTerminator)
3422
+ continue;
3423
+ const varRe = new RegExp(`\\b${guardedVar}\\b`);
3424
+ for (let l = blockEnd + 2; l <= lines.length; l++) {
3425
+ if (!varRe.test(lines[l - 1]))
3426
+ continue;
3427
+ sanitizers.push({
3428
+ type: 'python_string_literal_guard',
3429
+ method: 'if',
3430
+ line: l,
3431
+ sanitizes: ['code_injection'],
3432
+ });
3433
+ }
3434
+ }
3435
+ return sanitizers;
3436
+ }
3437
+ /**
3438
+ * Detect a default-configured `xml.sax` parser being used to parse untrusted
3439
+ * XML (cognium-dev #4 residual-FP audit):
3440
+ *
3441
+ * parser = xml.sax.make_parser()
3442
+ * doc = xml.dom.minidom.parseString(bar, parser) # NOT XXE
3443
+ *
3444
+ * versus the vulnerable form, which differs by one line:
3445
+ *
3446
+ * parser = xml.sax.make_parser()
3447
+ * parser.setFeature(xml.sax.handler.feature_external_ges, True)
3448
+ * doc = xml.dom.minidom.parseString(bar, parser) # XXE
3449
+ *
3450
+ * Since Python 3.7.1 (bpo-17239) `xml.sax` and `xml.dom.minidom` do not
3451
+ * process external entities unless `feature_external_ges` is switched on or a
3452
+ * custom `EntityResolver` is installed. Without one of those, untrusted XML
3453
+ * cannot pull in an external entity, so CWE-611 does not apply. Residual DoS
3454
+ * risk (billion laughs / quadratic blowup) is a different weakness, CWE-776,
3455
+ * and is unaffected by this gate.
3456
+ *
3457
+ * The check is per-parser-variable and file-scoped: if the parser is enabled
3458
+ * anywhere, reassigned, or passed somewhere this scan cannot follow, no
3459
+ * sanitizer is emitted and the sink stands. In the OWASP BenchmarkPython
3460
+ * corpus this separates all 8 XXE true positives (which set the feature) from
3461
+ * the 3 false positives (which do not) — both groups call the same API.
3462
+ */
3463
+ function findPythonDefaultSaxParserXxeSanitizers(code) {
3464
+ const sanitizers = [];
3465
+ const lines = code.split('\n');
3466
+ const parserVars = new Set();
3467
+ for (const line of lines) {
3468
+ const made = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:xml\.)?sax\.make_parser\s*\(/);
3469
+ if (made)
3470
+ parserVars.add(made[1]);
3471
+ }
3472
+ if (parserVars.size === 0)
3473
+ return sanitizers;
3474
+ // Drop any parser that has entity processing switched on, gets a custom
3475
+ // resolver, or is rebound to something else.
3476
+ for (const line of lines) {
3477
+ for (const parser of [...parserVars]) {
3478
+ const enabled = new RegExp(`\\b${parser}\\.setFeature\\s*\\([^,]*external_(?:ges|pes)[^,]*,\\s*True\\s*\\)`);
3479
+ const resolver = new RegExp(`\\b${parser}\\.setEntityResolver\\s*\\(`);
3480
+ const rebound = new RegExp(`^\\s*${parser}\\s*=(?!=)`);
3481
+ if (enabled.test(line) || resolver.test(line))
3482
+ parserVars.delete(parser);
3483
+ else if (rebound.test(line) && !/make_parser\s*\(/.test(line))
3484
+ parserVars.delete(parser);
3485
+ }
3486
+ }
3487
+ if (parserVars.size === 0)
3488
+ return sanitizers;
3489
+ // Emit on parse calls that hand the XML to one of those parsers.
3490
+ for (let i = 0; i < lines.length; i++) {
3491
+ const line = lines[i];
3492
+ const parseCall = /\b(?:minidom\.)?parseString\s*\(|\b(?:minidom\.)?parse\s*\(|\bsax\.parse(?:String)?\s*\(/;
3493
+ if (!parseCall.test(line))
3494
+ continue;
3495
+ const usesDefaultParser = [...parserVars].some(p => new RegExp(`\\b${p}\\b`).test(line));
3496
+ if (!usesDefaultParser)
3497
+ continue;
3498
+ sanitizers.push({
3499
+ type: 'python_default_sax_parser',
3500
+ method: 'make_parser',
3501
+ line: i + 1,
3502
+ sanitizes: ['xxe'],
3503
+ });
3504
+ }
3505
+ return sanitizers;
3506
+ }
3507
+ /**
3508
+ * Detect the parse-and-allowlist open-redirect guard (cognium-dev #4
3509
+ * residual-FP audit):
3510
+ *
3511
+ * url = urllib.parse.urlparse(bar)
3512
+ * if url.netloc not in ['google.com'] or url.scheme != 'https':
3513
+ * return "Invalid URL."
3514
+ * return flask.redirect(bar)
3515
+ *
3516
+ * Past the guard the target's host is one of a fixed set, which is the
3517
+ * canonical CWE-601 mitigation — an attacker cannot steer the redirect
3518
+ * off-site. Requires the reject polarity with a terminator, a `netloc`
3519
+ * membership test against a literal collection, and a `urlparse` whose
3520
+ * argument is the value later redirected to.
3521
+ *
3522
+ * Only `open_redirect` is sanitized: knowing the host is allow-listed says
3523
+ * nothing about the path or query, so SSRF and injection sinks are untouched.
3524
+ */
3525
+ function findPythonUrlAllowlistRedirectSanitizers(code) {
3526
+ const sanitizers = [];
3527
+ const lines = code.split('\n');
3528
+ // `url = urlparse(bar)` → parsed name maps back to the raw URL variable.
3529
+ const parsedFrom = new Map();
3530
+ for (const line of lines) {
3531
+ 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*\)/);
3532
+ if (m)
3533
+ parsedFrom.set(m[1], m[2]);
3534
+ }
3535
+ if (parsedFrom.size === 0)
3536
+ return sanitizers;
3537
+ const terminator = /\b(return|raise|abort\s*\(|sys\.exit\s*\()/;
3538
+ for (let i = 0; i < lines.length; i++) {
3539
+ const guard = lines[i].match(/^(\s*)if\s+([A-Za-z_][A-Za-z0-9_]*)\.netloc\s+not\s+in\s+[[({]/);
3540
+ if (!guard)
3541
+ continue;
3542
+ const guardIndent = guard[1].length;
3543
+ const rawVar = parsedFrom.get(guard[2]);
3544
+ if (!rawVar)
3545
+ continue;
3546
+ let bodyHasTerminator = false;
3547
+ let blockEnd = -1;
3548
+ const maxScan = Math.min(lines.length, i + 26);
3549
+ for (let j = i + 1; j < maxScan; j++) {
3550
+ const line = lines[j];
3551
+ if (line.trim() === '')
3552
+ continue;
3553
+ const indent = line.length - line.trimStart().length;
3554
+ if (indent <= guardIndent) {
3555
+ blockEnd = j - 1;
3556
+ break;
3557
+ }
3558
+ if (terminator.test(line))
3559
+ bodyHasTerminator = true;
3560
+ }
3561
+ if (blockEnd === -1)
3562
+ blockEnd = Math.min(lines.length - 1, i + 25);
3563
+ if (!bodyHasTerminator)
3564
+ continue;
3565
+ const varRe = new RegExp(`\\b${rawVar}\\b`);
3566
+ for (let l = blockEnd + 2; l <= lines.length; l++) {
3567
+ if (!varRe.test(lines[l - 1]))
3568
+ continue;
3569
+ sanitizers.push({
3570
+ type: 'python_url_allowlist_guard',
3571
+ method: 'if',
3572
+ line: l,
3573
+ sanitizes: ['open_redirect'],
3574
+ });
3575
+ }
3576
+ }
3577
+ return sanitizers;
3578
+ }
3058
3579
  /**
3059
3580
  * Detect Python `defusedxml` import-alias usage as xxe sanitizer
3060
3581
  * (cognium-dev #216 Sprint 74 Pattern B).