pqcheck 0.16.18 → 0.16.20
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 +1 -1
- package/bin/cipherwake-statusline.js +46 -0
- package/bin/pqcheck.js +87 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://www.npmjs.com/package/pqcheck)
|
|
9
9
|
[](./LICENSE)
|
|
10
10
|
|
|
11
|
-
> **Latest: v0.16.
|
|
11
|
+
> **Latest: v0.16.20** — `pqcheck deploy-check --ai` now adds an absolute posture grade (A+ → F, strict SSL-Labs rubric over CSP / HSTS / X-Frame-Options / X-Content-Type-Options / Referrer-Policy / Permissions-Policy + `x-powered-by` leak) alongside the existing drift-only `ship_decision`. Grade D/F sets `posture_decision=block` so AI coders gate posture failures even on a clean drift. Adds `scope_note` (drift pass ≠ functional verification) and surfaces ready-to-paste fix snippets (Next.js / vercel.json / Express). Watched-domain monitoring also gains `posture_regression` + `cert_expiring` alerts. [Full changelog →](./CHANGELOG.md)
|
|
12
12
|
|
|
13
13
|
## Two ways to use it
|
|
14
14
|
|
|
@@ -21,10 +21,56 @@
|
|
|
21
21
|
import { readFileSync, existsSync } from "node:fs";
|
|
22
22
|
import { join, dirname } from "node:path";
|
|
23
23
|
import { homedir } from "node:os";
|
|
24
|
+
import { execSync } from "node:child_process";
|
|
24
25
|
|
|
25
26
|
const GLOBAL_STATE_FILE = join(homedir(), ".config", "cipherwake", "last-scan.json");
|
|
26
27
|
const STALE_THRESHOLD_HOURS = 24;
|
|
27
28
|
|
|
29
|
+
// v0.16.19 — `--prepend=<command>` flag for statusLine composition. Claude
|
|
30
|
+
// Code's `statusLine.command` only supports ONE command, which means two
|
|
31
|
+
// tools (e.g. Cipherwake + PinnedAI) both writing their own statusLine
|
|
32
|
+
// silently clobber each other depending on install order. When a customer
|
|
33
|
+
// runs both, they only see whichever installed last. With --prepend, the
|
|
34
|
+
// Cipherwake binary runs the other tool's command first, joins its output
|
|
35
|
+
// with a separator, and renders Cipherwake's own line after. Result:
|
|
36
|
+
// `[pinned output] · ◆ Cipherwake · domain ✓ PASS · 5m ago` — both
|
|
37
|
+
// surfaces visible in the single statusLine slot.
|
|
38
|
+
//
|
|
39
|
+
// pqcheck setup detects an existing statusLine.command at install time
|
|
40
|
+
// and writes the wrapper form automatically:
|
|
41
|
+
// "command": "npx --package=pqcheck@latest cipherwake-statusline --prepend='<prior-command>'"
|
|
42
|
+
//
|
|
43
|
+
// If the prior command errors (or the tool was uninstalled), this binary
|
|
44
|
+
// swallows the failure and just renders Cipherwake's part — never causes
|
|
45
|
+
// the whole statusLine to break.
|
|
46
|
+
const PREPEND_SEPARATOR = " · ";
|
|
47
|
+
const prependArg = process.argv.find((a) => a.startsWith("--prepend="));
|
|
48
|
+
if (prependArg) {
|
|
49
|
+
const prependCmd = prependArg.slice("--prepend=".length);
|
|
50
|
+
if (prependCmd) {
|
|
51
|
+
try {
|
|
52
|
+
// 5s soft timeout — statusLine rendering must stay snappy. If the
|
|
53
|
+
// other tool hangs, we cut it off and continue with Cipherwake's part.
|
|
54
|
+
// stdio: ignore on stderr so a noisy prior tool doesn't pollute the
|
|
55
|
+
// bar; we want only its rendered stdout output.
|
|
56
|
+
const out = execSync(prependCmd, {
|
|
57
|
+
encoding: "utf8",
|
|
58
|
+
timeout: 5_000,
|
|
59
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
60
|
+
env: process.env,
|
|
61
|
+
}).trim();
|
|
62
|
+
if (out) {
|
|
63
|
+
process.stdout.write(out);
|
|
64
|
+
process.stdout.write(PREPEND_SEPARATOR);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Prepend command failed (uninstalled, timeout, errored). Skip
|
|
68
|
+
// silently — never break the whole statusLine because of a
|
|
69
|
+
// composition partner.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
28
74
|
// v0.16.6 — project-aware state lookup. Walk up from CWD looking for a
|
|
29
75
|
// repo-local `.cipherwake/last-scan.json`. This way each project shows
|
|
30
76
|
// its own last scan, and switching projects doesn't bleed the previous
|
package/bin/pqcheck.js
CHANGED
|
@@ -495,6 +495,22 @@ async function runOneScan({ domain, format, quiet, threshold, webhookUrl, multi,
|
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
+
// R86 — surface absolute posture grade + remediation alongside drift verdict.
|
|
499
|
+
// The grade= field carries DBR grade; posture_grade= is the new absolute
|
|
500
|
+
// posture (A+/A/B/C/D/F based on header presence/strength).
|
|
501
|
+
// posture_decision separates the absolute-state routing signal from the
|
|
502
|
+
// drift-based ship_decision so a weak-but-unchanged site gets a review
|
|
503
|
+
// nudge distinct from drift.
|
|
504
|
+
const posture = report.posture || null;
|
|
505
|
+
const postureFields = posture ? {
|
|
506
|
+
posture_grade: posture.grade,
|
|
507
|
+
posture_score: posture.score,
|
|
508
|
+
posture_decision: posture.decision,
|
|
509
|
+
posture_missing: (posture.missing || []).join(",") || "none",
|
|
510
|
+
posture_leaks: (posture.info_leaks || []).join("; ") || "none",
|
|
511
|
+
posture_findings_count: (posture.findings || []).length,
|
|
512
|
+
posture_fixes_count: (posture.fixes || []).length,
|
|
513
|
+
} : {};
|
|
498
514
|
console.log(formatAiFooterBlock({
|
|
499
515
|
status: shipDecision,
|
|
500
516
|
domain,
|
|
@@ -509,6 +525,13 @@ async function runOneScan({ domain, format, quiet, threshold, webhookUrl, multi,
|
|
|
509
525
|
findings_critical: findings.filter((f) => severityRank(f.severity) === 4).length,
|
|
510
526
|
scanned_at: new Date().toISOString(),
|
|
511
527
|
advisory_only: "true",
|
|
528
|
+
// R85 (2026-06-03) — scope honesty disclaimer. pass = trust surface
|
|
529
|
+
// stable, NOT app functionality. Agents that follow the protocol must
|
|
530
|
+
// see this so they don't announce a deploy whose signup is broken
|
|
531
|
+
// (the exact bug the user reported on socialideagen 2026-06-02).
|
|
532
|
+
scope: "trust_surface_drift",
|
|
533
|
+
scope_note: "ship_decision=pass means public trust surface stable. Does NOT verify app functionality. Pair with functional checks (e.g. Playwright e2e) for full deploy safety.",
|
|
534
|
+
...postureFields,
|
|
512
535
|
}));
|
|
513
536
|
console.log("");
|
|
514
537
|
|
|
@@ -3400,6 +3423,20 @@ async function runTrustDiffCommand(args) {
|
|
|
3400
3423
|
console.log(color("dim", " Run with --verbose to see all verified signals."));
|
|
3401
3424
|
}
|
|
3402
3425
|
|
|
3426
|
+
// R86 — posture grade + remediation alongside drift. trust-diff API returns
|
|
3427
|
+
// result.current_report which contains the full scan; if so, read posture
|
|
3428
|
+
// from it. Otherwise gracefully omit (older API responses without posture).
|
|
3429
|
+
const currentReport = result.current_report || result.report || null;
|
|
3430
|
+
const posture = currentReport?.posture || null;
|
|
3431
|
+
const postureFields = posture ? {
|
|
3432
|
+
posture_grade: posture.grade,
|
|
3433
|
+
posture_score: posture.score,
|
|
3434
|
+
posture_decision: posture.decision,
|
|
3435
|
+
posture_missing: (posture.missing || []).join(",") || "none",
|
|
3436
|
+
posture_leaks: (posture.info_leaks || []).join("; ") || "none",
|
|
3437
|
+
posture_findings_count: (posture.findings || []).length,
|
|
3438
|
+
posture_fixes_count: (posture.fixes || []).length,
|
|
3439
|
+
} : {};
|
|
3403
3440
|
console.log(formatAiFooterBlock({
|
|
3404
3441
|
status: shipDecision,
|
|
3405
3442
|
domain,
|
|
@@ -3417,6 +3454,13 @@ async function runTrustDiffCommand(args) {
|
|
|
3417
3454
|
quota_limit: result.quota?.monthly_limit ?? "",
|
|
3418
3455
|
scanned_at: new Date().toISOString(),
|
|
3419
3456
|
advisory_only: "true",
|
|
3457
|
+
// R85 — scope honesty disclaimer (per build brief 2026-06-03).
|
|
3458
|
+
// ship_decision=pass means trust surface stable, NOT app functions.
|
|
3459
|
+
// Agents must see this so they don't announce a deploy whose signup
|
|
3460
|
+
// is broken (the exact bug socialideagen had on 2026-06-02).
|
|
3461
|
+
scope: "trust_surface_drift",
|
|
3462
|
+
scope_note: "ship_decision=pass means public trust surface stable. Does NOT verify app functionality. Pair with functional checks for full deploy safety.",
|
|
3463
|
+
...postureFields,
|
|
3420
3464
|
}));
|
|
3421
3465
|
console.log("");
|
|
3422
3466
|
|
|
@@ -6317,19 +6361,54 @@ async function runSetupCommand(args) {
|
|
|
6317
6361
|
settings = JSON.parse(raw);
|
|
6318
6362
|
existed = true;
|
|
6319
6363
|
} catch { /* will create */ }
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6364
|
+
// v0.16.19 — statusLine composition. Previous behavior was: if any
|
|
6365
|
+
// statusLine.command already existed, SKIP. That was safe (never
|
|
6366
|
+
// clobbered other tools) but invisible — a user with PinnedAI's
|
|
6367
|
+
// statusline installed would never see Cipherwake's after running
|
|
6368
|
+
// `pqcheck setup`. Now: detect the prior command and wrap it via
|
|
6369
|
+
// `cipherwake-statusline --prepend=<prior>` so both render in the
|
|
6370
|
+
// single statusLine slot. The prepend logic in cipherwake-statusline.js
|
|
6371
|
+
// swallows prepend-command failures silently, so this composition
|
|
6372
|
+
// survives the user later uninstalling the partner tool.
|
|
6373
|
+
//
|
|
6374
|
+
// Skip the wrap when the existing command IS already our wrapper
|
|
6375
|
+
// (idempotent re-install — don't recursively nest).
|
|
6376
|
+
const CIPHERWAKE_CMD_PREFIX = "npx --package=pqcheck@latest cipherwake-statusline";
|
|
6377
|
+
const priorCmd = (existed && settings.statusLine && typeof settings.statusLine === "object")
|
|
6378
|
+
? String(settings.statusLine.command || "").trim()
|
|
6379
|
+
: "";
|
|
6380
|
+
const priorIsOurs = priorCmd.startsWith("npx --package=pqcheck") && priorCmd.includes("cipherwake-statusline");
|
|
6381
|
+
|
|
6382
|
+
if (priorIsOurs) {
|
|
6383
|
+
console.log(color("dim", ` ⊝ ${displayPath} statusLine already points at cipherwake-statusline — leaving alone`));
|
|
6384
|
+
installSummary.push({ component: "Claude Code statusLine", path: settingsPath, status: "skipped-already-ours" });
|
|
6325
6385
|
} else {
|
|
6326
6386
|
const backupPath = existed ? await backupSettingsJson(settingsPath) : null;
|
|
6327
6387
|
if (backupPath) console.log(color("dim", ` backup: ${backupPath}`));
|
|
6328
|
-
|
|
6388
|
+
let command;
|
|
6389
|
+
if (priorCmd) {
|
|
6390
|
+
// Compose: wrap the existing command via --prepend.
|
|
6391
|
+
// Single-quote the prior command + escape any single quotes
|
|
6392
|
+
// (rare in practice but defensive).
|
|
6393
|
+
const safe = priorCmd.replace(/'/g, `'\\''`);
|
|
6394
|
+
command = `${CIPHERWAKE_CMD_PREFIX} --prepend='${safe}'`;
|
|
6395
|
+
console.log(color("green", ` ✓ composed statusLine: existing command wrapped via --prepend → ${displayPath}`));
|
|
6396
|
+
console.log(color("dim", ` prior command: ${priorCmd.slice(0, 80)}${priorCmd.length > 80 ? "..." : ""}`));
|
|
6397
|
+
console.log(color("dim", ` both surfaces now render in the single statusLine slot`));
|
|
6398
|
+
} else {
|
|
6399
|
+
// Fresh install — no prior command to compose with.
|
|
6400
|
+
command = CIPHERWAKE_CMD_PREFIX;
|
|
6401
|
+
console.log(color("green", ` ✓ added statusLine config → ${displayPath}`));
|
|
6402
|
+
}
|
|
6403
|
+
settings.statusLine = { type: "command", command };
|
|
6329
6404
|
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
6330
6405
|
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
6331
|
-
|
|
6332
|
-
|
|
6406
|
+
installSummary.push({
|
|
6407
|
+
component: "Claude Code statusLine",
|
|
6408
|
+
path: settingsPath,
|
|
6409
|
+
status: existed && priorCmd ? "installed-composed" : (existed ? "installed-updated" : "installed-created"),
|
|
6410
|
+
backup: backupPath,
|
|
6411
|
+
});
|
|
6333
6412
|
}
|
|
6334
6413
|
} catch (err) {
|
|
6335
6414
|
console.log(color("red", ` ✗ statusLine config install failed: ${err.message}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pqcheck",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.20",
|
|
4
4
|
"description": "Deploy gate for AI-coded web apps. `pqcheck deploy-check --ai` returns ship_decision=pass|review|block for Claude Code / Cursor / Copilot / Aider to gate deploys before they ship. Anonymous, no signup, free for first use.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-coder",
|