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 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 };