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 +101 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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;
|