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.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;
|