pqcheck 0.16.30 → 0.16.32
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/pqcheck.js +63 -1
- 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.32** — `--fresh` now actually refreshes posture (R92, originally shipped in 0.16.31). External dogfood bug: a customer deployed CSP/HSTS/X-Frame-Options/X-Content-Type-Options/Referrer-Policy/Permissions-Policy via Next.js, verified all six on the wire with curl, and `pqcheck deploy-check --fresh` continued to report `posture_grade=D` + `posture_leaks=x-powered-by: Next.js` — directly contradicting reality. Root cause: the CLI silently dropped `--fresh` from the trust-diff request body. Now plumbed through end-to-end. Every response carries `fresh_status` (`applied | rate_limited | unauthenticated | unavailable | not_requested`) so callers route on whether the posture is current — no more silent stale reads. `--verbose` emits a `CIPHERWAKE_SCANNER_OBSERVED` block with the actual headers, final URL, and status the grade was computed from, so customers can diff "what Cipherwake saw" vs `curl -I` instantly. 0.16.32 is a clean re-publish: same CLI surface as 0.16.31, post-fix lib refactor + test coverage added. [Full changelog →](./CHANGELOG.md)
|
|
12
12
|
|
|
13
13
|
## Two ways to use it
|
|
14
14
|
|
package/bin/pqcheck.js
CHANGED
|
@@ -3770,6 +3770,12 @@ async function runTrustDiffCommand(args) {
|
|
|
3770
3770
|
const baseline = parseFlag(args, "--baseline") || "last-week";
|
|
3771
3771
|
const failOn = parseFlag(args, "--fail-on") || "high";
|
|
3772
3772
|
const format = parseFlag(args, "--format") || "pretty";
|
|
3773
|
+
// R92 (2026-06-06) — --fresh + --verbose were silently dropped by the
|
|
3774
|
+
// CLI before R92. The result: `pqcheck deploy-check <D> --fresh` ran but
|
|
3775
|
+
// returned stale posture from scan_cache. After R92 they're plumbed
|
|
3776
|
+
// through to /api/trust-diff body so the server can act on them.
|
|
3777
|
+
const fresh = args.includes("--fresh") || args.includes("--force");
|
|
3778
|
+
const verbose = args.includes("--verbose");
|
|
3773
3779
|
// R86.4 (2026-06-03) — see runOneScan for rationale. Default ship_decision
|
|
3774
3780
|
// is drift-only (per-deploy regression gate); --strict-posture opts into
|
|
3775
3781
|
// worst-of(drift, posture). Recommended only after a site reaches A/B
|
|
@@ -3799,7 +3805,17 @@ async function runTrustDiffCommand(args) {
|
|
|
3799
3805
|
resp = await fetch(`${API_BASE}/api/trust-diff`, {
|
|
3800
3806
|
method: "POST",
|
|
3801
3807
|
headers,
|
|
3802
|
-
body: JSON.stringify({
|
|
3808
|
+
body: JSON.stringify({
|
|
3809
|
+
domain,
|
|
3810
|
+
baseline,
|
|
3811
|
+
fail_on: failOn,
|
|
3812
|
+
routeAssertionsConfig: _routeCfg,
|
|
3813
|
+
// R92 (2026-06-06) — pass --fresh + --verbose through to server.
|
|
3814
|
+
// Server returns `fresh_status` so caller can route on
|
|
3815
|
+
// applied | rate_limited | unauthenticated | unavailable | not_requested.
|
|
3816
|
+
fresh,
|
|
3817
|
+
verbose,
|
|
3818
|
+
}),
|
|
3803
3819
|
});
|
|
3804
3820
|
} catch (err) {
|
|
3805
3821
|
console.error(color("red", `error: network failure calling /api/trust-diff: ${err.message}`));
|
|
@@ -3856,6 +3872,45 @@ async function runTrustDiffCommand(args) {
|
|
|
3856
3872
|
const result = await resp.json();
|
|
3857
3873
|
const verdict = result.verdict || "pass";
|
|
3858
3874
|
const deltas = Array.isArray(result.deltas) ? result.deltas : [];
|
|
3875
|
+
// R92 (2026-06-06) — surface non-applied fresh status BEFORE any verdict
|
|
3876
|
+
// rendering so the customer sees the staleness warning at the top, not
|
|
3877
|
+
// after the (potentially stale) grade. Three failure modes get their own
|
|
3878
|
+
// explanation since they imply different next actions.
|
|
3879
|
+
const freshStatus = typeof result.fresh_status === "string" ? result.fresh_status : "not_requested";
|
|
3880
|
+
if (fresh && freshStatus !== "applied" && freshStatus !== "not_requested") {
|
|
3881
|
+
const why = freshStatus === "rate_limited"
|
|
3882
|
+
? "fresh-scan per-IP cap reached (20/hr). The posture below is from the last cached scan — re-run after the cap window, or accept the cached read for now."
|
|
3883
|
+
: freshStatus === "unauthenticated"
|
|
3884
|
+
? "fresh-scan requires an API key (free tier inclusive). Set CIPHERWAKE_API_KEY or run via OIDC; without it, --fresh silently downgrades to cached. https://cipherwake.io/account#api-keys"
|
|
3885
|
+
: freshStatus === "unavailable"
|
|
3886
|
+
? "fresh-scan path failed mid-run. The posture below is from the last cached scan — re-run to retry."
|
|
3887
|
+
: `fresh request returned status=${freshStatus}.`;
|
|
3888
|
+
console.error("");
|
|
3889
|
+
console.error(color("yellow", `⚠ --fresh requested but NOT applied: ${why}`));
|
|
3890
|
+
console.error("");
|
|
3891
|
+
}
|
|
3892
|
+
// R92 — scanner_observed (verbose mode). Surfaces the actual headers /
|
|
3893
|
+
// final URL / status that Cipherwake's posture grade was computed from,
|
|
3894
|
+
// so a customer can diff against `curl -I` and catch a stale or
|
|
3895
|
+
// wrong-target read instantly.
|
|
3896
|
+
if (verbose && result.scanner_observed) {
|
|
3897
|
+
const obs = result.scanner_observed;
|
|
3898
|
+
console.log("");
|
|
3899
|
+
console.log(color("dim", "CIPHERWAKE_SCANNER_OBSERVED"));
|
|
3900
|
+
console.log(color("dim", `final_url=${obs.final_url ?? "<unknown>"}`));
|
|
3901
|
+
console.log(color("dim", `final_status=${obs.final_status ?? "<unknown>"}`));
|
|
3902
|
+
console.log(color("dim", `fetched_at=${obs.fetched_at}`));
|
|
3903
|
+
if (obs.headers && typeof obs.headers === "object") {
|
|
3904
|
+
const headerKeys = Object.keys(obs.headers).sort();
|
|
3905
|
+
for (const k of headerKeys) {
|
|
3906
|
+
const v = obs.headers[k];
|
|
3907
|
+
const val = Array.isArray(v) ? v.join(", ") : (v == null ? "" : String(v));
|
|
3908
|
+
console.log(color("dim", `header.${k}=${val}`));
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
console.log(color("dim", "END_CIPHERWAKE_SCANNER_OBSERVED"));
|
|
3912
|
+
console.log("");
|
|
3913
|
+
}
|
|
3859
3914
|
|
|
3860
3915
|
// AI Coder Mode — three-layer output (banner / body / structured block).
|
|
3861
3916
|
if (parseAiMode(args)) {
|
|
@@ -4020,6 +4075,13 @@ async function runTrustDiffCommand(args) {
|
|
|
4020
4075
|
: undefined,
|
|
4021
4076
|
...assertionFields,
|
|
4022
4077
|
...postureFields,
|
|
4078
|
+
// R92 — every CIPHERWAKE_AI_GUARD_RESULT block now carries fresh_status
|
|
4079
|
+
// so MCP servers / Aider / Cursor / Claude Code can route programmatically:
|
|
4080
|
+
// applied → safe to interpret the posture as current
|
|
4081
|
+
// rate_limited / unauthenticated / unavailable → posture below is from cache,
|
|
4082
|
+
// do not interpret as a fresh measurement (the customer asked but didn't get it)
|
|
4083
|
+
// not_requested → caller didn't ask for fresh; cached is by design
|
|
4084
|
+
fresh_status: freshStatus,
|
|
4023
4085
|
}));
|
|
4024
4086
|
if (routeAssertions) {
|
|
4025
4087
|
console.log(formatRouteAssertionsBlock(routeAssertions));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pqcheck",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.32",
|
|
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",
|