xploitscan 1.1.2 → 1.1.4
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.js +95 -31
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
uploadScanResults
|
|
19
19
|
} from "./chunk-WGENFXRJ.js";
|
|
20
20
|
|
|
21
|
-
// ../../node_modules/.pnpm/@babel+parser@7.29.
|
|
21
|
+
// ../../node_modules/.pnpm/@babel+parser@7.29.3/node_modules/@babel/parser/lib/index.js
|
|
22
22
|
var require_lib = __commonJS({
|
|
23
|
-
"../../node_modules/.pnpm/@babel+parser@7.29.
|
|
23
|
+
"../../node_modules/.pnpm/@babel+parser@7.29.3/node_modules/@babel/parser/lib/index.js"(exports) {
|
|
24
24
|
"use strict";
|
|
25
25
|
Object.defineProperty(exports, "__esModule", {
|
|
26
26
|
value: true
|
|
@@ -1971,7 +1971,7 @@ var require_lib = __commonJS({
|
|
|
1971
1971
|
}
|
|
1972
1972
|
flowParseDeclareVariable(node) {
|
|
1973
1973
|
this.next();
|
|
1974
|
-
node.id = this.flowParseTypeAnnotatableIdentifier(
|
|
1974
|
+
node.id = this.flowParseTypeAnnotatableIdentifier();
|
|
1975
1975
|
this.scope.declareName(node.id.name, 5, node.id.loc.start);
|
|
1976
1976
|
this.semicolon();
|
|
1977
1977
|
return this.finishNode(node, "DeclareVariable");
|
|
@@ -2145,9 +2145,14 @@ var require_lib = __commonJS({
|
|
|
2145
2145
|
reservedType: word
|
|
2146
2146
|
});
|
|
2147
2147
|
}
|
|
2148
|
-
|
|
2148
|
+
flowParseRestrictedIdentifierName(liberal, declaration) {
|
|
2149
2149
|
this.checkReservedType(this.state.value, this.state.startLoc, declaration);
|
|
2150
|
-
return this.
|
|
2150
|
+
return this.parseIdentifierName(liberal);
|
|
2151
|
+
}
|
|
2152
|
+
flowParseRestrictedIdentifier(liberal, declaration) {
|
|
2153
|
+
const node = this.startNode();
|
|
2154
|
+
const name = this.flowParseRestrictedIdentifierName(liberal, declaration);
|
|
2155
|
+
return this.createIdentifier(node, name);
|
|
2151
2156
|
}
|
|
2152
2157
|
flowParseTypeAlias(node) {
|
|
2153
2158
|
node.id = this.flowParseRestrictedIdentifier(false, true);
|
|
@@ -2181,14 +2186,21 @@ var require_lib = __commonJS({
|
|
|
2181
2186
|
this.semicolon();
|
|
2182
2187
|
return this.finishNode(node, "OpaqueType");
|
|
2183
2188
|
}
|
|
2189
|
+
flowParseTypeParameterBound() {
|
|
2190
|
+
if (this.match(14) || this.isContextual(81)) {
|
|
2191
|
+
const node = this.startNode();
|
|
2192
|
+
this.next();
|
|
2193
|
+
node.typeAnnotation = this.flowParseType();
|
|
2194
|
+
return this.finishNode(node, "TypeAnnotation");
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2184
2197
|
flowParseTypeParameter(requireDefault = false) {
|
|
2185
2198
|
const nodeStartLoc = this.state.startLoc;
|
|
2186
2199
|
const node = this.startNode();
|
|
2187
2200
|
const variance = this.flowParseVariance();
|
|
2188
|
-
|
|
2189
|
-
node.name = ident.name;
|
|
2201
|
+
node.name = this.flowParseRestrictedIdentifierName();
|
|
2190
2202
|
node.variance = variance;
|
|
2191
|
-
node.bound =
|
|
2203
|
+
node.bound = this.flowParseTypeParameterBound();
|
|
2192
2204
|
if (this.match(29)) {
|
|
2193
2205
|
this.eat(29);
|
|
2194
2206
|
node.default = this.flowParseType();
|
|
@@ -2883,13 +2895,13 @@ var require_lib = __commonJS({
|
|
|
2883
2895
|
node.typeAnnotation = this.flowParseTypeInitialiser();
|
|
2884
2896
|
return this.finishNode(node, "TypeAnnotation");
|
|
2885
2897
|
}
|
|
2886
|
-
flowParseTypeAnnotatableIdentifier(
|
|
2887
|
-
const
|
|
2898
|
+
flowParseTypeAnnotatableIdentifier() {
|
|
2899
|
+
const node = this.startNode();
|
|
2900
|
+
const name = this.parseIdentifierName();
|
|
2888
2901
|
if (this.match(14)) {
|
|
2889
|
-
|
|
2890
|
-
this.resetEndLocation(ident);
|
|
2902
|
+
node.typeAnnotation = this.flowParseTypeAnnotation();
|
|
2891
2903
|
}
|
|
2892
|
-
return
|
|
2904
|
+
return this.createIdentifier(node, name);
|
|
2893
2905
|
}
|
|
2894
2906
|
typeCastToParameter(node) {
|
|
2895
2907
|
node.expression.typeAnnotation = node.typeAnnotation;
|
|
@@ -5106,6 +5118,7 @@ var require_lib = __commonJS({
|
|
|
5106
5118
|
adjustInnerComments(node, node.properties, commentWS);
|
|
5107
5119
|
break;
|
|
5108
5120
|
case "CallExpression":
|
|
5121
|
+
case "NewExpression":
|
|
5109
5122
|
case "OptionalCallExpression":
|
|
5110
5123
|
adjustInnerComments(node, node.arguments, commentWS);
|
|
5111
5124
|
break;
|
|
@@ -5118,6 +5131,7 @@ var require_lib = __commonJS({
|
|
|
5118
5131
|
case "ObjectMethod":
|
|
5119
5132
|
case "ClassMethod":
|
|
5120
5133
|
case "ClassPrivateMethod":
|
|
5134
|
+
case "TSTypeParameterDeclaration":
|
|
5121
5135
|
adjustInnerComments(node, node.params, commentWS);
|
|
5122
5136
|
break;
|
|
5123
5137
|
case "ArrayExpression":
|
|
@@ -5134,6 +5148,9 @@ var require_lib = __commonJS({
|
|
|
5134
5148
|
case "TSEnumBody":
|
|
5135
5149
|
adjustInnerComments(node, node.members, commentWS);
|
|
5136
5150
|
break;
|
|
5151
|
+
case "TSInterfaceBody":
|
|
5152
|
+
adjustInnerComments(node, node.body, commentWS);
|
|
5153
|
+
break;
|
|
5137
5154
|
default: {
|
|
5138
5155
|
if (node.type === "RecordExpression") {
|
|
5139
5156
|
adjustInnerComments(node, node.properties, commentWS);
|
|
@@ -43495,7 +43512,25 @@ var ALWAYS_IGNORE = [
|
|
|
43495
43512
|
"*.map",
|
|
43496
43513
|
"package-lock.json",
|
|
43497
43514
|
"pnpm-lock.yaml",
|
|
43498
|
-
"yarn.lock"
|
|
43515
|
+
"yarn.lock",
|
|
43516
|
+
// Intentionally-vulnerable test corpora. Security tools (XploitScan,
|
|
43517
|
+
// Semgrep, Bearer, Gitleaks, Snyk Code) all maintain directories of
|
|
43518
|
+
// known-bad code samples used to verify that rules fire. Scanning
|
|
43519
|
+
// them produces a flood of "critical" findings on the scanner's own
|
|
43520
|
+
// dogfood, drowning the signal from real product code. Skip by
|
|
43521
|
+
// default. Users who genuinely want to scan a fixture directory can
|
|
43522
|
+
// override via .xploitscanignore (the negation syntax `!test-
|
|
43523
|
+
// fixtures/` un-ignores it).
|
|
43524
|
+
//
|
|
43525
|
+
// We only match these as directory names anywhere in the tree, so a
|
|
43526
|
+
// project whose actual source happens to live under a path called
|
|
43527
|
+
// `fixtures-prod/` is unaffected.
|
|
43528
|
+
"test-fixtures",
|
|
43529
|
+
"test-fixtures/**",
|
|
43530
|
+
"__fixtures__",
|
|
43531
|
+
"__fixtures__/**",
|
|
43532
|
+
"vulnerable-samples",
|
|
43533
|
+
"vulnerable-samples/**"
|
|
43499
43534
|
];
|
|
43500
43535
|
async function collectFiles(directory) {
|
|
43501
43536
|
const ig = ignore.default();
|
|
@@ -45514,7 +45549,21 @@ For each finding, respond ONLY with a JSON array. No other text.
|
|
|
45514
45549
|
Each element: {"index": <number>, "verdict": "real" or "fp", "reason": "<1 sentence>"}`;
|
|
45515
45550
|
var MAX_FINDINGS_PER_BATCH = 15;
|
|
45516
45551
|
var MAX_CONTEXT_LINES = 10;
|
|
45517
|
-
var
|
|
45552
|
+
var DEFAULT_MAX_TOTAL_FINDINGS = 200;
|
|
45553
|
+
var MAX_TOTAL_FINDINGS = (() => {
|
|
45554
|
+
const raw = process.env.XPLOITSCAN_AI_FILTER_MAX;
|
|
45555
|
+
if (!raw) return DEFAULT_MAX_TOTAL_FINDINGS;
|
|
45556
|
+
const n = parseInt(raw, 10);
|
|
45557
|
+
if (!Number.isFinite(n) || n < 1) return DEFAULT_MAX_TOTAL_FINDINGS;
|
|
45558
|
+
return Math.min(n, 1e3);
|
|
45559
|
+
})();
|
|
45560
|
+
var SEVERITY_PRIORITY = {
|
|
45561
|
+
critical: 0,
|
|
45562
|
+
high: 1,
|
|
45563
|
+
medium: 2,
|
|
45564
|
+
low: 3,
|
|
45565
|
+
info: 4
|
|
45566
|
+
};
|
|
45518
45567
|
function getExpandedContext(content, line, contextLines = MAX_CONTEXT_LINES) {
|
|
45519
45568
|
const lines = content.split("\n");
|
|
45520
45569
|
const start = Math.max(0, line - 1 - contextLines);
|
|
@@ -45562,8 +45611,13 @@ async function filterFalsePositives(findings, fileContents) {
|
|
|
45562
45611
|
const empty = { findings, filteredFindings: [], aiReviewed: false, removedCount: 0, totalBefore: findings.length };
|
|
45563
45612
|
if (!process.env.ANTHROPIC_API_KEY) return empty;
|
|
45564
45613
|
if (findings.length === 0) return empty;
|
|
45565
|
-
const
|
|
45566
|
-
|
|
45614
|
+
const prioritized = [...findings].sort((a, b) => {
|
|
45615
|
+
const pa = SEVERITY_PRIORITY[(a.severity || "").toLowerCase()] ?? 5;
|
|
45616
|
+
const pb = SEVERITY_PRIORITY[(b.severity || "").toLowerCase()] ?? 5;
|
|
45617
|
+
return pa - pb;
|
|
45618
|
+
});
|
|
45619
|
+
const toReview = prioritized.slice(0, MAX_TOTAL_FINDINGS);
|
|
45620
|
+
const overflow = prioritized.slice(MAX_TOTAL_FINDINGS);
|
|
45567
45621
|
const totalBefore = findings.length;
|
|
45568
45622
|
const byFile = /* @__PURE__ */ new Map();
|
|
45569
45623
|
for (const f of toReview) {
|
|
@@ -47843,6 +47897,7 @@ import { execFile as execFile3 } from "child_process";
|
|
|
47843
47897
|
import chalk3 from "chalk";
|
|
47844
47898
|
import ora2 from "ora";
|
|
47845
47899
|
var CLERK_PUBLISHABLE_KEY = process.env.CLERK_PUBLISHABLE_KEY ?? "";
|
|
47900
|
+
var WEB_BASE = process.env.XPLOITSCAN_WEB_BASE ?? "https://xploitscan.com";
|
|
47846
47901
|
async function loginCommand() {
|
|
47847
47902
|
const existing = getStoredToken();
|
|
47848
47903
|
if (existing) {
|
|
@@ -47931,19 +47986,28 @@ async function waitForBrowserLogin() {
|
|
|
47931
47986
|
res.end("Missing parameters");
|
|
47932
47987
|
}
|
|
47933
47988
|
} else if (url.pathname === "/login") {
|
|
47934
|
-
const
|
|
47935
|
-
|
|
47936
|
-
|
|
47937
|
-
|
|
47938
|
-
|
|
47939
|
-
|
|
47940
|
-
|
|
47941
|
-
|
|
47942
|
-
|
|
47943
|
-
|
|
47944
|
-
|
|
47945
|
-
|
|
47946
|
-
|
|
47989
|
+
const port = server.address().port;
|
|
47990
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
47991
|
+
const cliLoginUrl = `${WEB_BASE}/auth/cli-login?callback=${encodeURIComponent(callbackUrl)}&state=${encodeURIComponent(expectedState)}`;
|
|
47992
|
+
res.writeHead(302, { Location: cliLoginUrl, "Content-Type": "text/html" });
|
|
47993
|
+
res.end(`<!doctype html>
|
|
47994
|
+
<html>
|
|
47995
|
+
<head>
|
|
47996
|
+
<meta charset="utf-8" />
|
|
47997
|
+
<meta http-equiv="refresh" content="0; url=${cliLoginUrl}" />
|
|
47998
|
+
<title>xploitscan login</title>
|
|
47999
|
+
</head>
|
|
48000
|
+
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0a0a0f; color: #e0e0e0;">
|
|
48001
|
+
<div style="text-align: center;">
|
|
48002
|
+
<h1 style="color: #00d4ff;">xploitscan</h1>
|
|
48003
|
+
<p>Redirecting to login...</p>
|
|
48004
|
+
<p style="color: #888; font-size: 0.85rem;">
|
|
48005
|
+
If not redirected, <a href="${cliLoginUrl}" style="color: #00d4ff;">click here</a>.
|
|
48006
|
+
</p>
|
|
48007
|
+
</div>
|
|
48008
|
+
<script>window.location.replace(${JSON.stringify(cliLoginUrl)});</script>
|
|
48009
|
+
</body>
|
|
48010
|
+
</html>`);
|
|
47947
48011
|
} else {
|
|
47948
48012
|
res.writeHead(302, { Location: "/login" });
|
|
47949
48013
|
res.end();
|
|
@@ -48155,7 +48219,7 @@ async function cursorInstallCommand(opts = {}) {
|
|
|
48155
48219
|
var program = new Command();
|
|
48156
48220
|
program.name("xploitscan").description(
|
|
48157
48221
|
"AI security scanner for vibe-coded apps. Find vulnerabilities before attackers do."
|
|
48158
|
-
).version("1.1.
|
|
48222
|
+
).version("1.1.4");
|
|
48159
48223
|
program.command("scan").description("Scan a directory for security vulnerabilities").argument("[directory]", "Directory to scan", ".").option("--no-ai", "Skip AI-powered analysis").option(
|
|
48160
48224
|
"-f, --format <format>",
|
|
48161
48225
|
"Output format: terminal, json, sarif, splunk-hec, elastic-ecs, datadog-logs",
|