Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion public/.well-known/agent-skills/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "skill-md",
"description": "Query and apply The Website Specification — a platform-agnostic specification of what a good website does. Use when the user asks what their site should have, whether something is required, how to audit a URL, what's missing for agent readiness, or anything else where you'd otherwise be guessing at web best practice. Backs answers with primary sources and ships an MCP server with search, list, fetch, checklist, and audit tools.",
"url": "/.well-known/agent-skills/specification-website/SKILL.md",
"digest": "sha256:dafb93041d83177a5c40ef4d1171beca174773298111045f84992a32c90bb1d9"
"digest": "sha256:7070db105de9efc80aecd3cf5ccf3c232da7116dd182f022ea20e38631127dcd"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Query and apply The Website Specification — a platform-agnostic s

# specification.website

The Website Specification is a single source of truth for what a good website does. Ten categories, 173 pages, every item tagged with a status. It ships in three machine-readable forms: per-page Markdown, llms.txt / llms-full.txt, and an MCP server.
The Website Specification is a single source of truth for what a good website does. Ten categories, 177 pages, every item tagged with a status. It ships in three machine-readable forms: per-page Markdown, llms.txt / llms-full.txt, and an MCP server.

## When to use this skill

Expand Down
Binary file modified public/og-default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/og/checklist.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/og/spec.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/og/spec/resilience.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/spec/resilience/ipv6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/og/spec/security.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/spec/security/dane.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/spec/security/rpki.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/spec/security/tls-configuration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: "Added pages on IPv6, RPKI, DANE and TLS configuration"
date: "2026-10-15"
type: added
relatedSlugs: [ipv6, rpki, dane, tls-configuration]
---

Four pages cover the infrastructure underneath a website: [IPv6 reachability](/spec/resilience/ipv6/) for web and name servers, [RPKI route origin validation](/spec/security/rpki/), [DANE for HTTPS](/spec/security/dane/) (optional, since no mainstream browser checks it), and [TLS configuration hardening](/spec/security/tls-configuration/), which extends the HTTPS checklist with the individual parameters scanners test.
5 changes: 3 additions & 2 deletions src/content/spec/performance/compression.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ summary: "Compress text responses with brotli where supported, gzip everywhere e
status: required
order: 60
appliesTo: [all]
relatedSlugs: [cache-control, vary, http3, conditional-requests, compression-dictionary-transport]
updated: "2026-07-09T00:00:00.000Z"
relatedSlugs: [cache-control, vary, http3, conditional-requests, compression-dictionary-transport, tls-configuration]
updated: "2026-10-15T00:00:00.000Z"
sources:
- title: "RFC 7932 — Brotli Compressed Data Format"
url: "https://www.rfc-editor.org/rfc/rfc7932"
Expand Down Expand Up @@ -70,6 +70,7 @@ Text resources — HTML, CSS, JavaScript, JSON, SVG, XML — compress to 20–30
- Compressing images or video. Pure waste.
- Missing `Vary: Accept-Encoding` behind a CDN. Cache poisoning waiting to happen.
- Brotli at quality 11 on dynamic responses. The encode cost erases the wire saving.
- Compressing a response that reflects attacker-controlled input next to a secret. This is the BREACH attack: an attacker who can inject text into a page (through a query parameter, say) and watch the size of the encrypted response can recover a secret on the same page, such as a CSRF token, one character at a time, because a guess that matches the secret compresses better. The fix is narrow, not a reason to switch compression off: mask per-request secrets with a fresh random value on every response (most frameworks' CSRF protection already does), keep secrets out of responses that echo user input, or skip compression on those specific responses. Static assets and pages without secrets are not affected. Scanners that flag "HTTP compression enabled" as a risk on every site, as Internet.nl does, are measuring something much broader than the actual exposure.

## Verification

Expand Down
75 changes: 75 additions & 0 deletions src/content/spec/resilience/ipv6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
title: "IPv6 reachability"
slug: ipv6
category: resilience
summary: "Publish AAAA records for the web server and the authoritative name servers, make sure every IPv6 address actually answers, and serve the same site over IPv6 as over IPv4."
status: recommended
order: 70
appliesTo: [all]
relatedSlugs: [monitoring-uptime, https-tls, dnssec, rpki]
updated: "2026-10-15T00:00:00.000Z"
sources:
- title: "RFC 3596 — DNS Extensions to Support IP Version 6"
url: "https://www.rfc-editor.org/rfc/rfc3596"
publisher: "IETF"
- title: "RFC 4213 — Basic Transition Mechanisms for IPv6 Hosts and Routers"
url: "https://www.rfc-editor.org/rfc/rfc4213"
publisher: "IETF"
- title: "RFC 8305 — Happy Eyeballs Version 2: Better Connectivity Using Concurrency"
url: "https://www.rfc-editor.org/rfc/rfc8305"
publisher: "IETF"
- title: "Internet.nl — Website test"
url: "https://internet.nl/"
publisher: "Internet.nl"
---

## What it is

IPv6 is the successor to IPv4, with 128-bit addresses instead of 32-bit ones. A host name is reachable over IPv6 when DNS publishes an `AAAA` record for it (RFC 3596) and something at that address answers. Running both protocols side by side, with an `A` and an `AAAA` record for the same name, is called dual stack (RFC 4213), and it is how almost every IPv6-capable site is run today.

Two separate things need IPv6, and the second is often forgotten:

1. **The web server.** `example.com` and `www.example.com` each need an `AAAA` record that points at a host serving the site.
2. **The authoritative name servers.** The servers listed in your `NS` records need `AAAA` records of their own, so an IPv6-only resolver can look your domain up at all.

The wrong belief most operators hold is that a missing or broken `AAAA` record would show up as a visible outage. It usually does not. Browsers use Happy Eyeballs (RFC 8305): they try IPv6 and IPv4 almost at once and keep whichever connects first. If the IPv6 address is dead, the browser quietly falls back to IPv4 after a short delay, and the operator testing from a desktop never notices. The failure lands on clients that do not race the two protocols, on IPv6-only networks, and on tests such as Internet.nl that check each protocol on its own.

## Why it matters

- **IPv6-only and IPv6-preferred networks are common.** Many mobile carriers hand out only IPv6 addresses and reach IPv4 through a translation layer (NAT64). Native IPv6 skips that layer, and its latency and failure modes.
- **IPv4 addresses are scarce.** Hosts increasingly share IPv4 addresses behind carrier-grade NAT, which makes IPv4 paths slower and harder to debug than direct IPv6 ones.
- **A broken `AAAA` costs time even when it "works".** Every Happy Eyeballs fallback adds a connection delay, typically a few hundred milliseconds, before the first byte.
- **Name servers are a single point of failure.** If every authoritative server is IPv4-only, an IPv6-only resolver cannot resolve any name in your zone, however well your web server is configured.

## How to implement

1. **Check whether your host supports IPv6.** Most CDNs and managed platforms do, often by default. If yours does not, that is a reason to move, not a reason to skip the record.
2. **Publish `AAAA` records** for every name that has an `A` record: the apex, `www`, and any other host name that serves the site.

```
example.com. 300 IN A 192.0.2.10
example.com. 300 IN AAAA 2001:db8::10
www.example.com. 300 IN CNAME example.com.
```

3. **Make sure the server listens on IPv6.** The web server, the firewall, and any load balancer all need to accept connections on ports 80 and 443 over IPv6, with the same certificate.
4. **Serve the same site on both.** The same content, the same redirects (HTTP to HTTPS, apex to `www` or the reverse), the same security headers. Internet.nl compares the two responses and flags differences.
5. **Use name servers with IPv6.** Managed DNS providers almost all publish `AAAA` records for their name servers; check that yours do, and that at least two of them are reachable over IPv6.
6. **Monitor both protocols separately.** An uptime check that resolves the name normally will happily use IPv4 and miss a dead IPv6 path. Add a check pinned to IPv6. See [monitoring and uptime](/spec/resilience/monitoring-uptime/).

This site ships it: the apex and both of its Cloudflare name servers have `AAAA` records, and the site answers identically over IPv6.

## Common mistakes

- **An `AAAA` record pointing at a host that does not listen on IPv6.** Often left over from a migration, or added by a control panel for a server whose IPv6 firewall drops port 443. Browsers hide it; other clients time out.
- **Different behaviour over IPv6.** An old virtual-host config that serves a default page, skips the HTTPS redirect, or omits HSTS on the IPv6 listener.
- **A certificate that only covers the IPv4 path.** Separate front ends per protocol drift apart, and one ends up with an expired or mismatched certificate.
- **IPv4-only name servers.** The web server is dual stack, but nothing in the delegation can be reached over IPv6.
- **Adding `AAAA` for the apex but not for `www`**, or the reverse, so half the site is reachable.

## Verification

- `dig +short AAAA example.com` and `dig +short AAAA www.example.com` return addresses.
- For each name server in `dig +short NS example.com`, `dig +short AAAA <ns>` returns an address.
- `curl -6 -sI https://example.com/` and `curl -4 -sI https://example.com/` return the same status, redirects, and headers.
- Run the [Internet.nl website test](https://internet.nl/). Its IPv6 section covers name servers, web server addresses and reachability, and whether the IPv6 and IPv4 sites are the same.
2 changes: 1 addition & 1 deletion src/content/spec/resilience/monitoring-uptime.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ summary: "Monitor the site from outside your own infrastructure, combine synthet
status: recommended
order: 50
appliesTo: [all]
relatedSlugs: [error-pages, maintenance-pages, third-party-scripts]
relatedSlugs: [error-pages, maintenance-pages, third-party-scripts, ipv6]
updated: "2026-10-15T12:00:00.000Z"
sources:
- title: "Google SRE Book — Monitoring Distributed Systems"
Expand Down
2 changes: 1 addition & 1 deletion src/content/spec/security/caa-records.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ summary: "A CAA record tells certificate authorities which of them are allowed t
status: recommended
order: 110
appliesTo: [all]
relatedSlugs: [https-tls, hsts, dnssec, for-sale-dns]
relatedSlugs: [https-tls, hsts, dnssec, for-sale-dns, rpki, dane]
updated: "2026-05-29T09:13:20.000Z"
sources:
- title: "RFC 8659 — DNS Certification Authority Authorization (CAA) Resource Record"
Expand Down
63 changes: 63 additions & 0 deletions src/content/spec/security/dane.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: "DANE for HTTPS"
slug: dane
category: security
summary: "A TLSA record at _443._tcp pins your HTTPS certificate or key in DNSSEC-signed DNS. No mainstream browser checks it, so for websites it is optional; its real home is SMTP."
status: optional
order: 125
appliesTo: [all]
relatedSlugs: [dnssec, https-tls, caa-records, tls-configuration]
updated: "2026-10-15T00:00:00.000Z"
sources:
- title: "RFC 6698 — The DNS-Based Authentication of Named Entities (DANE) Transport Layer Security (TLS) Protocol: TLSA"
url: "https://www.rfc-editor.org/rfc/rfc6698"
publisher: "IETF"
- title: "RFC 7671 — The DNS-Based Authentication of Named Entities (DANE) Protocol: Updates and Operational Guidance"
url: "https://www.rfc-editor.org/rfc/rfc7671"
publisher: "IETF"
- title: "RFC 7672 — SMTP Security via Opportunistic DNS-Based Authentication of Named Entities (DANE) Transport Layer Security (TLS)"
url: "https://www.rfc-editor.org/rfc/rfc7672"
publisher: "IETF"
---

## What it is

DANE (DNS-Based Authentication of Named Entities, RFC 6698) lets a domain publish, in DNS, which certificate or public key a TLS service should present. The record type is `TLSA`, and it lives at a name built from the port, the transport and the host. For HTTPS on `www.example.com` that is `_443._tcp.www.example.com`:

```
_443._tcp.www.example.com. 3600 IN TLSA 3 1 1 8f1a...c94e
```

The three numbers say what is being matched. `3 1 1` (DANE-EE, public key, SHA-256) is the usual choice: "the server's own public key hashes to this value", independent of which certificate authority signed it. `2 1 1` (DANE-TA) pins a trust anchor, such as your CA's intermediate, instead.

DANE only means anything on top of [DNSSEC](/spec/security/dnssec/). Without signatures, anyone who can forge a DNS answer can forge the `TLSA` record too, so clients ignore `TLSA` records from unsigned zones.

## Why it matters

For websites, honestly, not much yet. **No mainstream browser validates DANE.** Chrome, Firefox, Safari and Edge all ignore `TLSA` records and trust the certificate authority system alone. Chrome briefly experimented with DNSSEC-authenticated certificates and dropped the idea, citing the latency of extra DNSSEC lookups and the share of networks that break them; no other browser shipped it. Publishing a `TLSA` record for port 443 therefore changes nothing for a visitor today. That is why this page is `optional`, and why tests that score it (Internet.nl does) are measuring readiness rather than a protection any visitor receives.

Where DANE does real work is mail. Mail servers deliver to each other over opportunistic STARTTLS, which an attacker can strip or redirect, and there is no browser-style certificate check to fall back on. DANE for SMTP (RFC 7672) lets a receiving domain tell sending servers "always use TLS, and expect this key", and major mail providers do check it. If you run mail on your domain, that is the place to spend DNSSEC and DANE effort first: see [DANE for SMTP on specification.email](https://specification.email/spec/authentication/dane-for-smtp/).

## How to implement

If you publish a `TLSA` record for HTTPS anyway, for consistency with a mail setup or for clients that do validate:

1. **Sign the zone with DNSSEC** and confirm the chain validates. Without this, stop.
2. **Choose the usage.** `3 1 1` pins your server's public key; it survives certificate renewals as long as the key does not change. `2 1 1` pins the CA's intermediate key and survives renewals with that CA.
3. **Generate the record** from the certificate the server actually serves, with a tool such as `ldns-dane` or `hash-slinger`, or by hashing the SubjectPublicKeyInfo with SHA-256.
4. **Roll keys in two steps** (RFC 7671). Publish the `TLSA` record for the new key alongside the old one, wait at least the record's TTL, then switch the server to the new key, then remove the old record. ACME clients that generate a new key at every renewal break `3 1 1` records unless configured to reuse the key or to publish the new record first.

This site does not ship it for HTTPS: the zone is DNSSEC-signed, but no browser would act on a `_443._tcp` record. For SMTP the `TLSA` records belong to the MX hosts, which here are run by the mail provider, so publishing them is the provider's job.

## Common mistakes

- **A `TLSA` record in an unsigned zone.** Validating clients ignore it; it is decoration.
- **Automated certificate renewal with a fresh key each time** and a `3 1 1` record that never changes. The record stops matching on the next renewal. For mail, where senders do check, that is a delivery outage.
- **Expecting browser protection.** A `TLSA` record does not stop a mis-issued certificate from being accepted by a browser. [CAA records](/spec/security/caa-records/) and Certificate Transparency monitoring are what limit mis-issuance on the web.
- **Publishing `TLSA` for the wrong name.** It belongs at `_443._tcp.` plus the exact host name clients connect to, for each host name.

## Verification

- `dig +dnssec TLSA _443._tcp.www.example.com` returns the record and the `ad` flag from a validating resolver.
- `openssl s_client -connect www.example.com:443 -dane_tlsa_domain www.example.com -dane_tlsa_rrdata "3 1 1 <hash>"` reports a verified DANE match.
- The [Internet.nl website test](https://internet.nl/) includes DANE in its HTTPS section: it checks that a `TLSA` record exists, is DNSSEC-signed, and matches the served certificate.
4 changes: 2 additions & 2 deletions src/content/spec/security/dnssec.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ summary: "DNSSEC cryptographically signs DNS records so resolvers can verify the
status: optional
order: 120
appliesTo: [all]
relatedSlugs: [caa-records, https-tls, hsts, for-sale-dns]
relatedSlugs: [caa-records, https-tls, hsts, for-sale-dns, dane, rpki]
updated: "2026-08-08T00:00:00.000Z"
sources:
- title: "RFC 4033 — DNS Security Introduction and Requirements"
Expand Down Expand Up @@ -35,7 +35,7 @@ DNSSEC signs; it does not encrypt. The signatures let a resolver detect a tamper

Plain DNS is unauthenticated. Anyone on the network path — a coffee-shop router, a compromised ISP, a malicious resolver — can lie about which IP your domain points to, where mail should go, or what TXT records exist. That undermines TLS issuance (because Domain Validation depends on DNS or HTTP that depends on DNS), email security (SPF, DKIM, DMARC live in TXT records), and CAA enforcement.

DNSSEC closes the cache-poisoning and on-path spoofing surface for any resolver that validates. It is also a prerequisite for protocols like DANE, which pin TLS keys in DNS.
DNSSEC closes the cache-poisoning and on-path spoofing surface for any resolver that validates. It is also a prerequisite for [DANE](/spec/security/dane/), which pins TLS keys in DNS; for websites no browser checks it yet, but for mail it is DNSSEC's most useful application.

The honest caveat: DNSSEC is operationally tricky. A misconfiguration — a missing `DS` record after a key rollover, an expired signature — takes your whole domain offline for validating resolvers, which is much worse than the average outage. The benefit is real but only worth taking on with tooling that handles rollovers automatically.

Expand Down
6 changes: 4 additions & 2 deletions src/content/spec/security/https-tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ summary: "Serve every page over HTTPS using TLS 1.2 or 1.3, redirect plain HTTP
status: required
order: 10
appliesTo: [all]
relatedSlugs: [hsts, caa-records, content-security-policy, mixed-content]
updated: "2026-08-21T00:00:00.000Z"
relatedSlugs: [tls-configuration, hsts, caa-records, content-security-policy, mixed-content, dane]
updated: "2026-10-15T00:00:00.000Z"
sources:
- title: "RFC 9846 — The Transport Layer Security (TLS) Protocol Version 1.3"
url: "https://www.rfc-editor.org/rfc/rfc9846"
Expand Down Expand Up @@ -65,6 +65,8 @@ Cipher and protocol checklist:
- Hybrid post-quantum key agreement offered on TLS 1.3 — `X25519MLKEM768`.
- A complete certificate chain — serve the intermediate, not just the leaf.

The remaining TLS parameters that scanners test one by one (TLS compression, renegotiation, 0-RTT, Extended Main Secret, cipher order, signature hashes, and certificate key size) are covered in [TLS configuration hardening](/spec/security/tls-configuration/).

That third line is stricter than the familiar "forward secrecy only" rule, and the difference is where TLS 1.2 configurations tend to be out of date. RFC 10015 says clients and servers **MUST NOT** offer or select RSA key exchange, static finite-field Diffie-Hellman, _or ephemeral finite-field Diffie-Hellman_ in TLS 1.2 — so `DHE` suites are now disallowed even though they are forward-secret, on the grounds that finite-field groups are slow, historically misconfigured, and no longer worth maintaining alongside the elliptic-curve equivalents. Static `ECDH` is a `SHOULD NOT`. In practice this leaves `ECDHE`, which is what a current Mozilla "Intermediate" configuration already gives you.

The fourth line is the newer one. RFC 10024 (August 2026) put three hybrid mechanisms on the standards track — `X25519MLKEM768`, `SecP256r1MLKEM768` and `SecP384r1MLKEM1024` — each pairing the post-quantum ML-KEM with an ordinary elliptic-curve exchange, so a break in either half still leaves the connection secure. `X25519MLKEM768` is the one to offer: the RFC marks it Recommended and describes it as already widely deployed, because browsers and large CDNs shipped it ahead of publication. Its predecessor code points, the draft `X25519Kyber768Draft00` and `SecP256r1Kyber768Draft00`, are obsolete; a server still pinned to those is negotiating something no current client asks for.
Expand Down
Loading
Loading