pqcheck 0.13.1 → 0.14.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 +2 -2
- package/bin/pqcheck.js +147 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Wire Cipherwake into your CI so every PR gets a Trust Diff comment when your dom
|
|
|
22
22
|
npx pqcheck onboard cipherwake.io
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
That runs in sequence: scan your domain → write the GitHub Action workflow → capture a vendor lockfile → generate a release checklist → commit + push. **No API key, no repo secret.** The scaffolded workflow uses GitHub Actions OIDC (`id-token: write`) to authenticate to Cipherwake — Free includes
|
|
25
|
+
That runs in sequence: scan your domain → write the GitHub Action workflow → capture a vendor lockfile → generate a release checklist → commit + push. **No API key, no repo secret.** The scaffolded workflow uses GitHub Actions OIDC (`id-token: write`) to authenticate to Cipherwake — Free includes 100 Trust Diff calls/month per repo, no setup required.
|
|
26
26
|
|
|
27
27
|
**Or step-by-step if you prefer:**
|
|
28
28
|
|
|
@@ -49,7 +49,7 @@ That's it. The scaffolded workflow includes `permissions: id-token: write`, so t
|
|
|
49
49
|
|
|
50
50
|
## What's new in 0.12.0
|
|
51
51
|
|
|
52
|
-
**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
|
|
52
|
+
**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.
|
|
53
53
|
|
|
54
54
|
- `pqcheck init` — interactive scaffold for `.github/workflows/cipherwake.yml`. Prompts for domain, fail-on severity, baseline. No copy-paste from docs required.
|
|
55
55
|
- `pqcheck deploy-check <domain>` — pre-deploy Trust Diff gate with deploy-friendly framing. Uses last-scan as default baseline. Same exit semantics as `trust-diff`.
|
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
|
|
@@ -2665,7 +2807,7 @@ async function runInitCommand(args) {
|
|
|
2665
2807
|
console.log(` ${color("bold", "Next steps:")}`);
|
|
2666
2808
|
console.log("");
|
|
2667
2809
|
console.log(` ${color("dim", "1.")} Generate a Cipherwake API key at ${color("violet", "https://cipherwake.io/account#api-keys")}`);
|
|
2668
|
-
console.log(` ${color("dim", "Free tier:
|
|
2810
|
+
console.log(` ${color("dim", "Free tier: 100 Trust Diff calls/month per repo")}`);
|
|
2669
2811
|
console.log("");
|
|
2670
2812
|
console.log(` ${color("dim", "2.")} Add it as a repo secret:`);
|
|
2671
2813
|
console.log(` ${color("dim", "Settings → Secrets and variables → Actions → New repository secret")}`);
|
|
@@ -2701,7 +2843,7 @@ function renderTrustDiffWorkflow({ domain, failOn, baseline }) {
|
|
|
2701
2843
|
# posture regresses vs the baseline (cert / SPKI / vendor scripts / HSTS / CSP /
|
|
2702
2844
|
# DMARC / HNDL).
|
|
2703
2845
|
#
|
|
2704
|
-
# Free tier:
|
|
2846
|
+
# Free tier: 100 Trust Diff calls/month per repo (OIDC-metered).
|
|
2705
2847
|
# Methodology: https://cipherwake.io/methodology/
|
|
2706
2848
|
# Action source: https://github.com/cipherwakelabs/pqcheck
|
|
2707
2849
|
|
|
@@ -2715,7 +2857,7 @@ on:
|
|
|
2715
2857
|
|
|
2716
2858
|
permissions:
|
|
2717
2859
|
contents: read
|
|
2718
|
-
id-token: write # required for OIDC-based metering (Free=
|
|
2860
|
+
id-token: write # required for OIDC-based metering (Free=100 calls/repo/mo, no API key needed)
|
|
2719
2861
|
security-events: write # required for SARIF upload to Code Scanning
|
|
2720
2862
|
pull-requests: write # required for sticky PR comment (Action v3.1+)
|
|
2721
2863
|
|
|
@@ -2762,7 +2904,7 @@ async function prompt(question) {
|
|
|
2762
2904
|
// pre-build, Netlify build commands, custom CD scripts)
|
|
2763
2905
|
//
|
|
2764
2906
|
// Exit codes match trust-diff: 0 pass · 1 warn · 2 fail · 3 error.
|
|
2765
|
-
// Consumes the same Free
|
|
2907
|
+
// Consumes the same Free 100 Trust Diff calls/month per repo quota.
|
|
2766
2908
|
// =============================================================================
|
|
2767
2909
|
|
|
2768
2910
|
async function runDeployCheckCommand(args) {
|
|
@@ -3310,7 +3452,7 @@ async function runOnboardCommand(args) {
|
|
|
3310
3452
|
// to paste the key as a GitHub repo secret. With Action v3.2 + OIDC repo
|
|
3311
3453
|
// metering, the scaffolded workflow has `permissions: { id-token: write }`
|
|
3312
3454
|
// and the action fetches a GitHub-signed token automatically — no key, no
|
|
3313
|
-
// secret, no browser hop. Free tier is
|
|
3455
|
+
// secret, no browser hop. Free tier is 100 calls/repo/mo, enforced server-
|
|
3314
3456
|
// side via the `meter_gh_action_call` RPC against `gh_action_repo_quota`.
|
|
3315
3457
|
// For higher limits, the user links this repo to a paid account at /account
|
|
3316
3458
|
// (one-time OAuth) — still no API key in CI.
|
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.0",
|
|
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",
|