xploitscan-shared-rules 1.9.0 → 1.10.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/index.js CHANGED
@@ -370,6 +370,77 @@ function callPreservesTaint(node, rec) {
370
370
  }
371
371
  return false;
372
372
  }
373
+ function isParamDerived(n, p) {
374
+ if (!n) return false;
375
+ if (n.type === "Identifier") return n.name === p;
376
+ if (n.type === "MemberExpression") return isParamDerived(n.object, p);
377
+ return false;
378
+ }
379
+ function formattedFlow(n, p) {
380
+ if (!n) return false;
381
+ switch (n.type) {
382
+ case "TemplateLiteral":
383
+ return n.expressions.some(
384
+ (e) => isParamDerived(e, p) || formattedFlow(e, p)
385
+ );
386
+ case "BinaryExpression":
387
+ if (n.operator !== "+") return false;
388
+ return isParamDerived(n.left, p) || isParamDerived(n.right, p) || formattedFlow(n.left, p) || formattedFlow(n.right, p);
389
+ case "ObjectExpression":
390
+ return n.properties.some(
391
+ (pr) => pr.type === "SpreadElement" && isParamDerived(pr.argument, p)
392
+ );
393
+ case "ArrayExpression":
394
+ return n.elements.some(
395
+ (el) => el != null && el.type === "SpreadElement" && isParamDerived(el.argument, p)
396
+ );
397
+ case "ConditionalExpression":
398
+ return formattedFlow(n.consequent, p) || formattedFlow(n.alternate, p);
399
+ case "AwaitExpression":
400
+ return formattedFlow(n.argument, p);
401
+ default:
402
+ return false;
403
+ }
404
+ }
405
+ function collectReturnArgs(fn) {
406
+ const out = [];
407
+ if (fn.type === "ArrowFunctionExpression" && fn.body.type !== "BlockStatement") {
408
+ out.push(fn.body);
409
+ return out;
410
+ }
411
+ const visit = (n) => {
412
+ if (!n || typeof n !== "object") return;
413
+ const node = n;
414
+ if (typeof node.type !== "string") return;
415
+ if (node.type === "ReturnStatement") {
416
+ const arg = node.argument;
417
+ if (arg) out.push(arg);
418
+ return;
419
+ }
420
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
421
+ return;
422
+ }
423
+ for (const key of Object.keys(node)) {
424
+ const v = node[key];
425
+ if (Array.isArray(v)) v.forEach(visit);
426
+ else visit(v);
427
+ }
428
+ };
429
+ visit(fn.body);
430
+ return out;
431
+ }
432
+ function computePassthrough(fn) {
433
+ const params = fn.params ?? [];
434
+ const returns = collectReturnArgs(fn);
435
+ const out = /* @__PURE__ */ new Set();
436
+ for (let i = 0; i < params.length; i++) {
437
+ const param = params[i];
438
+ if (param.type !== "Identifier") continue;
439
+ const name = param.name;
440
+ if (returns.some((r) => formattedFlow(r, name))) out.add(i);
441
+ }
442
+ return out;
443
+ }
373
444
  function buildTaintMap(parsed) {
374
445
  const tainted = /* @__PURE__ */ new Set();
375
446
  function markPatternTainted(target) {
@@ -463,6 +534,7 @@ function buildTaintMap(parsed) {
463
534
  if (node.type === "CallExpression") {
464
535
  if (nodeIsTaintedSource(node)) return true;
465
536
  if (callPreservesTaint(node, exprIsTainted)) return true;
537
+ if (localCallPropagates(node, exprIsTainted)) return true;
466
538
  if (node.callee.type === "MemberExpression") {
467
539
  if (exprIsTainted(node.callee.object)) return true;
468
540
  const obj = node.callee.object;
@@ -495,6 +567,34 @@ function buildTaintMap(parsed) {
495
567
  }
496
568
  return false;
497
569
  }
570
+ const fnPassthrough = /* @__PURE__ */ new Map();
571
+ const recordFn = (name, fn) => {
572
+ const pt = computePassthrough(fn);
573
+ if (pt.size > 0) fnPassthrough.set(name, pt);
574
+ };
575
+ traverse(parsed.ast, {
576
+ FunctionDeclaration(path) {
577
+ const n = path.node;
578
+ if (n.type === "FunctionDeclaration" && n.id && n.id.type === "Identifier") {
579
+ recordFn(n.id.name, n);
580
+ }
581
+ },
582
+ VariableDeclarator(path) {
583
+ const n = path.node;
584
+ if (n.type !== "VariableDeclarator" || n.id.type !== "Identifier" || !n.init) return;
585
+ if (n.init.type === "ArrowFunctionExpression" || n.init.type === "FunctionExpression") {
586
+ recordFn(n.id.name, n.init);
587
+ }
588
+ }
589
+ });
590
+ function localCallPropagates(node, rec) {
591
+ if (node.callee.type !== "Identifier") return false;
592
+ const pt = fnPassthrough.get(node.callee.name);
593
+ if (!pt) return false;
594
+ return node.arguments.some(
595
+ (a, i) => pt.has(i) && a.type !== "SpreadElement" && rec(a)
596
+ );
597
+ }
498
598
  traverse(parsed.ast, {
499
599
  VariableDeclarator(path) {
500
600
  const node = path.node;
@@ -579,6 +679,7 @@ function buildTaintMap(parsed) {
579
679
  }
580
680
  if (node.type === "CallExpression") {
581
681
  if (callPreservesTaint(node, isTainted)) return true;
682
+ if (localCallPropagates(node, isTainted)) return true;
582
683
  if (node.callee.type === "MemberExpression") {
583
684
  if (isTainted(node.callee.object)) return true;
584
685
  const obj = node.callee.object;