Cipherwake's Trust Diff catches what changed between deploys. The Posture Grade catches what was never set right in the first place. Strict SSL-Labs-style rubric, A+ → F, with the per-finding remediation copy your AI coder can paste directly.
The posture grade evaluates the HTTP response headers your origin returns on a homepage GET / request. Specifically:
'unsafe-inline' / 'unsafe-eval', free of wildcard sources (script-src *, default-src *).max-age ≥ 31536000 (one year), preload directive set.DENY / SAMEORIGIN) OR CSP frame-ancestors set.nosniff.x-powered-by and absence of version strings in server (e.g. nginx/1.18.0, Apache/2.4.41).That's the entire scope. We do not grade TLS configuration here — TLS lives in the Decryption Blast Radius score. We do not grade headers on subpaths, only the apex homepage response. If a header is set on /app but not /, we grade the / response.
One HTTPS request to https://<domain>/ using the SSRF-pinned safeHttpsFetch helper (same redirect-loop pattern used across Cipherwake probes; max 2 redirects, https-only, cycle detection). Response headers are parsed via lib/httpHeaders.ts, which has been GPT-R10 adversarially reviewed for CSP directive-aware parsing (specifically: directive splitting, source-list parsing, and the frame-ancestors / X-Frame-Options overlap).
The grade is computed deterministically by lib/postureGrade.ts. There is no LLM in the path — every grade is reproducible from the response headers alone. The same input always produces the same output.
Each finding has a fixed deduction. Deductions sum, then a threshold ladder converts the total to a letter:
| Finding | Deduction | Why this weight |
|---|---|---|
| Content-Security-Policy missing | 2.0 | The single biggest XSS-mitigation gap; no browser-level fence on inline scripts. |
| HSTS missing | 2.0 | First-request HTTPS downgrade vector; SSL-strip is trivial without HSTS. |
CSP wildcard source (*) | 1.5 | Effectively no CSP — any host can load scripts. Worse than misconfigured. |
CSP 'unsafe-inline' | 1.0 | Re-enables the XSS class CSP exists to block. Common in legacy migrations. |
CSP 'unsafe-eval' | 1.0 | Re-enables eval()-class injection. Required by some bundlers; flag is honest signal. |
HSTS max-age < 1 year | 1.0 | Short TTL means browser forgets HTTPS preference quickly; partial protection only. |
| X-Frame-Options missing (AND no CSP frame-ancestors) | 1.0 | Clickjacking unmitigated. Either header alone counts as covered. |
| X-Content-Type-Options missing | 1.0 | MIME-sniffing-driven XSS still possible in older browsers; cheap fix. |
| Referrer-Policy missing | 1.0 | Defaults leak full URL to third-party requests; PII exposure risk on URL-encoded params. |
HSTS no preload directive | 0.5 | Pre-load list inclusion is the strongest form; valuable but not catastrophic without it. |
| Permissions-Policy missing | 0.5 | Lower-impact than CSP; restricts powerful browser APIs (camera, mic, geolocation). |
x-powered-by header present | 0.5 | Tech-stack disclosure helps attacker target known CVEs. Cheap to strip. |
server header reveals version | 0.5 | Same class as x-powered-by; bare product name is fine, version string is not. |
| Total deductions | Letter | Decision | Meaning |
|---|---|---|---|
0 | A+ | pass | All headers present, no unsafe directives, no leaks. |
| ≤ 0.5 | A | pass | One minor finding (e.g. missing Permissions-Policy or x-powered-by). |
| ≤ 1.5 | B | review | One real gap or several minor ones. Worth fixing, not catastrophic. |
| ≤ 3.5 | C | review | Multiple real gaps, OR one critical header missing (CSP or HSTS alone). |
| ≤ 6.5 | D | block | Both CSP and HSTS missing, OR equivalent severity, but at least one header is still working. AI coder should stop announcing. |
| > 6.5 | F | block | Essentially no browser-level defenses. Most of the rubric failing. |
For convenience the grade also reports a posture_score between 0 and 100, computed as max(0, 100 - (deductions / 8.5) * 100). A+ = 100. The denominator 8.5 is the sum of every deduction that can fire when a site has the worst possible configuration — so the score is true partial credit. A site that has valid HSTS preload set but is missing CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy still scores in the high-20s (≈ 29), not zero — because the HSTS preload is posture work that was done correctly. The letter and decision are the load-bearing outputs; the score is the chart-it-over-time signal.
ship_decision relates to posture (default vs --strict-posture)The CIPHERWAKE_AI_GUARD_RESULT block emits four related fields:
ship_decision_drift — drift-based: did anything change between baseline and now?ship_decision_posture — absolute-state: what does the current posture grade route to?ship_decision_mode — either drift_only (default) or strict_posture (when --strict-posture is passed).ship_decision — the headline routing field your AI coder reads. Its value depends on the mode:
drift_only): ship_decision = ship_decision_drift. Posture is surfaced for inspection but does NOT gate. Existing customers' flows stay unchanged.--strict-posture (strict_posture): ship_decision = worst-of(ship_decision_drift, ship_decision_posture). D/F posture promotes a clean-drift pass to block.The strict mode fixes what would otherwise be a footgun: a site with no drift since last scan but F-grade absolute posture would emit ship_decision=pass under the drift-only default, and an AI coder following the original protocol ("pass → announce") would ship an F-posture site. With --strict-posture, the same site emits ship_decision=block.
The reason --strict-posture is opt-in rather than the default: most AI-coded Next.js / Vite / SvelteKit deploys grade B/C/D out of the box (no explicit security headers shipped by the framework). Defaulting to worst-of-both would make the AI Coder Protocol stop + ask the user on every PR for stable sites that didn't get worse — friction tax without proportional safety win. The strict gate is the right behaviour for production-critical deploys, but it has to be opted into. New installs are encouraged to pass --strict-posture; existing integrations keep their drift-only semantic.
A representative deploy of socialideagen.vercel.app (a real AI-coded preview lacking the full posture surface) produces:
posture_grade=F
posture_score=8
posture_decision=block
posture_missing=csp,hsts,x_frame_options,x_content_type_options,referrer_policy
posture_leaks=x_powered_by
posture_findings_count=6
posture_fixes_count=1
This is the empirical lower-bar for "a brand-new Next.js deploy with no security work done." It informs the rubric calibration: a fresh-vanilla deploy lands at F, not at B/C. Customers landing at C or worse are not at parity with their framework's defaults — there is real fix work to do.
/api/* endpoints serve weaker headers than /, this grade does not see it. Use the Trust Diff --protected-paths probe + the Site Guards cookie-flags guard for path-specific posture.headers() block we suggest will produce A+ posture if applied verbatim, but you are responsible for verifying it doesn't break a feature that relied on a wider CSP. Always test in preview before merging.GET / request. CDN edges that vary headers by user-agent, geography, or A/B bucket will be graded by whichever set the probe sees.posture_grade=unreachable. Not a green pass.lib/httpHeaders.ts GPT R10 review) but does not evaluate CSP report-only headers separately. A site shipping Content-Security-Policy-Report-Only instead of enforcing CSP is graded as "CSP missing" — report-only is not a fence.server headers (no version) are not penalized. server: nginx is fine; server: nginx/1.18.0 is the leak. Some servers (Cloudflare, Fastly) report server: cloudflare / server: fastly and we treat those as bare.GET / for unauthenticated requests cannot be posture-graded by the public scanner. The grade is "unreachable" + a review status.# From any terminal, no signup, no API key:
npx pqcheck deploy-check <your-domain> --ai
# The CIPHERWAKE_AI_GUARD_RESULT block at the end of stdout will include:
# posture_grade=A+|A|B|C|D|F
# posture_score=0..100
# posture_decision=pass|review|block
# posture_missing=<comma-separated header keys>
# posture_leaks=<comma-separated leak keys>
# posture_findings_count=N
# posture_fixes_count=N
# scope_note=ship_decision=pass means public trust surface stable. Does NOT verify app functionality.
Or scan any HTTPS domain to see the posture grade alongside the Decryption Blast Radius score:
npx pqcheck cipherwake.io
report.posture.fixesEvery JSON scan response includes report.posture.fixes — an array of ready-to-paste remediation snippets keyed to the findings actually triggered on your site. We ship templates for:
headers() async function in next.config.js that sets the full A+ header set on every route.vercel.json headers array variant, for projects that prefer config-as-data.app.use(helmet({ ... })) with the equivalent options.app.disable('x-powered-by'), Next.js poweredByHeader: false).server_tokens off, Apache ServerTokens Prod).These are intended for AI-coder paste — your Claude Code / Cursor / Aider session reads posture_fixes_count from the guard block, fetches the report.posture.fixes array from the JSON, and applies the snippet that matches your detected stack. The snippets are versioned alongside this methodology page.
The posture grade is the absolute counterpart to two existing checks:
A complete Cipherwake check on a deploy is: posture grade (absolute baseline) + trust diff (drift since last deploy) + DBR (TLS health) + optional Site Guards (config-driven runtime). Each surfaces a different failure mode.
posture_regression + cert_expiringFor watched domains (Tier 2 monitoring), the server-side monitor cron emits two new alert types backed by this rubric:
posture_regression — fires when the posture grade drops vs baseline, OR a new missing header appears, OR a new info-leak appears. Severity is critical if the grade dropped to D/F, else warn. Dedup key encodes the grade transition + counts so flapping headers don't spam.cert_expiring — tiered alerts at 30 days (warn), 14 days (critical), and 7 days (critical with daily re-alert bypassing the 24h dedup window). Fires on the same monitor cron that drives posture_regression.These run via the existing alert delivery pipeline (email + webhook). The posture grade is what makes posture_regression meaningful — without an absolute rubric, "header drift" has no severity scale.
--strict-posture opt-in flag (shipped in pqcheck v0.16.22). Hot-fix to v1.1: the worst-of-both ship_decision behaviour from v1.1 was too aggressive as a default — most AI-coded sites grade B/C/D out of the box, so v1.1 would have made the AI Coder Protocol stop on every PR for stable sites that didn't get worse. v1.2 reverts the default to drift-only and exposes the strict gate via the new --strict-posture flag. Adds ship_decision_mode=drift_only|strict_posture field so the active mode is visible. Methodology + AI Coder Protocol pages recommend --strict-posture for new installs.ship_decision now folds posture in. Previously ship_decision was drift-only and could emit pass alongside posture_decision=block — a footgun where AI coders following the original protocol would announce an F-posture deploy. ship_decision is now worst-of(ship_decision_drift, ship_decision_posture). Both inputs are still emitted separately.CIPHERWAKE_POSTURE_FIXES block after the guard block. Previously only posture_fixes_count was in the --ai output, forcing agents to round-trip to the JSON response. Snippet text is now available end-to-end from the terminal.grade= field dropped when the legacy DBR grade isn't populated for a trust-diff scan. Previously emitted as an empty value.x-powered-by / server-version-leak at 0.5 each. Threshold ladder A+(0) / A(≤0.5) / B(≤1.5) / C(≤3.0) / D(≤5.0) / F(>5.0). Decision routing A+/A → pass, B/C → review, D/F → block. Shipped in pqcheck v0.16.20.Future rubric changes will be documented here with the same level of specificity. Customers who graded their site at A+ this week and find it has slipped to A in three months should be able to read this section and know exactly which header expectation tightened.