Securealy
Catalogue

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
Security

29 checks

Admin and API routes refuse anonymous requestsblocker · verified · free fix

Routes found in the sitemap and bundle answer 401, 403 or 405 to an anonymous request, never data.

Anonymous callers cannot read table rowsblocker · verified · free fix

With the page's own anon key, every exposed table returns zero rows to an anonymous select.

No .git directory servedblocker · verified · free fix

/.git/config and /.git/HEAD return 404.

No env files served by URLblocker · verified · free fix

/.env, /.env.local, /.env.production and .bak variants return 404.

No secret keys in client JavaScriptblocker · passive · free fix

First-party bundles contain no secret-shaped strings: Stripe sk_, Supabase service_role, AWS, OpenAI, Anthropic, Twilio, SendGrid, Resend.

No secrets committed in the repositoryblocker · local · free fix

A secret scan over the full history finds nothing live.

Row level security is on and policies are correctblocker · local · free fix

Every table has RLS enabled and no policy is USING (true) for anon.

TLS certificate is validblocker · passive · free fix

The certificate is trusted, matches the host and is not expired.

Auth settings are closed by defaulthigh · local

Signup matches intent, minimum password is 12 or more, OTP expiry and redirect allow-list are set, anonymous sign-ins off.

Content-Security-Policy presenthigh · passive

A CSP header exists on the HTML response.

HTTP redirects to HTTPShigh · passive

A plain http:// request answers with a 301 or 308 to https://.

Ignore rules cover the whole env familyhigh · local

git check-ignore proves .env, .env.*, and *.bak* are ignored.

No wildcard CORS with credentialshigh · passive · free fix

Access-Control-Allow-Origin is not * alongside Allow-Credentials true.

Storage buckets are not listablehigh · verified

Supabase or Firebase storage does not list objects to anonymous callers.

Strict-Transport-Security headerhigh · passive

HSTS is present with max-age of at least 6 months.

Supabase REST metadata not readable with the public keyhigh · passive

If the page ships a Supabase URL and anon key, the REST root does not list tables to anonymous callers.

TLS certificate is not about to expirehigh · passive

More than 14 days remain on the certificate.

Clickjacking protectionmedium · passive

X-Frame-Options or frame-ancestors is set.

Cookies are Secure, HttpOnly and SameSitemedium · passive

Every Set-Cookie on the HTML response carries the three flags where appropriate.

CSP has frame-ancestors and form-action and no wildcard script-srcmedium · passive

The policy names frame-ancestors, form-action, and does not allow scripts from everywhere.

Endpoints are rate limitedmedium · verified

Ten rapid requests to one endpoint produce a 429 or equivalent.

Internal files return 404medium · verified

README.md, CLAUDE.md, docs/ and scripts/ are not served.

No mixed contentmedium · passive

No http:// assets on the https page.

No source maps servedmedium · passive

First-party JavaScript does not reference a reachable .map file.

Permissions-Policy setlow · passive

A Permissions-Policy header is present.

Referrer-Policy setlow · passive

A Referrer-Policy header is present.

X-Content-Type-Options: nosnifflow · passive

The nosniff header is present.

Firebase config present (rules unverified)info · passive

A Firebase web config is in the bundle; rules cannot be judged passively.

Server software not advertisedinfo · passive

No Server or X-Powered-By header naming versions.

Payments

2 checks

Production is not running on a Stripe test keyhigh · passive · free fix

On a production domain the publishable key is pk_live, not pk_test.

Stripe webhook points at the live domainhigh · local

A webhook endpoint exists for the production domain, is enabled, and subscribes to the checkout and payment events the code handles.

Email

5 checks

DKIM record foundhigh · passive

A DKIM record resolves for a common selector (resend, google, default, k1, selector1).

DMARC policy publishedhigh · passive

_dmarc exists with a policy of quarantine or reject and a reporting address.

Magic-link and receipt emails come from an authenticated domainhigh · passive · free fix

The sending domain implied by DKIM selectors or a Supabase default sender has SPF and DMARC.

SPF record present and singlehigh · passive

Exactly one SPF TXT record at the apex.

MX records presentmedium · passive

The domain can receive mail.

Domain

5 checks

Domain not about to expirehigh · passive

More than 30 days remain on the registration.

CAA record presentlow · passive

A CAA record limits which authorities can issue certificates.

DNSSEC enabledlow · passive

The zone is signed.

security.txt publishedlow · passive

/.well-known/security.txt exists with a contact.

Nameservers identifiedinfo · passive

The DNS provider is identified.

Launch hygiene

7 checks

Missing pages return 404high · passive · free fix

A random path returns HTTP 404 with the site's own page, not 200.

Hosting env values are clean and scopedmedium · local

No value ends in a newline; production and preview values are scoped; no secret sits in a NEXT_PUBLIC_ or VITE_ name.

Privacy and terms pages discoverablemedium · passive

Links to a privacy policy and terms are present on the home page and resolve.

Env example lists every variable the code readslow · local

Every variable read in code appears in .env.example with a placeholder.

No broken links on the home pagelow · passive

Internal links on the home page resolve.

No builder leftoverslow · passive

No dev-server hints, placeholder copy, builder badges or chat residue in the HTML.

Favicon presentinfo · passive

A favicon resolves.

SEO

11 checks

Open Graph image resolvesmedium · passive

og:image is absolute, returns 200 and is at least 1200 by 630.

robots.txt validmedium · passive

/robots.txt returns 200 text/plain and names the sitemap.

Sitemap present and healthymedium · passive

/sitemap.xml returns 200 and a sample of its URLs return 200.

Title present and 30 to 60 charactersmedium · passive

The home page title length is in range.

Exactly one H1low · passive

The home page has one H1.

JSON-LD parseslow · passive

Structured data blocks parse as JSON.

lang and viewport setlow · passive

The html element has lang and a viewport meta exists.

Meta description present and 70 to 155 characterslow · passive

The description length is in range.

Self-referencing canonicallow · passive

A canonical link points at the page itself.

AI crawler policy is deliberateinfo · passive

robots.txt states a choice for GPTBot, ClaudeBot, PerplexityBot and Google-Extended.

llms.txt presentinfo · passive

/llms.txt exists.

Analytics

2 checks

Analytics installedmedium · passive

GA4, Plausible, PostHog, Fathom or equivalent is loaded.

Consent mechanism where requiredinfo · passive

A consent control exists if the site targets regions that require one.

Performance

4 checks

Home page under 2 MBlow · passive

Total transfer for the home page is under 2 MB.

Largest image under 300 KBlow · passive

No image on the home page exceeds 300 KB.

Time to first byte under 800 mslow · passive

The HTML response starts within 800 ms.

Few render-blocking resourcesinfo · passive

Fewer than four blocking scripts or stylesheets in the head.