preflyt-check 1.0.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 +127 -0
- package/bin/preflyt-check.js +354 -0
- package/package.json +17 -0
- package/src/index.js +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# preflyt-check
|
|
2
|
+
|
|
3
|
+
Pre-deployment security scanner for your deploy pipeline. Scans your live site for exposed `.env` files, open database ports, missing security headers, and other misconfigurations -- in under 30 seconds, with zero dependencies.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx preflyt-check https://mysite.com
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No install needed. No signup needed. 3 free scans.
|
|
12
|
+
|
|
13
|
+
## With a Pro API key
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx preflyt-check https://mysite.com --key sk_live_xxx
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Get your Pro key at [preflyt.dev/pricing](https://preflyt.dev/pricing) for unlimited scans.
|
|
20
|
+
|
|
21
|
+
## Options
|
|
22
|
+
|
|
23
|
+
| Flag | Description |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `--key`, `-k` | Pro API key for unlimited scans |
|
|
26
|
+
| `--fail` | Exit code 1 if issues found (default: off) |
|
|
27
|
+
| `--fail-on <level>` | Minimum severity to fail on: `high`, `medium`, `low` (default: `high`) |
|
|
28
|
+
| `--quiet`, `-q` | Minimal output, just pass/fail |
|
|
29
|
+
| `--json` | Output raw JSON instead of formatted text |
|
|
30
|
+
| `--timeout <sec>` | Scan timeout in seconds (default: 60) |
|
|
31
|
+
| `--help`, `-h` | Show usage |
|
|
32
|
+
|
|
33
|
+
## Exit codes
|
|
34
|
+
|
|
35
|
+
Preflyt will never block your deploy due to our own errors. Exit code 1 only occurs when you explicitly use `--fail` and real issues are confirmed.
|
|
36
|
+
|
|
37
|
+
| Exit code | When |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `0` | Scan clean, scan errored, timed out, API unreachable, limit reached, or `--fail` not set |
|
|
40
|
+
| `1` | `--fail` set AND scan succeeded AND findings match `--fail-on` severity |
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
### Bare deploy script
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
#!/bin/bash
|
|
48
|
+
# deploy.sh
|
|
49
|
+
git push production main
|
|
50
|
+
npx preflyt-check https://mysite.com
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### package.json post-deploy
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"scripts": {
|
|
58
|
+
"deploy": "vercel --prod",
|
|
59
|
+
"postdeploy": "npx preflyt-check https://mysite.com"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### GitHub Actions
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
- name: Deploy
|
|
68
|
+
run: npm run deploy
|
|
69
|
+
|
|
70
|
+
- name: Security check
|
|
71
|
+
run: npx preflyt-check https://mysite.com --fail --fail-on high
|
|
72
|
+
env:
|
|
73
|
+
# Optional: store key in GitHub Secrets
|
|
74
|
+
PREFLYT_KEY: ${{ secrets.PREFLYT_KEY }}
|
|
75
|
+
# Only fail on high-severity issues
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### GitHub Actions (with Pro key)
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
- name: Security check
|
|
82
|
+
run: npx preflyt-check https://mysite.com --key $PREFLYT_KEY --fail
|
|
83
|
+
env:
|
|
84
|
+
PREFLYT_KEY: ${{ secrets.PREFLYT_KEY }}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Docker
|
|
88
|
+
|
|
89
|
+
```dockerfile
|
|
90
|
+
RUN npm install -g preflyt-check
|
|
91
|
+
# In your entrypoint or health check:
|
|
92
|
+
CMD ["sh", "-c", "preflyt-check https://mysite.com && node server.js"]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Programmatic usage
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
const { scan } = require("preflyt-check");
|
|
99
|
+
|
|
100
|
+
const result = await scan("https://mysite.com", {
|
|
101
|
+
apiKey: "sk_live_xxx", // optional
|
|
102
|
+
timeout: 60, // optional, seconds
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
console.log(result.status); // "clean" | "issues_found" | "error"
|
|
106
|
+
console.log(result.total_issues); // number
|
|
107
|
+
console.log(result.findings); // array of { title, severity, category }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## What it checks
|
|
111
|
+
|
|
112
|
+
- Exposed .env, .git, backup files, source maps, phpinfo
|
|
113
|
+
- Open database ports (MySQL, PostgreSQL, MongoDB, Redis)
|
|
114
|
+
- Exposed dev servers and admin tools
|
|
115
|
+
- Missing security headers (HSTS, CSP, X-Frame-Options)
|
|
116
|
+
- CORS misconfiguration
|
|
117
|
+
- Insecure cookies
|
|
118
|
+
- Server version leakage
|
|
119
|
+
|
|
120
|
+
## Zero dependencies
|
|
121
|
+
|
|
122
|
+
This package uses only Node.js built-in modules. No `node_modules` tree, no supply chain risk, instant install.
|
|
123
|
+
|
|
124
|
+
## Links
|
|
125
|
+
|
|
126
|
+
- Website: [preflyt.dev](https://preflyt.dev)
|
|
127
|
+
- Pricing: [preflyt.dev/pricing](https://preflyt.dev/pricing)
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// ── Zero-dependency CLI for Preflyt security scanning ────────────────────────
|
|
5
|
+
// Uses only Node.js built-in modules. No node_modules required.
|
|
6
|
+
|
|
7
|
+
var https = require("https");
|
|
8
|
+
var http = require("http");
|
|
9
|
+
var URL = require("url").URL;
|
|
10
|
+
|
|
11
|
+
var API_URL = "https://api.preflyt.dev/api/scan/cli";
|
|
12
|
+
var VERSION = "1.0.0";
|
|
13
|
+
|
|
14
|
+
var SEVERITY_RANK = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
|
|
15
|
+
|
|
16
|
+
// ── Arg parsing ──────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function parseArgs(argv) {
|
|
19
|
+
var args = {
|
|
20
|
+
url: null,
|
|
21
|
+
key: null,
|
|
22
|
+
fail: false,
|
|
23
|
+
failOn: "high",
|
|
24
|
+
quiet: false,
|
|
25
|
+
json: false,
|
|
26
|
+
timeout: 60,
|
|
27
|
+
help: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
var i = 2; // skip node + script
|
|
31
|
+
while (i < argv.length) {
|
|
32
|
+
var arg = argv[i];
|
|
33
|
+
|
|
34
|
+
if (arg === "--help" || arg === "-h") {
|
|
35
|
+
args.help = true;
|
|
36
|
+
} else if (arg === "--fail") {
|
|
37
|
+
args.fail = true;
|
|
38
|
+
} else if (arg === "--quiet" || arg === "-q") {
|
|
39
|
+
args.quiet = true;
|
|
40
|
+
} else if (arg === "--json") {
|
|
41
|
+
args.json = true;
|
|
42
|
+
} else if (arg === "--key" || arg === "-k") {
|
|
43
|
+
i++;
|
|
44
|
+
args.key = argv[i] || null;
|
|
45
|
+
} else if (arg === "--fail-on") {
|
|
46
|
+
i++;
|
|
47
|
+
args.failOn = argv[i] || "high";
|
|
48
|
+
} else if (arg === "--timeout") {
|
|
49
|
+
i++;
|
|
50
|
+
args.timeout = parseInt(argv[i], 10) || 60;
|
|
51
|
+
} else if (!arg.startsWith("-") && !args.url) {
|
|
52
|
+
args.url = arg;
|
|
53
|
+
}
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return args;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Output helpers ───────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
var CATEGORY_LABELS = {
|
|
63
|
+
file_exposure: "File & Code Exposure",
|
|
64
|
+
server_network: "Server & Network Security",
|
|
65
|
+
http_hardening: "HTTP Hardening",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
var SEVERITY_LABELS = {
|
|
69
|
+
critical: "CRIT",
|
|
70
|
+
high: "HIGH",
|
|
71
|
+
medium: "MEDIUM",
|
|
72
|
+
low: "LOW",
|
|
73
|
+
info: "INFO",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function padRight(str, len) {
|
|
77
|
+
while (str.length < len) str += " ";
|
|
78
|
+
return str;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function printHelp() {
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log(" preflyt-check <url> [options]");
|
|
84
|
+
console.log("");
|
|
85
|
+
console.log(" Pre-deployment security scanner. Checks your live site for");
|
|
86
|
+
console.log(" exposed secrets, open ports, and misconfigurations.");
|
|
87
|
+
console.log("");
|
|
88
|
+
console.log(" Options:");
|
|
89
|
+
console.log(" --key, -k <key> Pro API key for unlimited scans");
|
|
90
|
+
console.log(" --fail Exit code 1 if issues found");
|
|
91
|
+
console.log(" --fail-on <level> Minimum severity to fail on (high, medium, low)");
|
|
92
|
+
console.log(" --quiet, -q Minimal output, just pass/fail");
|
|
93
|
+
console.log(" --json Output raw JSON");
|
|
94
|
+
console.log(" --timeout <sec> Scan timeout in seconds (default: 60)");
|
|
95
|
+
console.log(" --help, -h Show this help");
|
|
96
|
+
console.log("");
|
|
97
|
+
console.log(" Examples:");
|
|
98
|
+
console.log(" npx preflyt-check https://mysite.com");
|
|
99
|
+
console.log(" npx preflyt-check https://mysite.com --key sk_live_xxx");
|
|
100
|
+
console.log(" npx preflyt-check https://mysite.com --fail --fail-on medium");
|
|
101
|
+
console.log("");
|
|
102
|
+
console.log(" https://preflyt.dev");
|
|
103
|
+
console.log("");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function printResults(data, args) {
|
|
107
|
+
// JSON mode — raw output
|
|
108
|
+
if (args.json) {
|
|
109
|
+
console.log(JSON.stringify(data, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var timeStr = data.scan_time_seconds ? "(" + data.scan_time_seconds + "s)" : "";
|
|
114
|
+
|
|
115
|
+
// Limit reached
|
|
116
|
+
if (data.status === "limit_reached") {
|
|
117
|
+
console.log("");
|
|
118
|
+
console.log(" \u24D8 Free scan limit reached (3/3 used).");
|
|
119
|
+
console.log("");
|
|
120
|
+
console.log(" Get unlimited CLI scans with Pro - $9.99/mo");
|
|
121
|
+
console.log(" https://preflyt.dev/pricing");
|
|
122
|
+
console.log("");
|
|
123
|
+
console.log(" Tip: Add --key YOUR_KEY for unlimited scans.");
|
|
124
|
+
console.log("");
|
|
125
|
+
console.log(" Deploy continues. No issues blocked.");
|
|
126
|
+
console.log("");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Error
|
|
131
|
+
if (data.status === "error") {
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(" \u26A0\uFE0F Scan could not complete: " + (data.message || "unknown error"));
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log(" Deploy continues. No issues blocked.");
|
|
136
|
+
console.log("");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Quiet mode
|
|
141
|
+
if (args.quiet) {
|
|
142
|
+
if (data.status === "clean") {
|
|
143
|
+
console.log(" \u2705 All clear. " + timeStr);
|
|
144
|
+
} else {
|
|
145
|
+
console.log(" \u26A0\uFE0F " + data.total_issues + " issue" + (data.total_issues !== 1 ? "s" : "") + " found. " + timeStr);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Full output — category summary
|
|
151
|
+
console.log("");
|
|
152
|
+
var cats = data.categories || {};
|
|
153
|
+
var order = ["file_exposure", "server_network", "http_hardening"];
|
|
154
|
+
|
|
155
|
+
for (var c = 0; c < order.length; c++) {
|
|
156
|
+
var key = order[c];
|
|
157
|
+
var cat = cats[key];
|
|
158
|
+
var label = CATEGORY_LABELS[key] || key;
|
|
159
|
+
if (!cat || cat.status === "clean") {
|
|
160
|
+
console.log(" \u2713 " + padRight(label, 28) + " - clean");
|
|
161
|
+
} else {
|
|
162
|
+
console.log(" \u2717 " + padRight(label, 28) + " - " + cat.count + " issue" + (cat.count !== 1 ? "s" : ""));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Clean
|
|
167
|
+
if (data.status === "clean") {
|
|
168
|
+
console.log("");
|
|
169
|
+
console.log(" \u2705 All clear. Ship it. " + timeStr);
|
|
170
|
+
console.log("");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Issues
|
|
175
|
+
console.log("");
|
|
176
|
+
console.log(" \u26A0\uFE0F " + data.total_issues + " issue" + (data.total_issues !== 1 ? "s" : "") + " found:");
|
|
177
|
+
console.log("");
|
|
178
|
+
|
|
179
|
+
var findings = data.findings || [];
|
|
180
|
+
// Sort by severity descending
|
|
181
|
+
findings.sort(function (a, b) {
|
|
182
|
+
return (SEVERITY_RANK[b.severity] || 0) - (SEVERITY_RANK[a.severity] || 0);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
for (var f = 0; f < findings.length; f++) {
|
|
186
|
+
var finding = findings[f];
|
|
187
|
+
var sevLabel = SEVERITY_LABELS[finding.severity] || finding.severity.toUpperCase();
|
|
188
|
+
console.log(" " + padRight(sevLabel, 8) + finding.title);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log("");
|
|
192
|
+
console.log(" Details: https://preflyt.dev " + timeStr);
|
|
193
|
+
console.log("");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── HTTP request ─────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
function doScan(url, apiKey, timeoutSec) {
|
|
199
|
+
return new Promise(function (resolve, reject) {
|
|
200
|
+
var payload = JSON.stringify({
|
|
201
|
+
url: url,
|
|
202
|
+
api_key: apiKey || null,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
var parsed = new URL(API_URL);
|
|
206
|
+
var reqModule = parsed.protocol === "https:" ? https : http;
|
|
207
|
+
|
|
208
|
+
var reqOpts = {
|
|
209
|
+
hostname: parsed.hostname,
|
|
210
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
211
|
+
path: parsed.pathname,
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: {
|
|
214
|
+
"Content-Type": "application/json",
|
|
215
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
216
|
+
"User-Agent": "preflyt-check/" + VERSION,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
var timeoutMs = timeoutSec * 1000;
|
|
221
|
+
|
|
222
|
+
var timer = setTimeout(function () {
|
|
223
|
+
req.destroy();
|
|
224
|
+
reject(new Error("timeout"));
|
|
225
|
+
}, timeoutMs);
|
|
226
|
+
|
|
227
|
+
var req = reqModule.request(reqOpts, function (res) {
|
|
228
|
+
var chunks = [];
|
|
229
|
+
res.on("data", function (chunk) {
|
|
230
|
+
chunks.push(chunk);
|
|
231
|
+
});
|
|
232
|
+
res.on("end", function () {
|
|
233
|
+
clearTimeout(timer);
|
|
234
|
+
var body = Buffer.concat(chunks).toString();
|
|
235
|
+
if (res.statusCode !== 200) {
|
|
236
|
+
try {
|
|
237
|
+
var errData = JSON.parse(body);
|
|
238
|
+
reject(new Error(errData.detail || errData.message || "HTTP " + res.statusCode));
|
|
239
|
+
} catch (_e) {
|
|
240
|
+
reject(new Error("HTTP " + res.statusCode));
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
resolve(JSON.parse(body));
|
|
246
|
+
} catch (_e) {
|
|
247
|
+
reject(new Error("Invalid JSON response"));
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
req.on("error", function (err) {
|
|
253
|
+
clearTimeout(timer);
|
|
254
|
+
reject(err);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
req.write(payload);
|
|
258
|
+
req.end();
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ── Exit code logic ──────────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
function shouldFail(data, args) {
|
|
265
|
+
// Exit 1 ONLY when ALL conditions are true:
|
|
266
|
+
// 1. --fail flag explicitly set
|
|
267
|
+
// 2. Scan completed successfully (status is clean or issues_found)
|
|
268
|
+
// 3. At least one finding meets or exceeds --fail-on severity
|
|
269
|
+
if (!args.fail) return false;
|
|
270
|
+
if (data.status !== "issues_found") return false;
|
|
271
|
+
|
|
272
|
+
var threshold = SEVERITY_RANK[args.failOn] || SEVERITY_RANK.high;
|
|
273
|
+
var findings = data.findings || [];
|
|
274
|
+
|
|
275
|
+
for (var i = 0; i < findings.length; i++) {
|
|
276
|
+
var rank = SEVERITY_RANK[findings[i].severity] || 0;
|
|
277
|
+
if (rank >= threshold) return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
async function main() {
|
|
286
|
+
var args = parseArgs(process.argv);
|
|
287
|
+
|
|
288
|
+
if (args.help) {
|
|
289
|
+
printHelp();
|
|
290
|
+
process.exit(0);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!args.url) {
|
|
294
|
+
printHelp();
|
|
295
|
+
process.exit(0);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Basic URL validation
|
|
299
|
+
try {
|
|
300
|
+
var parsed = new URL(args.url);
|
|
301
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
302
|
+
console.log("");
|
|
303
|
+
console.log(" URL must start with http:// or https://");
|
|
304
|
+
console.log("");
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
} catch (_e) {
|
|
308
|
+
console.log("");
|
|
309
|
+
console.log(" Invalid URL: " + args.url);
|
|
310
|
+
console.log(" URL must start with http:// or https://");
|
|
311
|
+
console.log("");
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!args.json) {
|
|
316
|
+
console.log("");
|
|
317
|
+
console.log("\uD83D\uDD0D Preflyt scanning " + args.url + "...");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
var data = await doScan(args.url, args.key, args.timeout);
|
|
322
|
+
printResults(data, args);
|
|
323
|
+
|
|
324
|
+
if (shouldFail(data, args)) {
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
process.exit(0);
|
|
328
|
+
} catch (err) {
|
|
329
|
+
if (!args.json) {
|
|
330
|
+
console.log("");
|
|
331
|
+
console.log(" \u26A0\uFE0F Scan could not complete: " + err.message);
|
|
332
|
+
console.log("");
|
|
333
|
+
console.log(" Deploy continues. No issues blocked.");
|
|
334
|
+
console.log("");
|
|
335
|
+
} else {
|
|
336
|
+
console.log(JSON.stringify({
|
|
337
|
+
status: "error",
|
|
338
|
+
url: args.url,
|
|
339
|
+
message: err.message,
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
// Always exit 0 on errors — never block a deploy due to our own failures
|
|
343
|
+
process.exit(0);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Wrap everything — any uncaught exception exits 0
|
|
348
|
+
try {
|
|
349
|
+
main();
|
|
350
|
+
} catch (err) {
|
|
351
|
+
console.error(" Preflyt encountered an unexpected error: " + err.message);
|
|
352
|
+
console.error(" Deploy continues. No issues blocked.");
|
|
353
|
+
process.exit(0);
|
|
354
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "preflyt-check",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pre-deployment security scanner. Checks your live site for exposed secrets, open ports, and misconfigurations.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"preflyt-check": "./bin/preflyt-check.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "src/index.js",
|
|
9
|
+
"keywords": ["security", "deployment", "scanner", "preflyt", "env", "exposed", "ports"],
|
|
10
|
+
"author": "Preflyt",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"homepage": "https://preflyt.dev",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/doureios39/preflyt-check"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const https = require("https");
|
|
4
|
+
const http = require("http");
|
|
5
|
+
const { URL } = require("url");
|
|
6
|
+
|
|
7
|
+
const API_URL = "https://api.preflyt.dev/api/scan/cli";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Run a Preflyt scan programmatically.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} url - The URL to scan.
|
|
13
|
+
* @param {object} [opts] - Options.
|
|
14
|
+
* @param {string} [opts.apiKey] - Pro API key for unlimited scans.
|
|
15
|
+
* @param {number} [opts.timeout] - Timeout in seconds (default 60).
|
|
16
|
+
* @returns {Promise<object>} The scan result.
|
|
17
|
+
*/
|
|
18
|
+
function scan(url, opts) {
|
|
19
|
+
opts = opts || {};
|
|
20
|
+
var timeout = (opts.timeout || 60) * 1000;
|
|
21
|
+
|
|
22
|
+
return new Promise(function (resolve, reject) {
|
|
23
|
+
var payload = JSON.stringify({
|
|
24
|
+
url: url,
|
|
25
|
+
api_key: opts.apiKey || null,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
var parsed = new URL(API_URL);
|
|
29
|
+
var reqModule = parsed.protocol === "https:" ? https : http;
|
|
30
|
+
|
|
31
|
+
var reqOpts = {
|
|
32
|
+
hostname: parsed.hostname,
|
|
33
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
34
|
+
path: parsed.pathname,
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
39
|
+
"User-Agent": "preflyt-check/1.0.0",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
var timer = setTimeout(function () {
|
|
44
|
+
req.destroy();
|
|
45
|
+
reject(new Error("timeout"));
|
|
46
|
+
}, timeout);
|
|
47
|
+
|
|
48
|
+
var req = reqModule.request(reqOpts, function (res) {
|
|
49
|
+
var chunks = [];
|
|
50
|
+
res.on("data", function (chunk) {
|
|
51
|
+
chunks.push(chunk);
|
|
52
|
+
});
|
|
53
|
+
res.on("end", function () {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
var body = Buffer.concat(chunks).toString();
|
|
56
|
+
if (res.statusCode !== 200) {
|
|
57
|
+
try {
|
|
58
|
+
var errData = JSON.parse(body);
|
|
59
|
+
reject(new Error(errData.detail || errData.message || "HTTP " + res.statusCode));
|
|
60
|
+
} catch (_e) {
|
|
61
|
+
reject(new Error("HTTP " + res.statusCode));
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
resolve(JSON.parse(body));
|
|
67
|
+
} catch (_e) {
|
|
68
|
+
reject(new Error("Invalid JSON response"));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
req.on("error", function (err) {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
reject(err);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
req.write(payload);
|
|
79
|
+
req.end();
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { scan: scan };
|