xploitscan 0.4.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 ADDED
@@ -0,0 +1,148 @@
1
+ # xploitscan
2
+
3
+ AI security scanner for vibe-coded apps. Find vulnerabilities before attackers do.
4
+
5
+ Built for solo devs and non-technical founders shipping AI-generated code via Cursor, Lovable, Bolt, Replit, and Claude Code.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ npx xploitscan scan .
11
+ ```
12
+
13
+ That's it. No config needed.
14
+
15
+ ## What It Catches
16
+
17
+ | Rule | Vulnerability | Severity |
18
+ |------|--------------|----------|
19
+ | VC001 | Hardcoded API keys & secrets (AWS, Stripe, OpenAI, Supabase, DB URLs) | Critical |
20
+ | VC002 | .env files with secrets committed to git | High |
21
+ | VC003 | API routes missing authentication | High |
22
+ | VC004 | Supabase service_role key in client code / RLS bypass | Critical |
23
+ | VC005 | Stripe webhooks without signature verification | Critical |
24
+ | VC006 | SQL injection via string interpolation | Critical |
25
+ | VC007 | XSS (dangerouslySetInnerHTML, innerHTML, v-html) | High |
26
+ | VC008 | Server without rate limiting | Medium |
27
+ | VC009 | Wildcard CORS configuration | Medium |
28
+ | VC010 | Client-side only authorization checks | High |
29
+
30
+ Plus **AI-powered contextual analysis** that catches issues static rules miss.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ # Run directly (no install)
36
+ npx xploitscan scan .
37
+
38
+ # Or install globally
39
+ npm install -g xploitscan
40
+ xploitscan scan .
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ```bash
46
+ # Scan current directory
47
+ xploitscan scan .
48
+
49
+ # Scan a specific directory
50
+ xploitscan scan ./my-project
51
+
52
+ # Skip AI analysis (faster, no API key needed)
53
+ xploitscan scan . --no-ai
54
+
55
+ # JSON output (for CI pipelines)
56
+ xploitscan scan . --format json
57
+
58
+ # SARIF output (for GitHub Code Scanning)
59
+ xploitscan scan . --format sarif
60
+
61
+ # Verbose output (show per-scanner results)
62
+ xploitscan scan . -v
63
+ ```
64
+
65
+ ## AI-Powered Analysis
66
+
67
+ Set your Anthropic API key for deeper, contextual vulnerability analysis:
68
+
69
+ ```bash
70
+ export ANTHROPIC_API_KEY=sk-ant-...
71
+ xploitscan scan .
72
+ ```
73
+
74
+ The AI analyzer understands your code in context and explains vulnerabilities in plain English with specific fix instructions.
75
+
76
+ ## CI Integration
77
+
78
+ ### GitHub Actions
79
+
80
+ ```yaml
81
+ name: Security Scan
82
+ on: [push, pull_request]
83
+
84
+ jobs:
85
+ xploitscan:
86
+ runs-on: ubuntu-latest
87
+ steps:
88
+ - uses: actions/checkout@v4
89
+ - uses: xploitscan/action@v1
90
+ with:
91
+ anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
92
+ ```
93
+
94
+ Results appear in the GitHub Security tab.
95
+
96
+ ### Any CI
97
+
98
+ ```bash
99
+ npx xploitscan scan . --format sarif --no-ai > results.sarif
100
+ ```
101
+
102
+ Exit code is 1 when critical or high severity issues are found.
103
+
104
+ ## Configuration
105
+
106
+ Create a `.xploitscanrc.json` in your project root:
107
+
108
+ ```json
109
+ {
110
+ "exclude": ["tests/**", "scripts/**"],
111
+ "ai": true,
112
+ "severity": "medium",
113
+ "disableRules": ["VC008"]
114
+ }
115
+ ```
116
+
117
+ ## Optional: Deeper Scanning
118
+
119
+ Install these tools for additional detection coverage:
120
+
121
+ ```bash
122
+ # Semgrep - 2000+ community security rules
123
+ pip install semgrep
124
+
125
+ # Gitleaks - advanced secret detection
126
+ brew install gitleaks
127
+ ```
128
+
129
+ XploitScan automatically uses them if available.
130
+
131
+ ## Auth & Pro Plan
132
+
133
+ ```bash
134
+ # Log in to sync scan history
135
+ xploitscan auth login
136
+
137
+ # Check your plan
138
+ xploitscan auth whoami
139
+
140
+ # Upgrade to Pro ($29/mo) for unlimited scans
141
+ xploitscan upgrade
142
+ ```
143
+
144
+ Free plan: 3 scans/day. Pro: unlimited scans, scan history, team features.
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ checkUsage,
4
+ clearToken,
5
+ getCheckoutUrl,
6
+ getStoredToken,
7
+ incrementUsage,
8
+ isAuthenticated,
9
+ storeToken,
10
+ syncUser,
11
+ uploadScanResults
12
+ } from "./chunk-47X3TUVR.js";
13
+ export {
14
+ checkUsage,
15
+ clearToken,
16
+ getCheckoutUrl,
17
+ getStoredToken,
18
+ incrementUsage,
19
+ isAuthenticated,
20
+ storeToken,
21
+ syncUser,
22
+ uploadScanResults
23
+ };
24
+ //# sourceMappingURL=api-QTNXZY4O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/utils/api.ts
10
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "fs";
11
+ import { join } from "path";
12
+ import { homedir } from "os";
13
+ var CONFIG_DIR = join(homedir(), ".xploitscan");
14
+ var TOKEN_FILE = join(CONFIG_DIR, "token.json");
15
+ var API_BASE = process.env.XPLOITSCAN_API_URL ?? "https://api.xploitscan.com";
16
+ if (API_BASE.startsWith("http://") && !API_BASE.includes("localhost") && !API_BASE.includes("127.0.0.1")) {
17
+ console.warn("WARNING: API URL is not using HTTPS. This is insecure.");
18
+ }
19
+ function getStoredToken() {
20
+ try {
21
+ if (!existsSync(TOKEN_FILE)) return null;
22
+ const data = JSON.parse(readFileSync(TOKEN_FILE, "utf-8"));
23
+ if (data.expiresAt && Date.now() > data.expiresAt) {
24
+ unlinkSync(TOKEN_FILE);
25
+ return null;
26
+ }
27
+ return data;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+ function storeToken(data) {
33
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
34
+ writeFileSync(TOKEN_FILE, JSON.stringify(data, null, 2), { mode: 384 });
35
+ }
36
+ function clearToken() {
37
+ try {
38
+ if (existsSync(TOKEN_FILE)) {
39
+ unlinkSync(TOKEN_FILE);
40
+ }
41
+ } catch {
42
+ }
43
+ }
44
+ function isAuthenticated() {
45
+ return getStoredToken() !== null;
46
+ }
47
+ async function apiRequest(path, options = {}) {
48
+ const token = getStoredToken();
49
+ const headers = {
50
+ "Content-Type": "application/json",
51
+ ...options.headers
52
+ };
53
+ if (token) {
54
+ headers.Authorization = `Bearer ${token.token}`;
55
+ }
56
+ return fetch(`${API_BASE}${path}`, {
57
+ ...options,
58
+ headers
59
+ });
60
+ }
61
+ async function checkUsage() {
62
+ const token = getStoredToken();
63
+ if (!token) {
64
+ return { allowed: true, plan: "anonymous", remaining: -1, limit: -1 };
65
+ }
66
+ try {
67
+ const res = await apiRequest("/api/usage/check");
68
+ if (!res.ok) {
69
+ return { allowed: true, plan: "unknown", remaining: -1, limit: -1 };
70
+ }
71
+ return await res.json();
72
+ } catch {
73
+ return { allowed: true, plan: "offline", remaining: -1, limit: -1 };
74
+ }
75
+ }
76
+ async function incrementUsage() {
77
+ const token = getStoredToken();
78
+ if (!token) return;
79
+ try {
80
+ await apiRequest("/api/usage/increment", { method: "POST" });
81
+ } catch {
82
+ }
83
+ }
84
+ async function uploadScanResults(result) {
85
+ const token = getStoredToken();
86
+ if (!token) return;
87
+ try {
88
+ await apiRequest("/api/scans", {
89
+ method: "POST",
90
+ body: JSON.stringify(result)
91
+ });
92
+ } catch {
93
+ }
94
+ }
95
+ async function syncUser() {
96
+ try {
97
+ const res = await apiRequest("/api/users/sync", { method: "POST" });
98
+ if (!res.ok) return null;
99
+ const data = await res.json();
100
+ return data.user;
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+ async function getCheckoutUrl() {
106
+ try {
107
+ const res = await apiRequest("/api/billing/checkout", { method: "POST" });
108
+ if (!res.ok) return null;
109
+ const data = await res.json();
110
+ return data.url;
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ export {
117
+ __require,
118
+ getStoredToken,
119
+ storeToken,
120
+ clearToken,
121
+ isAuthenticated,
122
+ checkUsage,
123
+ incrementUsage,
124
+ uploadScanResults,
125
+ syncUser,
126
+ getCheckoutUrl
127
+ };
128
+ //# sourceMappingURL=chunk-47X3TUVR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/api.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst CONFIG_DIR = join(homedir(), \".xploitscan\");\nconst TOKEN_FILE = join(CONFIG_DIR, \"token.json\");\n\nconst API_BASE = process.env.XPLOITSCAN_API_URL ?? \"https://api.xploitscan.com\";\n\nif (API_BASE.startsWith(\"http://\") && !API_BASE.includes(\"localhost\") && !API_BASE.includes(\"127.0.0.1\")) {\n console.warn(\"WARNING: API URL is not using HTTPS. This is insecure.\");\n}\n\ninterface TokenData {\n token: string;\n userId: string;\n email: string;\n expiresAt?: number;\n}\n\nexport function getStoredToken(): TokenData | null {\n try {\n if (!existsSync(TOKEN_FILE)) return null;\n const data = JSON.parse(readFileSync(TOKEN_FILE, \"utf-8\"));\n if (data.expiresAt && Date.now() > data.expiresAt) {\n // Token expired\n unlinkSync(TOKEN_FILE);\n return null;\n }\n return data;\n } catch {\n return null;\n }\n}\n\nexport function storeToken(data: TokenData): void {\n mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });\n writeFileSync(TOKEN_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });\n}\n\nexport function clearToken(): void {\n try {\n if (existsSync(TOKEN_FILE)) {\n unlinkSync(TOKEN_FILE);\n }\n } catch {\n // ignore\n }\n}\n\nexport function isAuthenticated(): boolean {\n return getStoredToken() !== null;\n}\n\nasync function apiRequest(\n path: string,\n options: RequestInit = {},\n): Promise<Response> {\n const token = getStoredToken();\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...(options.headers as Record<string, string>),\n };\n\n if (token) {\n headers.Authorization = `Bearer ${token.token}`;\n }\n\n return fetch(`${API_BASE}${path}`, {\n ...options,\n headers,\n });\n}\n\nexport async function checkUsage(): Promise<{\n allowed: boolean;\n plan: string;\n remaining: number;\n limit: number;\n}> {\n const token = getStoredToken();\n if (!token) {\n // Unauthenticated users get limited local scans\n return { allowed: true, plan: \"anonymous\", remaining: -1, limit: -1 };\n }\n\n try {\n const res = await apiRequest(\"/api/usage/check\");\n if (!res.ok) {\n // API error — allow scan to proceed locally\n return { allowed: true, plan: \"unknown\", remaining: -1, limit: -1 };\n }\n return await res.json();\n } catch {\n // Network error — allow local scan\n return { allowed: true, plan: \"offline\", remaining: -1, limit: -1 };\n }\n}\n\nexport async function incrementUsage(): Promise<void> {\n const token = getStoredToken();\n if (!token) return;\n\n try {\n await apiRequest(\"/api/usage/increment\", { method: \"POST\" });\n } catch {\n // Silent fail — don't block scan\n }\n}\n\nexport async function uploadScanResults(result: {\n directory: string;\n filesScanned: number;\n findings: unknown[];\n duration: number;\n}): Promise<void> {\n const token = getStoredToken();\n if (!token) return;\n\n try {\n await apiRequest(\"/api/scans\", {\n method: \"POST\",\n body: JSON.stringify(result),\n });\n } catch {\n // Silent fail\n }\n}\n\nexport async function syncUser(): Promise<{ plan: string; email: string } | null> {\n try {\n const res = await apiRequest(\"/api/users/sync\", { method: \"POST\" });\n if (!res.ok) return null;\n const data = await res.json();\n return data.user;\n } catch {\n return null;\n }\n}\n\nexport async function getCheckoutUrl(): Promise<string | null> {\n try {\n const res = await apiRequest(\"/api/billing/checkout\", { method: \"POST\" });\n if (!res.ok) return null;\n const data = await res.json();\n return data.url;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":[]}