pqcheck 0.1.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.
Files changed (2) hide show
  1. package/bin/pqcheck.js +184 -0
  2. package/package.json +30 -0
package/bin/pqcheck.js ADDED
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // pqcheck CLI — npx pqcheck <domain>
4
+ // =============================================================================
5
+ // Tiny wrapper around the public scan API at quantapact.com.
6
+ // Zero deps (uses node:fetch). Works under `npx pqcheck` without installation.
7
+ // =============================================================================
8
+
9
+ const API_BASE = process.env.PQCHECK_API_BASE || "https://quantapact.com";
10
+ const VERSION = "0.1.0";
11
+
12
+ const ANSI = {
13
+ reset: "\x1b[0m",
14
+ bold: "\x1b[1m",
15
+ dim: "\x1b[2m",
16
+ red: "\x1b[31m",
17
+ green: "\x1b[32m",
18
+ yellow: "\x1b[33m",
19
+ violet: "\x1b[35m",
20
+ cyan: "\x1b[36m",
21
+ };
22
+ const supportsColor = process.stdout.isTTY && process.env.TERM !== "dumb";
23
+ const color = (c, s) => (supportsColor ? `${ANSI[c]}${s}${ANSI.reset}` : s);
24
+
25
+ async function main() {
26
+ const args = process.argv.slice(2);
27
+
28
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
29
+ printUsage();
30
+ process.exit(args.length === 0 ? 1 : 0);
31
+ }
32
+ if (args.includes("--version") || args.includes("-v")) {
33
+ console.log(`pqcheck ${VERSION}`);
34
+ process.exit(0);
35
+ }
36
+
37
+ const domain = normalizeDomain(args.find((a) => !a.startsWith("-")) || "");
38
+ if (!domain) {
39
+ console.error(color("red", "error: no domain provided"));
40
+ printUsage();
41
+ process.exit(1);
42
+ }
43
+
44
+ const json = args.includes("--json");
45
+
46
+ process.stderr.write(color("dim", `Scanning ${domain} ...`));
47
+ let report;
48
+ try {
49
+ const resp = await fetch(`${API_BASE}/api/scan?domain=${encodeURIComponent(domain)}`, {
50
+ method: "GET",
51
+ headers: { accept: "application/json", "user-agent": `pqcheck-cli/${VERSION}` },
52
+ });
53
+ process.stderr.write("\r\x1b[K"); // clear "Scanning ..." line
54
+ if (!resp.ok) {
55
+ const errBody = await safeJSON(resp);
56
+ console.error(color("red", `error: ${resp.status} ${errBody?.error || resp.statusText}`));
57
+ if (errBody?.detail) console.error(color("dim", errBody.detail));
58
+ process.exit(1);
59
+ }
60
+ report = await resp.json();
61
+ } catch (err) {
62
+ process.stderr.write("\r\x1b[K");
63
+ console.error(color("red", `error: ${err.message}`));
64
+ process.exit(1);
65
+ }
66
+
67
+ if (json) {
68
+ console.log(JSON.stringify(report, null, 2));
69
+ process.exit(0);
70
+ }
71
+
72
+ printReport(report);
73
+ }
74
+
75
+ function printReport(r) {
76
+ if (!r.reachable) {
77
+ console.log(color("red", `\n ${r.domain} — unreachable`));
78
+ if (r.errorMessage) console.log(color("dim", ` ${r.errorMessage}`));
79
+ console.log("");
80
+ return;
81
+ }
82
+
83
+ const labelColor =
84
+ r.scoreLabel === "CRITICAL" ? "red" :
85
+ r.scoreLabel === "HIGH" ? "yellow" :
86
+ r.scoreLabel === "MEDIUM" ? "yellow" : "green";
87
+
88
+ console.log("");
89
+ console.log(` ${color("bold", r.domain)}`);
90
+ console.log(color("dim", " ─────────────────────────────────────"));
91
+ console.log(` ${color("bold", "PUBLIC SURFACE BLAST RADIUS:")} ${color(labelColor, `${r.score} / 10`)} ${color(labelColor, `(${r.scoreLabel})`)}`);
92
+ console.log("");
93
+ console.log(color("dim", " Public surface signals:"));
94
+ console.log(` • TLS: ${r.publicSurface.tlsVersion ?? "?"} ${r.publicSurface.cipher ? color("dim", `(${r.publicSurface.cipher})`) : ""}`);
95
+ console.log(` • Hybrid PQC: ${r.publicSurface.hybridPQC ? color("green", "yes") : color("yellow", "no")}`);
96
+ console.log(` • Cert expires: ${r.publicSurface.daysUntilCertExpiry !== null ? `in ${r.publicSurface.daysUntilCertExpiry} days` : "?"}`);
97
+ console.log(` • HSTS: ${r.publicSurface.hsts ? color("green", "enabled") : color("dim", "not detected")}`);
98
+ console.log(` • Subdomains: ${r.publicSurface.subdomainCount}${r.publicSurface.wildcardCert ? color("yellow", " (wildcard cert)") : ""}`);
99
+ console.log("");
100
+ if (r.findings && r.findings.length) {
101
+ console.log(color("dim", " Findings:"));
102
+ for (const f of r.findings) {
103
+ const sev = f.severity.toUpperCase();
104
+ const sevColor =
105
+ f.severity === "critical" ? "red" :
106
+ f.severity === "high" ? "yellow" :
107
+ f.severity === "medium" ? "yellow" : "dim";
108
+ console.log(` ${color(sevColor, `[${sev}]`)} ${f.title}`);
109
+ console.log(color("dim", ` ${f.detail}`));
110
+ }
111
+ console.log("");
112
+ }
113
+ console.log(color("dim", " ⚠ This is the PUBLIC surface only."));
114
+ console.log(color("dim", ` ${r.internalMultiplierBenchmark}`));
115
+ console.log("");
116
+
117
+ // Phase C: plain-English impact headline + sector ranking
118
+ if (r.impact && r.impact.headline) {
119
+ console.log(color("violet", " Plain-English impact:"));
120
+ console.log(" " + color("dim", r.impact.headline));
121
+ if (r.impact.dataTypeContext) {
122
+ console.log(" " + color("dim", `Data types at risk: ${r.impact.dataTypeContext}`));
123
+ }
124
+ if (r.impact.sensitivityNote) {
125
+ console.log(" " + color("dim", r.impact.sensitivityNote));
126
+ }
127
+ console.log("");
128
+ }
129
+ if (r.sectorRanking && r.sectorRanking.available) {
130
+ var sr = r.sectorRanking;
131
+ console.log(color("violet", ` Sector ranking:`));
132
+ console.log(` Among ${sr.sectorLabel}: ` + color("bold", `${sr.rank} of ${sr.total}`) + color("dim", ` (worse than ${sr.betterThanCount} peers measured)`));
133
+ console.log("");
134
+ } else if (r.sectorRanking && r.sectorRanking.reason) {
135
+ console.log(color("dim", ` ${r.sectorRanking.reason}`));
136
+ console.log("");
137
+ }
138
+
139
+ console.log(color("violet", ` → Full report: ${API_BASE}/?check=${encodeURIComponent(r.domain)}`));
140
+ console.log("");
141
+ }
142
+
143
+ function normalizeDomain(raw) {
144
+ return (raw || "")
145
+ .trim()
146
+ .toLowerCase()
147
+ .replace(/^https?:\/\//, "")
148
+ .replace(/^www\./, "")
149
+ .split("/")[0]
150
+ .split(":")[0];
151
+ }
152
+
153
+ async function safeJSON(resp) {
154
+ try { return await resp.json(); } catch { return null; }
155
+ }
156
+
157
+ function printUsage() {
158
+ console.log(`
159
+ ${color("bold", "pqcheck")} ${color("dim", `v${VERSION}`)}
160
+
161
+ Public Surface Blast Radius — quantum-decryption risk for any domain.
162
+
163
+ ${color("bold", "Usage:")}
164
+ npx pqcheck <domain> Scan and print human-readable report
165
+ npx pqcheck <domain> --json Output raw JSON
166
+
167
+ ${color("bold", "Options:")}
168
+ -h, --help Show this help
169
+ -v, --version Show version
170
+ --json Print raw JSON output
171
+
172
+ ${color("bold", "Examples:")}
173
+ npx pqcheck chase.com
174
+ npx pqcheck quantapact.com --json
175
+
176
+ Backed by the patented Decryption Blast Radius methodology.
177
+ ${color("violet", "https://quantapact.com")}
178
+ `);
179
+ }
180
+
181
+ main().catch((err) => {
182
+ console.error(color("red", `fatal: ${err.message}`));
183
+ process.exit(2);
184
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "pqcheck",
3
+ "version": "0.1.0",
4
+ "description": "Public Surface Blast Radius scanner — quantum-decryption risk in your terminal.",
5
+ "keywords": [
6
+ "post-quantum",
7
+ "cryptography",
8
+ "security",
9
+ "tls",
10
+ "scanner",
11
+ "harvest-now-decrypt-later",
12
+ "hndl",
13
+ "blast-radius",
14
+ "pqc"
15
+ ],
16
+ "homepage": "https://quantapact.com",
17
+ "bugs": "https://quantapact.com",
18
+ "license": "MIT",
19
+ "author": "Quantapact",
20
+ "type": "module",
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "bin": {
25
+ "pqcheck": "./bin/pqcheck.js"
26
+ },
27
+ "files": [
28
+ "bin/"
29
+ ]
30
+ }