The 65 checks
Every check the grade runs, with what passing means, how severe a failure is, and what it takes to run it. 51 run on any URL, 7 after you verify ownership, 7 on your own machine with your own keys. 12 fixes are published free in full; the rest are summarised here and written out in the Fix Kit.
- Severity
- blocker (one caps the grade at D, two at F), high, medium, low, info
- Weights
- blocker 10 · high 5 · medium 3 · low 1 · info 0. Score = passing weight over graded weight; a warning counts half.
- Letters
- A 90 · B 75 · C 60 · D 40 · F below
29 checks
Routes found in the sitemap and bundle answer 401, 403 or 405 to an anonymous request, never data.
With the page's own anon key, every exposed table returns zero rows to an anonymous select.
/.git/config and /.git/HEAD return 404.
/.env, /.env.local, /.env.production and .bak variants return 404.
First-party bundles contain no secret-shaped strings: Stripe sk_, Supabase service_role, AWS, OpenAI, Anthropic, Twilio, SendGrid, Resend.
A secret scan over the full history finds nothing live.
Every table has RLS enabled and no policy is USING (true) for anon.
The certificate is trusted, matches the host and is not expired.
Signup matches intent, minimum password is 12 or more, OTP expiry and redirect allow-list are set, anonymous sign-ins off.
A CSP header exists on the HTML response.
A plain http:// request answers with a 301 or 308 to https://.
git check-ignore proves .env, .env.*, and *.bak* are ignored.
Access-Control-Allow-Origin is not * alongside Allow-Credentials true.
Supabase or Firebase storage does not list objects to anonymous callers.
HSTS is present with max-age of at least 6 months.
If the page ships a Supabase URL and anon key, the REST root does not list tables to anonymous callers.
More than 14 days remain on the certificate.
X-Frame-Options or frame-ancestors is set.
Every Set-Cookie on the HTML response carries the three flags where appropriate.
The policy names frame-ancestors, form-action, and does not allow scripts from everywhere.
Ten rapid requests to one endpoint produce a 429 or equivalent.
README.md, CLAUDE.md, docs/ and scripts/ are not served.
No http:// assets on the https page.
First-party JavaScript does not reference a reachable .map file.
A Permissions-Policy header is present.
A Referrer-Policy header is present.
The nosniff header is present.
A Firebase web config is in the bundle; rules cannot be judged passively.
No Server or X-Powered-By header naming versions.
2 checks
On a production domain the publishable key is pk_live, not pk_test.
A webhook endpoint exists for the production domain, is enabled, and subscribes to the checkout and payment events the code handles.
5 checks
A DKIM record resolves for a common selector (resend, google, default, k1, selector1).
_dmarc exists with a policy of quarantine or reject and a reporting address.
The sending domain implied by DKIM selectors or a Supabase default sender has SPF and DMARC.
Exactly one SPF TXT record at the apex.
The domain can receive mail.
5 checks
More than 30 days remain on the registration.
A CAA record limits which authorities can issue certificates.
The zone is signed.
/.well-known/security.txt exists with a contact.
The DNS provider is identified.
7 checks
A random path returns HTTP 404 with the site's own page, not 200.
No value ends in a newline; production and preview values are scoped; no secret sits in a NEXT_PUBLIC_ or VITE_ name.
Links to a privacy policy and terms are present on the home page and resolve.
Every variable read in code appears in .env.example with a placeholder.
Internal links on the home page resolve.
No dev-server hints, placeholder copy, builder badges or chat residue in the HTML.
A favicon resolves.
11 checks
og:image is absolute, returns 200 and is at least 1200 by 630.
/robots.txt returns 200 text/plain and names the sitemap.
/sitemap.xml returns 200 and a sample of its URLs return 200.
The home page title length is in range.
The home page has one H1.
Structured data blocks parse as JSON.
The html element has lang and a viewport meta exists.
The description length is in range.
A canonical link points at the page itself.
robots.txt states a choice for GPTBot, ClaudeBot, PerplexityBot and Google-Extended.
/llms.txt exists.
2 checks
GA4, Plausible, PostHog, Fathom or equivalent is loaded.
A consent control exists if the site targets regions that require one.
4 checks
Total transfer for the home page is under 2 MB.
No image on the home page exceeds 300 KB.
The HTML response starts within 800 ms.
Fewer than four blocking scripts or stylesheets in the head.