pqcheck 0.7.5 → 0.7.7

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 CHANGED
@@ -12,6 +12,12 @@ The same scanner that powers [quantapact.com](https://quantapact.com), the brows
12
12
 
13
13
  ---
14
14
 
15
+ ## What's new in 0.7.6
16
+
17
+ User-Agent now consistently tags the subcommand on every request (`pqcheck-cli/0.7.6 (scan)`, `(lock)`, `(deps)`, `(history)`, `(watch)`). Lets the server aggregate adoption by subcommand. No new data collected — the subcommand token rides inside the User-Agent header that has always been logged anonymously. See [privacy](https://quantapact.com/privacy) and [CHANGELOG.md](./CHANGELOG.md).
18
+
19
+ ---
20
+
15
21
  ## What it does
16
22
 
17
23
  `pqcheck` scans any HTTPS domain and computes its **Decryption Blast Radius score** — the first continuous metric for harvest-now-decrypt-later (HNDL) risk. Every other TLS scanner answers "is post-quantum cryptography enabled?" with yes/no. `pqcheck` answers the question that actually matters: *if an adversary harvests this traffic today and decrypts it in 2035, how much past + future data unlocks?*
@@ -179,7 +185,7 @@ curl -s "https://www.quantapact.com/api/scan?domain=stripe.com" | jq '.grade, .s
179
185
 
180
186
  Full API reference at [quantapact.com/api](https://quantapact.com/api).
181
187
 
182
- **Rate limit:** ~60 requests/minute per IP. No API key required. Returns HTTP 429 if exceeded — back off and retry.
188
+ **Rate limits:** 300 scans per hour per IP, 20 `--fresh` (force-refresh) scans per hour per IP. No API key required. Returns HTTP 429 if exceeded — back off and retry, or [let us know via the feedback form](https://quantapact.com/feedback) if you need higher limits (we're prioritizing the API tier based on real demand).
183
189
 
184
190
  ## Methodology
185
191
 
@@ -233,6 +239,8 @@ MIT. © 2026 Quantapact.
233
239
 
234
240
  ---
235
241
 
236
- **Source:** [github.com/quantapact/pqcheck](https://github.com/quantapact/pqcheck) *(public, pending org transfer to `quantapact/pqcheck`)*
242
+ **Source:** [github.com/quantapact/pqcheck](https://github.com/quantapact/pqcheck)
243
+
244
+ **Changelog:** [CHANGELOG.md](./CHANGELOG.md) for version-by-version release notes.
237
245
 
238
246
  **Issues / feedback:** [quantapact.com/feedback](https://quantapact.com/feedback) or open an issue on the repo.
package/bin/pqcheck.js CHANGED
@@ -7,7 +7,7 @@
7
7
  // =============================================================================
8
8
 
9
9
  const API_BASE = process.env.PQCHECK_API_BASE || "https://quantapact.com";
10
- const VERSION = "0.7.5";
10
+ const VERSION = "0.7.7";
11
11
 
12
12
  const ANSI = {
13
13
  reset: "\x1b[0m",
@@ -107,28 +107,45 @@ async function main() {
107
107
  return; // runWatch handles its own exit; in practice it runs until killed.
108
108
  }
109
109
 
110
+ // --fresh: bypass server cache, force a fresh scan. Useful when verifying
111
+ // a cert/key change you just deployed. Subject to a 20/hr per-IP cap on
112
+ // the server side.
113
+ const fresh = args.includes("--fresh") || args.includes("--force");
114
+
110
115
  // One-shot scan(s)
111
116
  let worstExit = 0;
112
117
  for (const domain of domains) {
113
- const exit = await runOneScan({ domain, format, quiet, threshold, webhookUrl, multi: domains.length > 1 });
118
+ const exit = await runOneScan({ domain, format, quiet, threshold, webhookUrl, multi: domains.length > 1, fresh });
114
119
  if (exit > worstExit) worstExit = exit;
115
120
  }
116
121
  process.exit(worstExit);
117
122
  }
118
123
 
119
- async function runOneScan({ domain, format, quiet, threshold, webhookUrl, multi }) {
120
- if (!quiet && format === "text") process.stderr.write(color("dim", `Scanning ${domain} ...`));
124
+ async function runOneScan({ domain, format, quiet, threshold, webhookUrl, multi, fresh }) {
125
+ if (!quiet && format === "text") process.stderr.write(color("dim", `Scanning ${domain}${fresh ? " (forcing fresh)" : ""} ...`));
121
126
  let report;
122
127
  try {
123
- const resp = await fetch(`${API_BASE}/api/scan?domain=${encodeURIComponent(domain)}`, {
128
+ // --fresh appends ?force=1 to bypass the smart-cache. Use when verifying
129
+ // a cert/key change you just deployed — otherwise scans hit the 1h SWR
130
+ // cache and return up-to-1h-old data. Subject to a 20/hr per-IP cap on
131
+ // the server side; if exceeded, the server silently downgrades to a
132
+ // cached scan and returns that instead of erroring.
133
+ const qs = fresh ? `?domain=${encodeURIComponent(domain)}&force=1` : `?domain=${encodeURIComponent(domain)}`;
134
+ const resp = await fetch(`${API_BASE}/api/scan${qs}`, {
124
135
  method: "GET",
125
- headers: { accept: "application/json", "user-agent": `pqcheck-cli/${VERSION}` },
136
+ headers: { accept: "application/json", "user-agent": `pqcheck-cli/${VERSION} (scan)` },
126
137
  });
127
138
  if (!quiet && format === "text") process.stderr.write("\r\x1b[K");
128
139
  if (!resp.ok) {
129
140
  const errBody = await safeJSON(resp);
130
141
  console.error(color("red", `error scanning ${domain}: ${resp.status} ${errBody?.error || resp.statusText}`));
131
142
  if (errBody?.detail) console.error(color("dim", errBody.detail));
143
+ // Surface the 429 upsell hint if present — tells users how to ask for
144
+ // higher limits via the feedback form. Same demand signal we capture
145
+ // on the homepage.
146
+ if (resp.status === 429 && errBody?.need_more?.feedback_url) {
147
+ console.error(color("dim", `${errBody.need_more.message} → ${errBody.need_more.feedback_url}`));
148
+ }
132
149
  return 1;
133
150
  }
134
151
  report = await resp.json();
@@ -215,7 +232,7 @@ async function runWatch({ domains, format, quiet, threshold, webhookUrl, interva
215
232
  try {
216
233
  const resp = await fetch(`${API_BASE}/api/scan?domain=${encodeURIComponent(domain)}`, {
217
234
  method: "GET",
218
- headers: { accept: "application/json", "user-agent": `pqcheck-cli/${VERSION}` },
235
+ headers: { accept: "application/json", "user-agent": `pqcheck-cli/${VERSION} (watch)` },
219
236
  });
220
237
  if (!resp.ok) continue;
221
238
  const report = await resp.json();
@@ -547,6 +564,7 @@ ${color("bold", "Common flags:")}
547
564
  -q, --quiet Print only the numeric score
548
565
  --watch [seconds] Poll every N seconds (default 300) and report changes
549
566
  --webhook <url> POST scan results to a URL (one-shot or each watch tick)
567
+ --fresh Bypass server cache, force a fresh scan (subject to 20/hr per-IP cap)
550
568
 
551
569
  ${color("bold", "Subcommand-specific:")}
552
570
  pqcheck deps:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pqcheck",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "Decryption Blast Radius scanner — find out how much of your data unlocks when quantum decryption arrives.",
5
5
  "keywords": [
6
6
  "post-quantum",