xploitscan 1.1.10 → 1.2.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 +24 -0
- package/dist/{api-62XVZKRV.js → api-4UFZFIDO.js} +2 -2
- package/dist/{chunk-GPJLFSUZ.js → chunk-KE2IZNH6.js} +110 -1
- package/dist/{chunk-GPJLFSUZ.js.map → chunk-KE2IZNH6.js.map} +1 -1
- package/dist/index.js +117 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- /package/dist/{api-62XVZKRV.js.map → api-4UFZFIDO.js.map} +0 -0
package/README.md
CHANGED
|
@@ -131,6 +131,30 @@ Create a `.xploitscanrc` file in your project root:
|
|
|
131
131
|
}
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
### `.xploitscanignore`
|
|
135
|
+
|
|
136
|
+
A `.gitignore`-style file in your project root. Plain lines exclude whole
|
|
137
|
+
files from scanning (negate with `!`). A line may also carry a trailing
|
|
138
|
+
rule-ID list to suppress **only those rules** on matching paths while still
|
|
139
|
+
scanning the files for everything else:
|
|
140
|
+
|
|
141
|
+
```gitignore
|
|
142
|
+
# Don't scan generated code at all
|
|
143
|
+
generated/**
|
|
144
|
+
|
|
145
|
+
# Allow log-injection (VC044) in internal cron scripts only
|
|
146
|
+
scripts/cron/** VC044
|
|
147
|
+
|
|
148
|
+
# Silence two rules across all test files
|
|
149
|
+
**/*.test.ts VC031, VC043
|
|
150
|
+
|
|
151
|
+
# Suppress every rule on a legacy tree (the `scanner` wildcard)
|
|
152
|
+
legacy/** scanner
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For a single reviewed-and-accepted finding, prefer an inline
|
|
156
|
+
`// VC<id>-OK: <reason>` comment instead of a file- or path-wide rule.
|
|
157
|
+
|
|
134
158
|
## Web Dashboard
|
|
135
159
|
|
|
136
160
|
Scan via the web at [xploitscan.com](https://xploitscan.com):
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
storeToken,
|
|
15
15
|
syncUser,
|
|
16
16
|
uploadScanResults
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-KE2IZNH6.js";
|
|
18
18
|
export {
|
|
19
19
|
checkUsage,
|
|
20
20
|
clearProRulesCache,
|
|
@@ -29,4 +29,4 @@ export {
|
|
|
29
29
|
syncUser,
|
|
30
30
|
uploadScanResults
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=api-
|
|
32
|
+
//# sourceMappingURL=api-4UFZFIDO.js.map
|
|
@@ -43509,8 +43509,69 @@ var TAINTED_REQUEST_OBJECTS = /* @__PURE__ */ new Set([
|
|
|
43509
43509
|
"event"
|
|
43510
43510
|
// Lambda
|
|
43511
43511
|
]);
|
|
43512
|
+
var FS_READ_METHODS = /* @__PURE__ */ new Set(["readFile", "readFileSync"]);
|
|
43513
|
+
var PARSE_OBJECTS = /* @__PURE__ */ new Set([
|
|
43514
|
+
"JSON",
|
|
43515
|
+
"csv",
|
|
43516
|
+
"papa",
|
|
43517
|
+
"Papa",
|
|
43518
|
+
"yaml",
|
|
43519
|
+
"YAML",
|
|
43520
|
+
"toml",
|
|
43521
|
+
"qs",
|
|
43522
|
+
"querystring"
|
|
43523
|
+
]);
|
|
43524
|
+
var PARSE_BARE = /* @__PURE__ */ new Set(["parse", "parseSync"]);
|
|
43525
|
+
var ARRAY_ELEMENT_METHODS = /* @__PURE__ */ new Set([
|
|
43526
|
+
"map",
|
|
43527
|
+
"filter",
|
|
43528
|
+
"forEach",
|
|
43529
|
+
"find",
|
|
43530
|
+
"findIndex",
|
|
43531
|
+
"flatMap",
|
|
43532
|
+
"some",
|
|
43533
|
+
"every"
|
|
43534
|
+
]);
|
|
43535
|
+
var ARRAY_REDUCE_METHODS = /* @__PURE__ */ new Set(["reduce", "reduceRight"]);
|
|
43536
|
+
function callPreservesTaint(node, rec) {
|
|
43537
|
+
const callee = node.callee;
|
|
43538
|
+
const anyArgTainted = () => node.arguments.some((a) => a.type !== "SpreadElement" && rec(a));
|
|
43539
|
+
if (callee.type === "MemberExpression" && callee.property.type === "Identifier") {
|
|
43540
|
+
const pname = callee.property.name;
|
|
43541
|
+
if (FS_READ_METHODS.has(pname)) return anyArgTainted();
|
|
43542
|
+
if (pname === "parse" || pname === "parseSync") {
|
|
43543
|
+
const obj = callee.object;
|
|
43544
|
+
if (obj.type === "Identifier" && PARSE_OBJECTS.has(obj.name)) return anyArgTainted();
|
|
43545
|
+
}
|
|
43546
|
+
}
|
|
43547
|
+
if (callee.type === "Identifier") {
|
|
43548
|
+
if (FS_READ_METHODS.has(callee.name)) return anyArgTainted();
|
|
43549
|
+
if (PARSE_BARE.has(callee.name)) return anyArgTainted();
|
|
43550
|
+
}
|
|
43551
|
+
return false;
|
|
43552
|
+
}
|
|
43512
43553
|
function buildTaintMap(parsed) {
|
|
43513
43554
|
const tainted = /* @__PURE__ */ new Set();
|
|
43555
|
+
function markPatternTainted(target) {
|
|
43556
|
+
if (target.type === "Identifier") {
|
|
43557
|
+
tainted.add(target.name);
|
|
43558
|
+
} else if (target.type === "ObjectPattern") {
|
|
43559
|
+
for (const prop of target.properties) {
|
|
43560
|
+
if (prop.type === "ObjectProperty" && prop.value.type === "Identifier") {
|
|
43561
|
+
tainted.add(prop.value.name);
|
|
43562
|
+
} else if (prop.type === "RestElement" && prop.argument.type === "Identifier") {
|
|
43563
|
+
tainted.add(prop.argument.name);
|
|
43564
|
+
}
|
|
43565
|
+
}
|
|
43566
|
+
} else if (target.type === "ArrayPattern") {
|
|
43567
|
+
for (const el of target.elements) {
|
|
43568
|
+
if (el && el.type === "Identifier") tainted.add(el.name);
|
|
43569
|
+
else if (el && el.type === "RestElement" && el.argument.type === "Identifier") {
|
|
43570
|
+
tainted.add(el.argument.name);
|
|
43571
|
+
}
|
|
43572
|
+
}
|
|
43573
|
+
}
|
|
43574
|
+
}
|
|
43514
43575
|
function reachesRequestIdent(node) {
|
|
43515
43576
|
if (!node) return false;
|
|
43516
43577
|
if (node.type === "Identifier") return TAINTED_REQUEST_OBJECTS.has(node.name);
|
|
@@ -43581,6 +43642,7 @@ function buildTaintMap(parsed) {
|
|
|
43581
43642
|
}
|
|
43582
43643
|
if (node.type === "CallExpression") {
|
|
43583
43644
|
if (nodeIsTaintedSource(node)) return true;
|
|
43645
|
+
if (callPreservesTaint(node, exprIsTainted)) return true;
|
|
43584
43646
|
if (node.callee.type === "MemberExpression") {
|
|
43585
43647
|
if (exprIsTainted(node.callee.object)) return true;
|
|
43586
43648
|
const obj = node.callee.object;
|
|
@@ -43601,6 +43663,16 @@ function buildTaintMap(parsed) {
|
|
|
43601
43663
|
if (node.type === "AwaitExpression") {
|
|
43602
43664
|
return exprIsTainted(node.argument);
|
|
43603
43665
|
}
|
|
43666
|
+
if (node.type === "ArrayExpression") {
|
|
43667
|
+
return node.elements.some(
|
|
43668
|
+
(el) => el != null && el.type === "SpreadElement" && exprIsTainted(el.argument)
|
|
43669
|
+
);
|
|
43670
|
+
}
|
|
43671
|
+
if (node.type === "ObjectExpression") {
|
|
43672
|
+
return node.properties.some(
|
|
43673
|
+
(p) => p.type === "SpreadElement" && exprIsTainted(p.argument)
|
|
43674
|
+
);
|
|
43675
|
+
}
|
|
43604
43676
|
return false;
|
|
43605
43677
|
}
|
|
43606
43678
|
traverse(parsed.ast, {
|
|
@@ -43635,6 +43707,32 @@ function buildTaintMap(parsed) {
|
|
|
43635
43707
|
if (exprIsTainted(node.right)) {
|
|
43636
43708
|
tainted.add(node.left.name);
|
|
43637
43709
|
}
|
|
43710
|
+
},
|
|
43711
|
+
// for (const row of <tainted>) → row tainted (and destructured names).
|
|
43712
|
+
ForOfStatement(path) {
|
|
43713
|
+
const node = path.node;
|
|
43714
|
+
if (node.type !== "ForOfStatement") return;
|
|
43715
|
+
if (!exprIsTainted(node.right)) return;
|
|
43716
|
+
const decl = node.left;
|
|
43717
|
+
const target = decl.type === "VariableDeclaration" && decl.declarations[0] ? decl.declarations[0].id : decl;
|
|
43718
|
+
markPatternTainted(target);
|
|
43719
|
+
},
|
|
43720
|
+
// Array-iteration element taint: <tainted>.map((row) => …) marks `row`
|
|
43721
|
+
// tainted inside the callback. Gated on the receiver already being
|
|
43722
|
+
// tainted, so this never originates taint.
|
|
43723
|
+
CallExpression(path) {
|
|
43724
|
+
const node = path.node;
|
|
43725
|
+
if (node.type !== "CallExpression") return;
|
|
43726
|
+
const callee = node.callee;
|
|
43727
|
+
if (callee.type !== "MemberExpression" || callee.property.type !== "Identifier") return;
|
|
43728
|
+
const method = callee.property.name;
|
|
43729
|
+
const isReduce = ARRAY_REDUCE_METHODS.has(method);
|
|
43730
|
+
if (!ARRAY_ELEMENT_METHODS.has(method) && !isReduce) return;
|
|
43731
|
+
if (!exprIsTainted(callee.object)) return;
|
|
43732
|
+
const cb = node.arguments[0];
|
|
43733
|
+
if (!cb || cb.type !== "ArrowFunctionExpression" && cb.type !== "FunctionExpression") return;
|
|
43734
|
+
const elemParam = cb.params[isReduce ? 1 : 0];
|
|
43735
|
+
if (elemParam) markPatternTainted(elemParam);
|
|
43638
43736
|
}
|
|
43639
43737
|
});
|
|
43640
43738
|
const isTainted = (node) => {
|
|
@@ -43660,6 +43758,7 @@ function buildTaintMap(parsed) {
|
|
|
43660
43758
|
return isTainted(node.object);
|
|
43661
43759
|
}
|
|
43662
43760
|
if (node.type === "CallExpression") {
|
|
43761
|
+
if (callPreservesTaint(node, isTainted)) return true;
|
|
43663
43762
|
if (node.callee.type === "MemberExpression") {
|
|
43664
43763
|
if (isTainted(node.callee.object)) return true;
|
|
43665
43764
|
const obj = node.callee.object;
|
|
@@ -43677,6 +43776,16 @@ function buildTaintMap(parsed) {
|
|
|
43677
43776
|
}
|
|
43678
43777
|
}
|
|
43679
43778
|
}
|
|
43779
|
+
if (node.type === "ArrayExpression") {
|
|
43780
|
+
return node.elements.some(
|
|
43781
|
+
(el) => el != null && el.type === "SpreadElement" && isTainted(el.argument)
|
|
43782
|
+
);
|
|
43783
|
+
}
|
|
43784
|
+
if (node.type === "ObjectExpression") {
|
|
43785
|
+
return node.properties.some(
|
|
43786
|
+
(p) => p.type === "SpreadElement" && isTainted(p.argument)
|
|
43787
|
+
);
|
|
43788
|
+
}
|
|
43680
43789
|
return false;
|
|
43681
43790
|
};
|
|
43682
43791
|
return {
|
|
@@ -45896,4 +46005,4 @@ export {
|
|
|
45896
46005
|
loadCachedProRules,
|
|
45897
46006
|
clearProRulesCache
|
|
45898
46007
|
};
|
|
45899
|
-
//# sourceMappingURL=chunk-
|
|
46008
|
+
//# sourceMappingURL=chunk-KE2IZNH6.js.map
|