xploitscan-shared-rules 1.8.0 → 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 +1 -1
- package/dist/index.cjs +118 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +118 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
- **
|
|
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 {
|
|
@@ -3954,9 +4063,9 @@ var xxeVulnerability = {
|
|
|
3954
4063
|
));
|
|
3955
4064
|
}
|
|
3956
4065
|
}
|
|
3957
|
-
if (!/parseXml\s*\(/.test(content)) return matches;
|
|
4066
|
+
if (!/parseXml\s*\(/.test(content)) return filterSilenced(matches, content, "VC081");
|
|
3958
4067
|
const ctx = tryParse(content, filePath);
|
|
3959
|
-
if (!ctx) return matches;
|
|
4068
|
+
if (!ctx) return filterSilenced(matches, content, "VC081");
|
|
3960
4069
|
visitCalls(
|
|
3961
4070
|
ctx.parsed,
|
|
3962
4071
|
(callee) => isCalleeNamed(callee, "parseXml") || isCalleeNamed(callee, "parseXML"),
|
|
@@ -3980,7 +4089,7 @@ var xxeVulnerability = {
|
|
|
3980
4089
|
);
|
|
3981
4090
|
}
|
|
3982
4091
|
);
|
|
3983
|
-
return matches;
|
|
4092
|
+
return filterSilenced(matches, content, "VC081");
|
|
3984
4093
|
}
|
|
3985
4094
|
};
|
|
3986
4095
|
var ssti = {
|
|
@@ -4009,7 +4118,7 @@ var ssti = {
|
|
|
4009
4118
|
));
|
|
4010
4119
|
}
|
|
4011
4120
|
if (!/(?:\.compile|\.render|renderString|render_template_string)\s*\(/.test(content)) {
|
|
4012
|
-
return matches;
|
|
4121
|
+
return filterSilenced(matches, content, "VC082");
|
|
4013
4122
|
}
|
|
4014
4123
|
const ctx = tryParse(content, filePath);
|
|
4015
4124
|
if (!ctx) return matches;
|
|
@@ -4048,7 +4157,7 @@ var ssti = {
|
|
|
4048
4157
|
);
|
|
4049
4158
|
}
|
|
4050
4159
|
);
|
|
4051
|
-
return matches;
|
|
4160
|
+
return filterSilenced(matches, content, "VC082");
|
|
4052
4161
|
}
|
|
4053
4162
|
};
|
|
4054
4163
|
var javaDeserialization = {
|
|
@@ -4369,7 +4478,7 @@ var commandInjection = {
|
|
|
4369
4478
|
matches.push(m);
|
|
4370
4479
|
}
|
|
4371
4480
|
}
|
|
4372
|
-
return matches;
|
|
4481
|
+
return filterSilenced(matches, content, "VC094");
|
|
4373
4482
|
}
|
|
4374
4483
|
};
|
|
4375
4484
|
var corsLocalhost = {
|
|
@@ -7064,7 +7173,7 @@ var llmPromptInjection = {
|
|
|
7064
7173
|
});
|
|
7065
7174
|
}
|
|
7066
7175
|
}
|
|
7067
|
-
return findings;
|
|
7176
|
+
return filterSilenced(findings, content, "VC198");
|
|
7068
7177
|
}
|
|
7069
7178
|
};
|
|
7070
7179
|
var llmSystemPromptInjection = {
|
|
@@ -7101,7 +7210,7 @@ var llmSystemPromptInjection = {
|
|
|
7101
7210
|
});
|
|
7102
7211
|
}
|
|
7103
7212
|
}
|
|
7104
|
-
return findings;
|
|
7213
|
+
return filterSilenced(findings, content, "VC199");
|
|
7105
7214
|
}
|
|
7106
7215
|
};
|
|
7107
7216
|
var llmOutputAsHTML = {
|
|
@@ -7145,7 +7254,7 @@ var llmOutputAsHTML = {
|
|
|
7145
7254
|
});
|
|
7146
7255
|
}
|
|
7147
7256
|
}
|
|
7148
|
-
return findings;
|
|
7257
|
+
return filterSilenced(findings, content, "VC200");
|
|
7149
7258
|
}
|
|
7150
7259
|
};
|
|
7151
7260
|
var vectorStoreQueryNoUserFilter = {
|