pqcheck 0.14.2 → 0.15.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 CHANGED
@@ -25,8 +25,49 @@ The same scanner that powers [cipherwake.io](https://cipherwake.io), the browser
25
25
  | `npx pqcheck preview-diff --preview <URL> --production <URL>` | Compare a Vercel/Netlify preview deployment URL to production. Surfaces new third-party scripts, header regressions, and DBR score drops *inside the PR*, before merge. |
26
26
  | `npx pqcheck vendors export/check/sync <domain>` | Vendor lockfile (`cipherwake.vendors.json`) + CI gate that exits non-zero when a new third-party origin appears. Like `package-lock.json` for vendor scripts. |
27
27
  | `npx pqcheck onboard <domain>` | One command: scan → scaffold the GitHub Action → capture a vendor lockfile → set a baseline → commit + push. Zero copy-paste from docs. |
28
+ | **`npx pqcheck guard --domain <D> -- <cmd>`** 🆕 | **Deploy guard wrapper.** Wraps any deploy command. Runs `deploy-check` first; conditionally runs `<cmd>` based on `ship_decision`. Modes: `--gate-mode balanced` (default) / `advisory` / `strict`. ONE command instead of two — the strongest single artifact for AI-coder workflows because the AI never has to remember to chain check + deploy. |
29
+ | **`npx pqcheck protocol install`** 🆕 | **Opt-in installer** for the AI Coder Protocol — appends the pre-deploy verification rule to your `CLAUDE.md` / `.cursorrules` / `.aider.conf.yml` with explicit consent (Rule 17). One upfront question (auto / manual / no). Never silent writes. |
30
+ | **`npx pqcheck setup --auto --domain <D>`** 🆕 | **One-command full setup for every AI coder.** Installs (idempotently): GitHub Action workflow, AI Coder Protocol across all detected rules files (Claude / Cursor / Copilot / Aider / Windsurf / Continue / Cline / AGENTS.md) using fenced markers (`<!-- CIPHERWAKE_AI_CODER_PROTOCOL_START/END -->`), git pre-push hook, Claude Code statusLine + chat-hook (PostToolUse Bash). Skip flags available. Backups taken before any `~/.claude/settings.json` write. Audit trail at `~/.config/cipherwake/install-prefs.json`; install manifest at `~/.config/cipherwake/install-manifest.json`. |
31
+ | **`npx pqcheck setup --plan --domain <D>`** 🆕 | **Dry-run mode.** Prints every file change `--auto` would make (target paths + operation type: create / append-markered / deep-merge / backup-first) without writing anything. Run this first when you're not sure what `--auto` will touch. |
32
+ | **`npx pqcheck debug-network`** 🆕 | **Connectivity diagnostic.** Probes cipherwake.io API, homepage, crt.sh upstream, and the direct Vercel URL (bypassing Cloudflare). Reports HTTP status + timing per hop. Use when "scan hung" / "command not found" / corporate proxy issues come up — surfaces the actual broken hop with an actionable cause list. |
33
+ | **`--ai` flag** (any of the above) | **AI Coder Mode** (0.15.0). Three-layer output (banner / body / structured `CIPHERWAKE_AI_GUARD_RESULT` block) tuned for Claude Code / Cursor / Aider / Zed. Includes a `ship_decision=pass\|review\|block` field your AI coworker parses to decide whether to announce the deploy, ask you, or revert. See [/methodology/ai-coder-mode](https://cipherwake.io/methodology/ai-coder-mode). |
28
34
 
29
- Free tier covers all of the above within 100 Trust Diff calls/month per repo (paid lifts to 1K / 10K / 50K). Single-domain scans (`npx pqcheck <domain>`) are unmetered.
35
+ Free tier covers all of the above within 100 Trust Diff calls/month per repo via OIDC. **Founder Pro** ($19.99/mo, locked while subscription active) raises that to 5,000 calls/month + unlocks custom thresholds, vendor lockfile, CI fail rules, and 5 watched domains. Single-domain scans (`npx pqcheck <domain>`) are anonymous + rate-limited per IP — no account or key needed. `npx pqcheck deploy-check <domain> --ai` also works fully anonymously for first-deploy gating.
36
+
37
+ ### AI Coder Mode in 30 seconds
38
+
39
+ ```bash
40
+ npx pqcheck cipherwake.io --ai
41
+ ```
42
+
43
+ Output:
44
+
45
+ ```
46
+ ◆ cipherwake · scan · ⚠ REVIEW · cipherwake.io · DBR 4.1 C · HIGH · ship_decision=review
47
+
48
+ Top finding:
49
+ [HIGH] ECDHE-only — quantum-vulnerable key exchange
50
+
51
+ Why it matters:
52
+ Forward-secret against classical attackers. Shor's algorithm decrypts
53
+ recorded handshakes once a CRQC exists.
54
+
55
+ Recommended next action:
56
+ Review finding above and decide if it was intentional.
57
+ View full report: https://cipherwake.io/r/cipherwake.io
58
+ Re-scan with --fresh after fix: npx pqcheck cipherwake.io --fresh --ai
59
+
60
+ CIPHERWAKE_AI_GUARD_RESULT
61
+ status=review
62
+ domain=cipherwake.io
63
+ ship_decision=review
64
+ max_severity=high
65
+ top_issue=tls.ecdhe_only_quantum_vulnerable
66
+ advisory_only=true
67
+ END_CIPHERWAKE_AI_GUARD_RESULT
68
+ ```
69
+
70
+ The structured block is what your AI coworker (Claude / Cursor / Aider / Zed) parses to decide whether to announce the deploy, ask you, or revert. Exit code in `--ai` mode reflects `ship_decision`: `0` pass · `1` review · `2` block.
30
71
 
31
72
  ---
32
73
 
@@ -56,7 +97,7 @@ git push
56
97
 
57
98
  That's it. The scaffolded workflow includes `permissions: id-token: write`, so the runner mints a signed OIDC token on each run and Cipherwake meters per repo — no secret to manage. Open a PR and Cipherwake comments inline when cert / SPKI / HSTS / CSP / DMARC / vendor scripts drift since your baseline.
58
99
 
59
- **Need higher limits?** Paid tiers (Starter $29/mo · Growth $79/mo · Scale $199/mo) lift the per-repo quota to 1,000 / 10,000 / 50,000 calls/month. Generate an API key at [/account#api-keys](https://cipherwake.io/account#api-keys), then add it as the repo secret `CIPHERWAKE_API_KEY`. The Action uses the secret when present and falls back to OIDC when not — no code change needed to upgrade.
100
+ **Need higher limits?** **Founder Pro ($19.99/mo)** lifts the per-repo quota to 5,000 calls/month and unlocks custom thresholds, the approved-vendor allowlist, vendor lockfile, CI fail rules, and 5 watched domains. Generate an API key at [/account#api-keys](https://cipherwake.io/account#api-keys), then add it as the repo secret `CIPHERWAKE_API_KEY`. The Action uses the secret when present and falls back to OIDC when not — no code change needed to upgrade. *Founder pricing is locked while your subscription remains active.*
60
101
 
61
102
  **Want more?**
62
103
  - Pre-commit hook: `npx pqcheck deploy-check <domain>` before every deploy
@@ -77,7 +118,7 @@ npx pqcheck trust-diff mycompany.com --baseline last-week --fail-on high
77
118
 
78
119
  Compares today's public trust posture against a configured baseline (`last-week`, `last-month`, or a saved per-branch baseline). Surfaces cert / SPKI / HSTS / CSP / DMARC / vendor-script drift since the baseline and gates the PR by severity. SARIF output uploads to GitHub Code Scanning. Pair with the [GitHub Action](https://github.com/cipherwakelabs/pqcheck/tree/main/action) `mode: trust-diff` for one-line CI integration.
79
120
 
80
- Exit codes: `0` pass · `1` warn · `2` fail · `3` error. Free tier (100 calls/repo/mo via GitHub Actions OIDC, no API key required) silently downgrades fail → report; Starter+ honors `--fail-on` for real CI gating.
121
+ Exit codes: `0` pass · `1` warn · `2` fail · `3` error. Free tier (100 calls/repo/mo via GitHub Actions OIDC, no API key required) silently downgrades fail → report; **Founder Pro** honors `--fail-on` for real CI gating.
81
122
 
82
123
  ### Preview Trust Diff — PR-time URL-vs-URL comparison
83
124
 
@@ -131,7 +172,7 @@ Like `package-lock.json`, but for the third-party scripts that load on your doma
131
172
  ```bash
132
173
  npx pqcheck vendors export mycompany.com # write cipherwake.vendors.json
133
174
  npx pqcheck vendors check mycompany.com # CI gate; exit 4 on new origins
134
- npx pqcheck vendors sync mycompany.com # Starter+ — pull dashboard allowlist
175
+ npx pqcheck vendors sync mycompany.com # Founder Pro — pull dashboard allowlist
135
176
  ```
136
177
 
137
178
  `pqcheck deps` also surfaces a one-line site-wide **CSP verdict** above the supply-chain table (`✗ No CSP enforcement` / `⚠ CSP is permissive` / `✓ Strict CSP enforced`) and friendly vendor labels (`New Relic · errors` / `Cloudflare · cdn` / `Adobe Fonts · fonts`) instead of raw hostnames. Same data shape ships on `/r/<domain>` and in the browser extension.
@@ -182,8 +223,14 @@ npx pqcheck init Interactive scaffold for .github/w
182
223
  npx pqcheck release-checklist [domain] Pre-release trust checklist (markdown, offline)
183
224
  npx pqcheck vendors export <domain> Write cipherwake.vendors.json from observed third-party scripts
184
225
  npx pqcheck vendors check <domain> CI gate; exit 4 on new origins not in lockfile
185
- npx pqcheck vendors sync <domain> Pull dashboard allowlist into lockfile (Starter+, needs API key)
226
+ npx pqcheck vendors sync <domain> Pull dashboard allowlist into lockfile (Founder Pro, needs API key)
186
227
  npx pqcheck watch <domain> Add domain to your watched list (needs CIPHERWAKE_API_KEY)
228
+ npx pqcheck guard --domain <D> -- <cmd> AI Coder Mode (0.15.0) — wrap any deploy command with a Trust Diff gate
229
+ npx pqcheck deploy-check <D> --ai AI Coder Mode (0.15.0) — frictionless first-deploy, anonymous, emits CIPHERWAKE_AI_GUARD_RESULT block
230
+ npx pqcheck setup --auto --domain <D> AI Coder Mode (0.15.0) — one-command install across CLAUDE.md/.cursorrules/.github + git pre-push hook + statusline
231
+ npx pqcheck setup --plan --domain <D> AI Coder Mode (0.15.0) — dry-run: print every file change before --auto writes anything
232
+ npx pqcheck protocol install --auto AI Coder Mode (0.15.0) — append AI Coder Protocol to detected rules files (idempotent, fenced markers)
233
+ npx pqcheck debug-network Probe upstream connectivity (cipherwake.io / crt.sh / Vercel) — for "scan hung" diagnosis
187
234
  ```
188
235
 
189
236
  ### Multi-domain
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // cipherwake-chat-hook — Claude Code PostToolUse hook
4
+ // =============================================================================
5
+ // Reads stdin (tool event JSON from Claude Code), checks if the tool was a
6
+ // pqcheck-related Bash command, reads the latest scan state, emits a
7
+ // systemMessage to Claude Code chat.
8
+ //
9
+ // Wire it up by adding to ~/.claude/settings.json:
10
+ //
11
+ // "hooks": {
12
+ // "PostToolUse": [{
13
+ // "matcher": "Bash",
14
+ // "hooks": [{
15
+ // "type": "command",
16
+ // "command": "npx cipherwake-chat-hook"
17
+ // }]
18
+ // }]
19
+ // }
20
+ //
21
+ // `pqcheck setup --auto` does this for you (idempotently, merging with any
22
+ // existing hook configs per CLAUDE.md Rule 17).
23
+ //
24
+ // Behavior:
25
+ // * Only emits a message if the tool was Bash + the command invoked pqcheck
26
+ // * Only emits if last-scan.json was updated within the last 60s (i.e. this
27
+ // pqcheck invocation actually changed state) — avoids spamming chat for
28
+ // stale state
29
+ // * Single line output for status-bar-style readability
30
+ // =============================================================================
31
+
32
+ import { readFileSync } from "node:fs";
33
+ import { join } from "node:path";
34
+ import { homedir } from "node:os";
35
+
36
+ // Hooks receive event JSON on stdin. If missing / malformed, exit silently.
37
+ let toolEvent;
38
+ try {
39
+ toolEvent = JSON.parse(readFileSync(0, "utf8"));
40
+ } catch {
41
+ process.exit(0);
42
+ }
43
+
44
+ // Only react to Bash tool uses that invoked pqcheck or cipherwake-statusline.
45
+ if (toolEvent?.tool_name !== "Bash") {
46
+ process.exit(0);
47
+ }
48
+ const command = String(toolEvent.tool_input?.command || "");
49
+ const isPqcheck =
50
+ /\bpqcheck\b/.test(command) ||
51
+ /\bcipherwake-statusline\b/.test(command);
52
+ if (!isPqcheck) {
53
+ process.exit(0);
54
+ }
55
+
56
+ // Read the last-scan state. If missing, exit silently.
57
+ let state;
58
+ try {
59
+ state = JSON.parse(
60
+ readFileSync(join(homedir(), ".config", "cipherwake", "last-scan.json"), "utf8"),
61
+ );
62
+ } catch {
63
+ process.exit(0);
64
+ }
65
+
66
+ // Only emit if the state was updated recently (<60s). Otherwise we'd narrate
67
+ // stale state on every unrelated Bash command, which would be obnoxious.
68
+ const writtenAt = new Date(state.written_at).getTime();
69
+ if (!Number.isFinite(writtenAt)) process.exit(0);
70
+ if (Date.now() - writtenAt > 60_000) process.exit(0);
71
+
72
+ const sd = state.ship_decision || "—";
73
+ const emoji = sd === "pass" ? "✓" : sd === "block" ? "✗" : "⚠";
74
+
75
+ const parts = [`◆ Cipherwake: ${emoji} ${state.domain} ship_decision=${sd}`];
76
+ if (typeof state.score === "number") parts.push(`DBR ${state.score.toFixed(1)}${state.grade ? " " + state.grade : ""}`);
77
+ if (state.max_severity && state.max_severity !== "none") parts.push(String(state.max_severity).toUpperCase());
78
+ if (state.top_issue && state.top_issue !== "none") parts.push(`top: ${state.top_issue}`);
79
+
80
+ const message = parts.join(" · ");
81
+
82
+ // Output JSON to stdout — Claude Code reads the `systemMessage` field and
83
+ // displays it to the user in the chat scrollback.
84
+ process.stdout.write(
85
+ JSON.stringify({
86
+ systemMessage: message,
87
+ suppressOutput: true,
88
+ }),
89
+ );
90
+ process.exit(0);
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // cipherwake-statusline — reads ~/.config/cipherwake/last-scan.json and outputs
4
+ // a single-line summary for AI-coder status surfaces.
5
+ // =============================================================================
6
+ // Designed for Claude Code's `statusLine` setting (a config-level hook that
7
+ // runs any shell command and renders its stdout in the persistent status line).
8
+ // One-line install:
9
+ //
10
+ // add to ~/.claude/settings.json:
11
+ // { "statusLine": { "type": "command", "command": "npx cipherwake-statusline" } }
12
+ //
13
+ // Cipherwake never modifies your settings.json — paste the line yourself per
14
+ // CLAUDE.md Rule 17 (consolidated consent for any change outside our own
15
+ // config dir).
16
+ //
17
+ // The script is dependency-free + fast (<50ms on cold start) because Claude
18
+ // Code calls it on every turn. Reads a single file, formats one line, exits.
19
+ // =============================================================================
20
+
21
+ import { readFileSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ import { homedir } from "node:os";
24
+
25
+ const STATE_FILE = join(homedir(), ".config", "cipherwake", "last-scan.json");
26
+ const STALE_THRESHOLD_HOURS = 24;
27
+
28
+ const C = {
29
+ reset: "\x1b[0m",
30
+ bold: "\x1b[1m",
31
+ dim: "\x1b[2m",
32
+ green: "\x1b[32m",
33
+ yellow: "\x1b[33m",
34
+ red: "\x1b[31m",
35
+ cyan: "\x1b[36m",
36
+ };
37
+
38
+ function formatAge(iso) {
39
+ if (!iso) return "—";
40
+ const ms = Date.now() - new Date(iso).getTime();
41
+ if (ms < 0) return "just now";
42
+ if (ms < 60_000) return "just now";
43
+ const min = Math.floor(ms / 60_000);
44
+ if (min < 60) return `${min}m ago`;
45
+ const hr = Math.floor(min / 60);
46
+ if (hr < 24) return `${hr}h ago`;
47
+ const d = Math.floor(hr / 24);
48
+ return `${d}d ago`;
49
+ }
50
+
51
+ function ageHours(iso) {
52
+ if (!iso) return Infinity;
53
+ return (Date.now() - new Date(iso).getTime()) / (60 * 60 * 1000);
54
+ }
55
+
56
+ // --no-color / NO_COLOR support per https://no-color.org
57
+ const noColor = process.argv.includes("--no-color") || process.env.NO_COLOR;
58
+ function c(color, str) {
59
+ if (noColor) return str;
60
+ return `${color}${str}${C.reset}`;
61
+ }
62
+
63
+ let state;
64
+ try {
65
+ state = JSON.parse(readFileSync(STATE_FILE, "utf8"));
66
+ } catch {
67
+ // No scan yet — render an onboarding hint so first-time users learn the
68
+ // command. Use the dim color so it doesn't dominate the status line.
69
+ process.stdout.write(
70
+ c(C.dim, "◆ cipherwake · no scan yet · ") + c(C.cyan, "npx pqcheck <domain> --ai")
71
+ );
72
+ process.exit(0);
73
+ }
74
+
75
+ const { domain, score, grade, ship_decision, written_at, max_severity, kind } = state;
76
+ const age = ageHours(written_at);
77
+
78
+ if (age > STALE_THRESHOLD_HOURS) {
79
+ const stalePart = c(C.dim, `◆ ${domain || "cipherwake"} · stale (${formatAge(written_at)})`);
80
+ const hintPart = c(C.cyan, `npx pqcheck ${domain || "<domain>"} --ai`);
81
+ process.stdout.write(`${stalePart} · ${hintPart}`);
82
+ process.exit(0);
83
+ }
84
+
85
+ const symbolByDecision = { pass: "✓", review: "⚠", block: "✗" };
86
+ const colorByDecision = { pass: C.green, review: C.yellow, block: C.red };
87
+ const symbol = symbolByDecision[ship_decision] || "·";
88
+ const cdec = colorByDecision[ship_decision] || C.dim;
89
+
90
+ const dbrStr = typeof score === "number" ? `DBR ${score.toFixed(1)}` : "";
91
+ const gradeStr = grade ? grade : "";
92
+ const sevStr = max_severity && max_severity !== "none"
93
+ ? `· ${String(max_severity).toUpperCase()}`
94
+ : "";
95
+ const kindStr = kind && kind !== "scan" ? `· ${kind}` : "";
96
+
97
+ // Final layout (color-coded; ANSI stripped under --no-color / NO_COLOR=1):
98
+ // ◆ <domain> ✓|⚠|✗ ship_decision · DBR X.X grade · SEVERITY · kind · age
99
+ process.stdout.write(
100
+ c(cdec, "◆") +
101
+ " " +
102
+ c(C.bold, domain || "cipherwake") +
103
+ " " +
104
+ c(cdec, `${symbol} ${(ship_decision || "—").toUpperCase()}`) +
105
+ " " +
106
+ c(C.dim, `· ${dbrStr}${gradeStr ? " " + gradeStr : ""} ${sevStr} ${kindStr} · ${formatAge(written_at)}`)
107
+ );