xploitscan-shared-rules 1.8.1 → 1.9.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/README.md CHANGED
@@ -6,7 +6,7 @@ This package is the source of truth for XploitScan's detection logic. It's consu
6
6
 
7
7
  ## What's in here
8
8
 
9
- - **206 security rules** — pattern + AST-based detection for hardcoded secrets, SQL injection, XSS, SSRF, prototype pollution, crypto misuse, deserialization, JWT alg confusion, and more.
9
+ - **210 security rules** — pattern + AST-based detection for hardcoded secrets, SQL injection, XSS, SSRF, prototype pollution, crypto misuse, deserialization, JWT alg confusion, and more.
10
10
  - **Compliance mappings** — each rule tagged with the SOC2, ISO 27001, and OWASP Top 10 controls it covers.
11
11
  - **AI false-positive filter** — optional Claude Haiku integration that re-evaluates findings to suppress benign matches before reporting.
12
12
  - **Entropy-based secret scanner** — detects high-entropy strings that look like credentials but don't match a known service-key pattern.
package/dist/index.cjs CHANGED
@@ -594,8 +594,69 @@ var TAINTED_REQUEST_OBJECTS = /* @__PURE__ */ new Set([
594
594
  "event"
595
595
  // Lambda
596
596
  ]);
597
+ var FS_READ_METHODS = /* @__PURE__ */ new Set(["readFile", "readFileSync"]);
598
+ var PARSE_OBJECTS = /* @__PURE__ */ new Set([
599
+ "JSON",
600
+ "csv",
601
+ "papa",
602
+ "Papa",
603
+ "yaml",
604
+ "YAML",
605
+ "toml",
606
+ "qs",
607
+ "querystring"
608
+ ]);
609
+ var PARSE_BARE = /* @__PURE__ */ new Set(["parse", "parseSync"]);
610
+ var ARRAY_ELEMENT_METHODS = /* @__PURE__ */ new Set([
611
+ "map",
612
+ "filter",
613
+ "forEach",
614
+ "find",
615
+ "findIndex",
616
+ "flatMap",
617
+ "some",
618
+ "every"
619
+ ]);
620
+ var ARRAY_REDUCE_METHODS = /* @__PURE__ */ new Set(["reduce", "reduceRight"]);
621
+ function callPreservesTaint(node, rec) {
622
+ const callee = node.callee;
623
+ const anyArgTainted = () => node.arguments.some((a) => a.type !== "SpreadElement" && rec(a));
624
+ if (callee.type === "MemberExpression" && callee.property.type === "Identifier") {
625
+ const pname = callee.property.name;
626
+ if (FS_READ_METHODS.has(pname)) return anyArgTainted();
627
+ if (pname === "parse" || pname === "parseSync") {
628
+ const obj = callee.object;
629
+ if (obj.type === "Identifier" && PARSE_OBJECTS.has(obj.name)) return anyArgTainted();
630
+ }
631
+ }
632
+ if (callee.type === "Identifier") {
633
+ if (FS_READ_METHODS.has(callee.name)) return anyArgTainted();
634
+ if (PARSE_BARE.has(callee.name)) return anyArgTainted();
635
+ }
636
+ return false;
637
+ }
597
638
  function buildTaintMap(parsed) {
598
639
  const tainted = /* @__PURE__ */ new Set();
640
+ function markPatternTainted(target) {
641
+ if (target.type === "Identifier") {
642
+ tainted.add(target.name);
643
+ } else if (target.type === "ObjectPattern") {
644
+ for (const prop of target.properties) {
645
+ if (prop.type === "ObjectProperty" && prop.value.type === "Identifier") {
646
+ tainted.add(prop.value.name);
647
+ } else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
648
+ tainted.add(prop.argument.name);
649
+ }
650
+ }
651
+ } else if (target.type === "ArrayPattern") {
652
+ for (const el of target.elements) {
653
+ if (el && el.type === "Identifier") tainted.add(el.name);
654
+ else if (el && el.type === "RestElement" && el.argument.type === "Identifier") {
655
+ tainted.add(el.argument.name);
656
+ }
657
+ }
658
+ }
659
+ }
599
660
  function reachesRequestIdent(node) {
600
661
  if (!node) return false;
601
662
  if (node.type === "Identifier") return TAINTED_REQUEST_OBJECTS.has(node.name);
@@ -666,6 +727,7 @@ function buildTaintMap(parsed) {
666
727
  }
667
728
  if (node.type === "CallExpression") {
668
729
  if (nodeIsTaintedSource(node)) return true;
730
+ if (callPreservesTaint(node, exprIsTainted)) return true;
669
731
  if (node.callee.type === "MemberExpression") {
670
732
  if (exprIsTainted(node.callee.object)) return true;
671
733
  const obj = node.callee.object;
@@ -686,6 +748,16 @@ function buildTaintMap(parsed) {
686
748
  if (node.type === "AwaitExpression") {
687
749
  return exprIsTainted(node.argument);
688
750
  }
751
+ if (node.type === "ArrayExpression") {
752
+ return node.elements.some(
753
+ (el) => el != null && el.type === "SpreadElement" && exprIsTainted(el.argument)
754
+ );
755
+ }
756
+ if (node.type === "ObjectExpression") {
757
+ return node.properties.some(
758
+ (p) => p.type === "SpreadElement" && exprIsTainted(p.argument)
759
+ );
760
+ }
689
761
  return false;
690
762
  }
691
763
  traverse(parsed.ast, {
@@ -720,6 +792,32 @@ function buildTaintMap(parsed) {
720
792
  if (exprIsTainted(node.right)) {
721
793
  tainted.add(node.left.name);
722
794
  }
795
+ },
796
+ // for (const row of <tainted>) → row tainted (and destructured names).
797
+ ForOfStatement(path) {
798
+ const node = path.node;
799
+ if (node.type !== "ForOfStatement") return;
800
+ if (!exprIsTainted(node.right)) return;
801
+ const decl = node.left;
802
+ const target = decl.type === "VariableDeclaration" && decl.declarations[0] ? decl.declarations[0].id : decl;
803
+ markPatternTainted(target);
804
+ },
805
+ // Array-iteration element taint: <tainted>.map((row) => …) marks `row`
806
+ // tainted inside the callback. Gated on the receiver already being
807
+ // tainted, so this never originates taint.
808
+ CallExpression(path) {
809
+ const node = path.node;
810
+ if (node.type !== "CallExpression") return;
811
+ const callee = node.callee;
812
+ if (callee.type !== "MemberExpression" || callee.property.type !== "Identifier") return;
813
+ const method = callee.property.name;
814
+ const isReduce = ARRAY_REDUCE_METHODS.has(method);
815
+ if (!ARRAY_ELEMENT_METHODS.has(method) && !isReduce) return;
816
+ if (!exprIsTainted(callee.object)) return;
817
+ const cb = node.arguments[0];
818
+ if (!cb || cb.type !== "ArrowFunctionExpression" && cb.type !== "FunctionExpression") return;
819
+ const elemParam = cb.params[isReduce ? 1 : 0];
820
+ if (elemParam) markPatternTainted(elemParam);
723
821
  }
724
822
  });
725
823
  const isTainted = (node) => {
@@ -745,6 +843,7 @@ function buildTaintMap(parsed) {
745
843
  return isTainted(node.object);
746
844
  }
747
845
  if (node.type === "CallExpression") {
846
+ if (callPreservesTaint(node, isTainted)) return true;
748
847
  if (node.callee.type === "MemberExpression") {
749
848
  if (isTainted(node.callee.object)) return true;
750
849
  const obj = node.callee.object;
@@ -762,6 +861,16 @@ function buildTaintMap(parsed) {
762
861
  }
763
862
  }
764
863
  }
864
+ if (node.type === "ArrayExpression") {
865
+ return node.elements.some(
866
+ (el) => el != null && el.type === "SpreadElement" && isTainted(el.argument)
867
+ );
868
+ }
869
+ if (node.type === "ObjectExpression") {
870
+ return node.properties.some(
871
+ (p) => p.type === "SpreadElement" && isTainted(p.argument)
872
+ );
873
+ }
765
874
  return false;
766
875
  };
767
876
  return {