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.
- package/bin/pqcheck.js +184 -0
- 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
|
+
}
|