diff --git a/public/fido-self-validation-2026-10-21.webp b/public/fido-self-validation-2026-10-21.webp
new file mode 100644
index 0000000..16db836
Binary files /dev/null and b/public/fido-self-validation-2026-10-21.webp differ
diff --git a/src/content/docs/understand/fido-self-validation.mdx b/src/content/docs/understand/fido-self-validation.mdx
new file mode 100644
index 0000000..4c50760
--- /dev/null
+++ b/src/content/docs/understand/fido-self-validation.mdx
@@ -0,0 +1,110 @@
+---
+title: 'FIDO2 Server Self-Validation'
+description: 'Results of running the FIDO Alliance conformance tools against the Seamless Auth API: tool version, date, pass or fail per suite, the tests that did not run and why, and known deviations. Self-validation, not certification.'
+sidebar:
+ order: 7
+---
+
+import { Aside } from '@astrojs/starlight/components';
+import Image from '../../../components/Image.astro';
+
+
+
+## Latest Run
+
+| | |
+| --- | --- |
+| Date | 2026-10-21 |
+| Tool | FIDO Conformance Tools 1.9.1 (desktop), FIDO2 Server tests |
+| Server | [`seamless-auth-api` v0.19.3](https://github.com/fells-code/seamless-auth-api/tree/v0.19.3) |
+| Result | **159 passed, 0 failed** (21.52 s) |
+| Test groups | All selected: MakeCredential Request, MakeCredential Response, MakeCredential Platform Attestations, GetAssertion Request, GetAssertion Response, Metadata Service Tests |
+| Optional features selected | Android Keystore attestation |
+
+
+
+## Results Per Suite
+
+Every test the tool ran in each suite passed.
+
+| Suite | What it tests | Result |
+| --- | --- | --- |
+| Server-ServerPublicKeyCredentialCreationOptions-Req-1 | Registration options | Pass |
+| Server-ServerAuthenticatorAttestationResponse-Resp-1 | Registration response structure | Pass |
+| Server-ServerAuthenticatorAttestationResponse-Resp-2 | Registration `clientDataJSON` | Pass |
+| Server-ServerAuthenticatorAttestationResponse-Resp-3 | `attestationObject` | Pass |
+| Server-ServerAuthenticatorAttestationResponse-Resp-4 | Authentication algorithms | Pass, optional algorithms not run (below) |
+| Server-ServerAuthenticatorAttestationResponse-Resp-5 | `packed` full attestation | Pass |
+| Server-ServerAuthenticatorAttestationResponse-Resp-6 | `packed` self attestation | Pass |
+| Server-ServerAuthenticatorAttestationResponse-Resp-7 | `none` attestation | Pass |
+| Server-ServerAuthenticatorAttestationResponse-Resp-8 | `fido-u2f` attestation | Pass |
+| Server-ServerAuthenticatorAttestationResponse-Resp-9 | `tpm` attestation | Pass, P-3 and P-4 not run (below) |
+| Server-ServerAuthenticatorAttestationResponse-Resp-A | `android-key` attestation | Pass |
+| Server-ServerPublicKeyCredentialGetOptionsResponse-Req-1 | Authentication options | Pass |
+| Server-ServerAuthenticatorAssertionResponse-Resp-1 | Authentication response structure | Pass |
+| Server-ServerAuthenticatorAssertionResponse-Resp-2 | Authentication `clientDataJSON` | Pass |
+| Server-ServerAuthenticatorAssertionResponse-Resp-3 | `authenticatorData` | Pass |
+| Server-MDS3 | FIDO Metadata Service 3 | Pass |
+
+## Tests That Did Not Run
+
+The tool runs an optional test only when it is selected, and only for the algorithms the server
+supports. The API supports EdDSA, ES256, RS256 and RS1
+([`webauthnAlgorithms.ts`](https://github.com/fells-code/seamless-auth-api/blob/v0.19.3/src/lib/webauthnAlgorithms.ts)),
+so these did not run:
+
+- **Resp-4 P-1, P-2, P-3, P-4, P-6, P-7, P-10, P-11:** the optional algorithms ES256K (secp256k1),
+ PS256, PS384, PS512, RS384, RS512, ES384 and ES512, which the API does not offer.
+- **Resp-9 P-4,** `tpm` attestation with ES256. The tool lists it as experimental, and it was
+ deselected for this run. With it selected, it fails: the attestation is refused with
+ `PubArea unique is not same as public key x and y (TPM|ECC)`. The cause is in the WebAuthn
+ library the API uses (SimpleWebAuthn 14.0.3): its TPM structure parser does not read the hash
+ algorithm that follows an ECDSA signing scheme, so every later field is read two bytes early.
+ The authenticator's key matches the credential. A TPM that sets an ECDSA scheme on an ES256 key
+ cannot register a passkey with `attestation: 'direct'` until the library is fixed. Reported
+ upstream as [SimpleWebAuthn#818](https://github.com/MasterKale/SimpleWebAuthn/issues/818).
+- **Resp-9 P-3,** `tpm` attestation whose `pubArea.nameAlg` differs from the algorithm used for
+ `attested.name`. The tool did not run it in this configuration.
+
+## Known Deviations
+
+- **Android SafetyNet attestation is not tested.** Tool 1.9.1 has no SafetyNet suite, and
+ SafetyNet cannot be tested with fixtures: its statements expire within a minute and the tools
+ never emit one. The API still verifies SafetyNet attestation through SimpleWebAuthn.
+- **The tools drive a test-only interface.** They speak their own message format, so the API mounts
+ `/conformance` routes only when `FIDO_CONFORMANCE_MODE=true`, and never under
+ `NODE_ENV=production`
+ ([`conformanceMode.ts`](https://github.com/fells-code/seamless-auth-api/blob/v0.19.3/src/config/conformanceMode.ts)).
+ Those routes verify registrations and assertions with the same SimpleWebAuthn calls, origins,
+ RP ID and supported algorithms as the production WebAuthn routes
+ ([`conformance.ts`](https://github.com/fells-code/seamless-auth-api/blob/v0.19.3/src/controllers/conformance.ts),
+ [`webauthn.ts`](https://github.com/fells-code/seamless-auth-api/blob/v0.19.3/src/controllers/webauthn.ts)).
+ They skip what the tools cannot exercise: the pre-auth flow, session issuance, and the
+ deployment's authenticator policy. The options they return echo the selection criteria,
+ attestation conveyance and extensions the tools request, as the tools require.
+- **Test metadata and roots.** For the run, the API loads the tools' own metadata statements and
+ Metadata Service endpoints and trusts the tools' test root certificate, because the tools sign
+ every attestation with test keys.
+
+## Earlier Runs
+
+The first runs, on 2026-09-02 against the development branch after v0.7.4, found and fixed two issues: the
+conformance metadata loader did not read the tools' nested statement directory, so every
+attestation was refused as unlisted, and the registration options carried an extension the tools
+had not asked for. The tool's own report was not kept from those runs, so they are not published
+as results.
+
+---
+
+## Related Docs
+
+- [Security Posture and Threat Model](/understand/security)
diff --git a/src/content/docs/understand/security.mdx b/src/content/docs/understand/security.mdx
index d19bb33..27ca113 100644
--- a/src/content/docs/understand/security.mdx
+++ b/src/content/docs/understand/security.mdx
@@ -15,8 +15,8 @@ can be checked rather than taken on trust.
The longer rationale for each accepted tradeoff lives in the API repository's
@@ -397,6 +397,7 @@ Source code checks run in CI:
## Related Docs
- [OAuth Client and RFC 9700](/understand/oauth-rfc9700)
+- [FIDO2 Server Self-Validation](/understand/fido-self-validation)
- [Session Model](/understand/sessions)
- [Auth Events and Auditing](/understand/authevents)
- [Cookies, Sessions, and Tokens](/reference/cookies-sessions-tokens)