pqcheck 0.13.2 → 0.14.1
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 +38 -1
- package/bin/pqcheck.js +142 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -47,6 +47,42 @@ That's it. The scaffolded workflow includes `permissions: id-token: write`, so t
|
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
+
## What's new in 0.14.0
|
|
51
|
+
|
|
52
|
+
**Preview Deploy Trust Diff (R67-locked 2026-05-19).** Compare a preview deployment URL against production and surface application-surface changes (new third-party scripts, header regressions, DBR score drops) inside the PR review — before merge. The stickiest dev-workflow feature per Cipherwake's design ranking, with a dedicated SSRF-pinned scan path that keeps preview-URL hostnames out of our moat tables (feature-branch names stay private).
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx pqcheck preview-diff \
|
|
56
|
+
--preview https://feature-x-abc123.vercel.app \
|
|
57
|
+
--production https://example.com
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Sample output:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Cipherwake Preview Trust Diff
|
|
64
|
+
preview=https://feature-x-abc123.vercel.app
|
|
65
|
+
production=https://example.com
|
|
66
|
+
|
|
67
|
+
Application surface:
|
|
68
|
+
+ New third-party script: widget.intercom.io
|
|
69
|
+
- Content-Security-Policy [script-src] added permissive token(s): 'unsafe-inline'
|
|
70
|
+
~ Strict-Transport-Security weakened: max-age=31536000 → max-age=3600
|
|
71
|
+
|
|
72
|
+
Transport: preview is edge-hosted (Let's Encrypt) — informational only.
|
|
73
|
+
|
|
74
|
+
Verdict: WARN (max severity: high)
|
|
75
|
+
Tier: free · policy: report
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Flags: `--preview <URL>` · `--production <URL>` · `--compare-transport` (opt in TLS/cert/SPKI in verdict) · `--fail-on <severity>` (default `high`; pass `none` for report-only) · `--format pretty|json`.
|
|
79
|
+
|
|
80
|
+
Exit codes match `trust-diff`: `0` pass · `1` warn · `2` fail · `3` error. Free tier silently downgrades fail → report and notes the upgrade hook in the response; Starter+ honors `fail-on` for real CI gating.
|
|
81
|
+
|
|
82
|
+
Auth: `CIPHERWAKE_API_KEY` env (or the GitHub Action which fetches OIDC automatically — no key needed for Free).
|
|
83
|
+
|
|
84
|
+
Also: CSP weakening detection now diffs `script-src` / `default-src` / `object-src` / `frame-ancestors` / `base-uri` / `style-src` directives for newly-permissive tokens (`*`, `'unsafe-inline'`, `'unsafe-eval'`, `data:`, `blob:`).
|
|
85
|
+
|
|
50
86
|
## What's new in 0.12.0
|
|
51
87
|
|
|
52
88
|
**Developer habit-loop bundle (locked 2026-05-16).** Five new subcommands that put Cipherwake where developers already work: PRs, CI, release notes, vendor allowlists. Free tier covers all of them within the 100 Trust Diff calls/month per repo quota.
|
|
@@ -100,7 +136,8 @@ npx pqcheck diff <old.lock> <new.lock> Compare two QXM lockfiles; exit 2
|
|
|
100
136
|
npx pqcheck history <domain> Show 90-day score history (sparkline + samples)
|
|
101
137
|
npx pqcheck changes <domain> Summarize public attack-surface changes in last 14 days
|
|
102
138
|
npx pqcheck cert <file.pem> Analyze a local PEM/CRT cert file (offline, no network)
|
|
103
|
-
npx pqcheck trust-diff <domain> Trust Diff vs configured baseline; CI gate (Free:
|
|
139
|
+
npx pqcheck trust-diff <domain> Trust Diff vs configured baseline; CI gate (Free: 100/repo/mo)
|
|
140
|
+
npx pqcheck preview-diff --preview U --production U Preview-URL vs production-URL diff; new scripts + header regressions + score drops (NEW in 0.14.0)
|
|
104
141
|
npx pqcheck deploy-check <domain> Pre-deploy gate (Trust Diff alias with last-scan baseline)
|
|
105
142
|
npx pqcheck onboard <domain> One-command setup wizard (scan + init + vendors + checklist)
|
|
106
143
|
npx pqcheck init Interactive scaffold for .github/workflows/cipherwake.yml
|
package/bin/pqcheck.js
CHANGED
|
@@ -94,6 +94,12 @@ async function main() {
|
|
|
94
94
|
// cipherwakelabs/pqcheck Action mode: trust-diff.
|
|
95
95
|
return runTrustDiffCommand(args.slice(1));
|
|
96
96
|
}
|
|
97
|
+
if (args[0] === "preview-diff") {
|
|
98
|
+
// CLI v0.14.0 (locked 2026-05-19): preview deployment vs production diff.
|
|
99
|
+
// Calls /api/preview-diff with two URLs and reports application-surface
|
|
100
|
+
// changes (new third-party scripts, header regressions, score drops).
|
|
101
|
+
return runPreviewDiffCommand(args.slice(1));
|
|
102
|
+
}
|
|
97
103
|
if (args[0] === "history") {
|
|
98
104
|
return runHistoryCommand(args.slice(1));
|
|
99
105
|
}
|
|
@@ -2123,6 +2129,142 @@ async function runTrustDiffCommand(args) {
|
|
|
2123
2129
|
process.exit(0);
|
|
2124
2130
|
}
|
|
2125
2131
|
|
|
2132
|
+
/**
|
|
2133
|
+
* `pqcheck preview-diff --preview <URL> --production <URL>` — compare a preview
|
|
2134
|
+
* deployment URL against a production canonical URL. Surfaces new third-party
|
|
2135
|
+
* scripts, security-header regressions, and DBR score drops. CLI v0.14.0.
|
|
2136
|
+
*
|
|
2137
|
+
* Inputs:
|
|
2138
|
+
* --preview <URL> Preview deployment URL (required)
|
|
2139
|
+
* --production <URL> Production canonical URL (required)
|
|
2140
|
+
* --compare-transport Include TLS/cert/SPKI in verdict (default off — preview
|
|
2141
|
+
* URLs are typically edge-hosted by Vercel/Netlify/Cloudflare
|
|
2142
|
+
* and direct transport comparison is noise).
|
|
2143
|
+
* --fail-on <severity> any | low | medium | high | critical (default high).
|
|
2144
|
+
* Honored on paid tiers; Free is report-only.
|
|
2145
|
+
* --format pretty (default) | json
|
|
2146
|
+
*
|
|
2147
|
+
* Exit codes match trust-diff: 0 pass · 1 warn · 2 fail · 3 error.
|
|
2148
|
+
*
|
|
2149
|
+
* Requires CIPHERWAKE_API_KEY env var (Free tier: 100 calls/repo/mo at
|
|
2150
|
+
* /account#api-keys, or use the GitHub Action which fetches OIDC automatically).
|
|
2151
|
+
*/
|
|
2152
|
+
async function runPreviewDiffCommand(args) {
|
|
2153
|
+
const previewUrl = parseFlag(args, "--preview");
|
|
2154
|
+
const productionUrl = parseFlag(args, "--production");
|
|
2155
|
+
if (!previewUrl || !productionUrl) {
|
|
2156
|
+
console.error(color("red", "error: pqcheck preview-diff requires --preview and --production URLs"));
|
|
2157
|
+
console.error(color("dim", "Usage: npx pqcheck preview-diff --preview https://preview-xyz.vercel.app --production https://example.com [--compare-transport] [--fail-on high] [--format pretty|json]"));
|
|
2158
|
+
process.exit(3);
|
|
2159
|
+
}
|
|
2160
|
+
if (!QP_API_KEY) {
|
|
2161
|
+
console.error(color("red", "error: pqcheck preview-diff requires CIPHERWAKE_API_KEY"));
|
|
2162
|
+
console.error(color("dim", "Generate a free key (100 calls/repo/mo) at https://cipherwake.io/account#api-keys"));
|
|
2163
|
+
console.error(color("dim", "Then: export CIPHERWAKE_API_KEY=qpk_<32-hex>"));
|
|
2164
|
+
console.error(color("dim", "Or use the GitHub Action with 'permissions: id-token: write' — no key needed."));
|
|
2165
|
+
process.exit(3);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
const compareTransport = args.includes("--compare-transport");
|
|
2169
|
+
const failOn = parseFlag(args, "--fail-on") || "high";
|
|
2170
|
+
const format = parseFlag(args, "--format") || "pretty";
|
|
2171
|
+
|
|
2172
|
+
// R66 (B4 / Q4.2 fix): policy_mode mirrors --fail-on intent.
|
|
2173
|
+
// `--fail-on none` (or `off`) → report-only; anything else → fail mode.
|
|
2174
|
+
// Server silently downgrades on Free tier; paid tier honors fail.
|
|
2175
|
+
// This makes the CLI exit-code semantics actually fire for paid users.
|
|
2176
|
+
const policyMode = ["none", "off", ""].includes(String(failOn).toLowerCase())
|
|
2177
|
+
? "report"
|
|
2178
|
+
: "fail";
|
|
2179
|
+
|
|
2180
|
+
let resp;
|
|
2181
|
+
try {
|
|
2182
|
+
resp = await fetch(`${API_BASE}/api/preview-diff`, {
|
|
2183
|
+
method: "POST",
|
|
2184
|
+
headers: {
|
|
2185
|
+
"Content-Type": "application/json",
|
|
2186
|
+
"Authorization": `Bearer ${QP_API_KEY}`,
|
|
2187
|
+
"User-Agent": `pqcheck-cli/${VERSION}`,
|
|
2188
|
+
},
|
|
2189
|
+
body: JSON.stringify({
|
|
2190
|
+
preview_url: previewUrl,
|
|
2191
|
+
production_url: productionUrl,
|
|
2192
|
+
compare_transport: compareTransport,
|
|
2193
|
+
fail_on: failOn,
|
|
2194
|
+
policy_mode: policyMode,
|
|
2195
|
+
}),
|
|
2196
|
+
});
|
|
2197
|
+
} catch (err) {
|
|
2198
|
+
console.error(color("red", `error: network failure calling /api/preview-diff: ${err.message}`));
|
|
2199
|
+
process.exit(3);
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
2203
|
+
await handleAuthError(resp);
|
|
2204
|
+
process.exit(3);
|
|
2205
|
+
}
|
|
2206
|
+
if (resp.status === 429) {
|
|
2207
|
+
const body = await safeJSON(resp);
|
|
2208
|
+
console.error(color("red", "error: Preview Diff API quota exceeded for this month"));
|
|
2209
|
+
if (body?.message) console.error(color("dim", body.message));
|
|
2210
|
+
process.exit(3);
|
|
2211
|
+
}
|
|
2212
|
+
if (!resp.ok) {
|
|
2213
|
+
const body = await safeJSON(resp);
|
|
2214
|
+
console.error(color("red", `error: /api/preview-diff returned ${resp.status}`));
|
|
2215
|
+
if (body?.message) console.error(color("dim", body.message));
|
|
2216
|
+
process.exit(3);
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
const result = await resp.json();
|
|
2220
|
+
const verdict = result?.result?.verdict || "pass";
|
|
2221
|
+
const summaryLines = result?.result?.summary_lines || [];
|
|
2222
|
+
|
|
2223
|
+
if (format === "json") {
|
|
2224
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2225
|
+
} else {
|
|
2226
|
+
console.log("");
|
|
2227
|
+
console.log(` ${color("bold", "Cipherwake Preview Trust Diff")}`);
|
|
2228
|
+
console.log(` ${color("dim", `preview=${previewUrl}`)}`);
|
|
2229
|
+
console.log(` ${color("dim", `production=${productionUrl}`)}`);
|
|
2230
|
+
const prevScore = result?.preview?.score;
|
|
2231
|
+
const prodScore = result?.production?.score;
|
|
2232
|
+
if (typeof prevScore === "number" || typeof prodScore === "number") {
|
|
2233
|
+
console.log(` ${color("dim", `DBR: preview=${typeof prevScore === "number" ? prevScore.toFixed(1) : "—"} · production=${typeof prodScore === "number" ? prodScore.toFixed(1) : "—"}`)}`);
|
|
2234
|
+
}
|
|
2235
|
+
console.log("");
|
|
2236
|
+
console.log(` ${color("bold", "Application surface:")}`);
|
|
2237
|
+
if (summaryLines.length === 0 || (summaryLines.length === 1 && /no meaningful/i.test(summaryLines[0]))) {
|
|
2238
|
+
console.log(` ${color("green", "✓ No meaningful application-surface changes detected.")}`);
|
|
2239
|
+
} else {
|
|
2240
|
+
for (const line of summaryLines) {
|
|
2241
|
+
const c = line.startsWith("+") ? "yellow" : line.startsWith("-") ? "red" : "dim";
|
|
2242
|
+
console.log(` ${color(c, line)}`);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
console.log("");
|
|
2246
|
+
const transport = result?.result?.transport || {};
|
|
2247
|
+
if (transport.preview_is_edge_hosted) {
|
|
2248
|
+
console.log(` ${color("dim", `Transport: preview is edge-hosted (${transport.preview_cert_issuer ?? "unknown"}) — informational only.`)}`);
|
|
2249
|
+
} else if (transport.preview_cert_issuer && transport.preview_cert_issuer !== transport.production_cert_issuer) {
|
|
2250
|
+
console.log(` ${color("dim", `Transport: cert issuer differs (${transport.production_cert_issuer ?? "—"} → ${transport.preview_cert_issuer ?? "—"}).`)}`);
|
|
2251
|
+
}
|
|
2252
|
+
console.log("");
|
|
2253
|
+
const verdictColor = verdict === "fail" ? "red" : verdict === "warn" ? "yellow" : "green";
|
|
2254
|
+
console.log(` Verdict: ${color(verdictColor, verdict.toUpperCase())} (max severity: ${result?.result?.max_severity || "info"})`);
|
|
2255
|
+
console.log(` Tier: ${result?.tier || "free"} · policy: ${result?.policy_mode_effective || "report"}`);
|
|
2256
|
+
if (result.upgrade_hint) {
|
|
2257
|
+
console.log("");
|
|
2258
|
+
console.log(` ${color("dim", "💡 " + result.upgrade_hint)}`);
|
|
2259
|
+
}
|
|
2260
|
+
console.log("");
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
if (verdict === "fail") process.exit(2);
|
|
2264
|
+
if (verdict === "warn") process.exit(1);
|
|
2265
|
+
process.exit(0);
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2126
2268
|
/**
|
|
2127
2269
|
* Convert /api/trust-diff response to SARIF 2.1.0 for upload via
|
|
2128
2270
|
* github/codeql-action/upload-sarif@v3. Each delta becomes a result with
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pqcheck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "HTTPS posture scanner with Trust Diff for CI, vendor lockfile + drift alerts, cross-tenant key map, and HNDL/quantum-decryption risk scoring. Free, no signup.",
|
|
3
|
+
"version": "0.14.1",
|
|
4
|
+
"description": "HTTPS posture scanner with Preview Deploy Trust Diff for PRs, Trust Diff for CI, vendor lockfile + drift alerts, cross-tenant key map, and HNDL/quantum-decryption risk scoring. Free, no signup.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"post-quantum",
|
|
7
7
|
"cryptography",
|