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.cjs CHANGED
@@ -635,6 +635,77 @@ function callPreservesTaint(node, rec) {
635
635
  }
636
636
  return false;
637
637
  }
638
+ function isParamDerived(n, p) {
639
+ if (!n) return false;
640
+ if (n.type === "Identifier") return n.name === p;
641
+ if (n.type === "MemberExpression") return isParamDerived(n.object, p);
642
+ return false;
643
+ }
644
+ function formattedFlow(n, p) {
645
+ if (!n) return false;
646
+ switch (n.type) {
647
+ case "TemplateLiteral":
648
+ return n.expressions.some(
649
+ (e) => isParamDerived(e, p) || formattedFlow(e, p)
650
+ );
651
+ case "BinaryExpression":
652
+ if (n.operator !== "+") return false;
653
+ return isParamDerived(n.left, p) || isParamDerived(n.right, p) || formattedFlow(n.left, p) || formattedFlow(n.right, p);
654
+ case "ObjectExpression":
655
+ return n.properties.some(
656
+ (pr) => pr.type === "SpreadElement" && isParamDerived(pr.argument, p)
657
+ );
658
+ case "ArrayExpression":
659
+ return n.elements.some(
660
+ (el) => el != null && el.type === "SpreadElement" && isParamDerived(el.argument, p)
661
+ );
662
+ case "ConditionalExpression":
663
+ return formattedFlow(n.consequent, p) || formattedFlow(n.alternate, p);
664
+ case "AwaitExpression":
665
+ return formattedFlow(n.argument, p);
666
+ default:
667
+ return false;
668
+ }
669
+ }
670
+ function collectReturnArgs(fn) {
671
+ const out = [];
672
+ if (fn.type === "ArrowFunctionExpression" && fn.body.type !== "BlockStatement") {
673
+ out.push(fn.body);
674
+ return out;
675
+ }
676
+ const visit = (n) => {
677
+ if (!n || typeof n !== "object") return;
678
+ const node = n;
679
+ if (typeof node.type !== "string") return;
680
+ if (node.type === "ReturnStatement") {
681
+ const arg = node.argument;
682
+ if (arg) out.push(arg);
683
+ return;
684
+ }
685
+ if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
686
+ return;
687
+ }
688
+ for (const key of Object.keys(node)) {
689
+ const v = node[key];
690
+ if (Array.isArray(v)) v.forEach(visit);
691
+ else visit(v);
692
+ }
693
+ };
694
+ visit(fn.body);
695
+ return out;
696
+ }
697
+ function computePassthrough(fn) {
698
+ const params = fn.params ?? [];
699
+ const returns = collectReturnArgs(fn);
700
+ const out = /* @__PURE__ */ new Set();
701
+ for (let i = 0; i < params.length; i++) {
702
+ const param = params[i];
703
+ if (param.type !== "Identifier") continue;
704
+ const name = param.name;
705
+ if (returns.some((r) => formattedFlow(r, name))) out.add(i);
706
+ }
707
+ return out;
708
+ }
638
709
  function buildTaintMap(parsed) {
639
710
  const tainted = /* @__PURE__ */ new Set();
640
711
  function markPatternTainted(target) {
@@ -728,6 +799,7 @@ function buildTaintMap(parsed) {
728
799
  if (node.type === "CallExpression") {
729
800
  if (nodeIsTaintedSource(node)) return true;
730
801
  if (callPreservesTaint(node, exprIsTainted)) return true;
802
+ if (localCallPropagates(node, exprIsTainted)) return true;
731
803
  if (node.callee.type === "MemberExpression") {
732
804
  if (exprIsTainted(node.callee.object)) return true;
733
805
  const obj = node.callee.object;
@@ -760,6 +832,34 @@ function buildTaintMap(parsed) {
760
832
  }
761
833
  return false;
762
834
  }
835
+ const fnPassthrough = /* @__PURE__ */ new Map();
836
+ const recordFn = (name, fn) => {
837
+ const pt = computePassthrough(fn);
838
+ if (pt.size > 0) fnPassthrough.set(name, pt);
839
+ };
840
+ traverse(parsed.ast, {
841
+ FunctionDeclaration(path) {
842
+ const n = path.node;
843
+ if (n.type === "FunctionDeclaration" && n.id && n.id.type === "Identifier") {
844
+ recordFn(n.id.name, n);
845
+ }
846
+ },
847
+ VariableDeclarator(path) {
848
+ const n = path.node;
849
+ if (n.type !== "VariableDeclarator" || n.id.type !== "Identifier" || !n.init) return;
850
+ if (n.init.type === "ArrowFunctionExpression" || n.init.type === "FunctionExpression") {
851
+ recordFn(n.id.name, n.init);
852
+ }
853
+ }
854
+ });
855
+ function localCallPropagates(node, rec) {
856
+ if (node.callee.type !== "Identifier") return false;
857
+ const pt = fnPassthrough.get(node.callee.name);
858
+ if (!pt) return false;
859
+ return node.arguments.some(
860
+ (a, i) => pt.has(i) && a.type !== "SpreadElement" && rec(a)
861
+ );
862
+ }
763
863
  traverse(parsed.ast, {
764
864
  VariableDeclarator(path) {
765
865
  const node = path.node;
@@ -844,6 +944,7 @@ function buildTaintMap(parsed) {
844
944
  }
845
945
  if (node.type === "CallExpression") {
846
946
  if (callPreservesTaint(node, isTainted)) return true;
947
+ if (localCallPropagates(node, isTainted)) return true;
847
948
  if (node.callee.type === "MemberExpression") {
848
949
  if (isTainted(node.callee.object)) return true;
849
950
  const obj = node.callee.object;