{"id":"GHSA-6hxq-p678-4hr2","summary":"SimpleWebAuthn: Registration verification does not sufficiently ensure that attestation certificates chain to a trust anchor","details":"## Summary\n`validateCertificatePath()` does not verify that an attestation's certificate chain actually terminates at a configured trust anchor. When walking the chain it stops at the first self-signed certificate it finds (which could be user-supplied), and exits early.\n\nThis happens before the configured Apple/Google/etc trust anchor (which is concatenated to the end of the chain) is reached.\n\nA user can therefore register a credential and have the server accept it as if it were backed by a genuine Apple / Android SafetyNet / Yubikey / etc.\n\n## Details\n`packages/server/src/helpers/validateCertificatePath.ts`:\n\nThe configured trust anchor is appended to the end of the untrusted chain (line **83**):\n\n```ts\nconst x5cWithTrustAnchor = x5cCertsParsed.concat([anchor]);\n```\n\nThe walk then verifies each cert was signed by the next, but breaks on the first self-signed cert (lines **104–116**):\n\n```ts\nif (issuer.subject === issuer.issuer) {\n  // Root cert detected, make sure it signed itself\n  const issuerSignedIssuer = await issuer.verify(\n    { publicKey: issuer.publicKey, signatureOnly: true },\n    WebCrypto,\n  );\n  if (!issuerSignedIssuer) {\n    throw new InvalidSubjectAndIssuer();\n  }\n  break;   // \u003c-- exits before the appended trust anchor is ever checked if user supplied self-signed cert comes first\n}\n```\n\nThe success condition is therefore \"the certs form an internally-consistent chain ending in some self-signed cert\" Rather than \"the chain terminates at one of the configured trust anchors.\"\n\n## Exploit shape\n\n```\nattacker sends:  x5c = [ forgedLeaf (signed by attacker root),\n                         attackerSelfSignedRoot ]\n\nlibrary builds:  [ forgedLeaf, attackerSelfSignedRoot, \u003cconfigured Apple/Google/etc root\u003e ]\n\nwalk:  forgedLeaf -\u003e attackerSelfSignedRoot        (verifies, attacker controls both)\n       attackerSelfSignedRoot is self-signed        -\u003e break\n       attackerSelfSignedRoot -\u003e configured root    (NEVER CHECKED)\n```\n\n`return true`. The configured anchor never gets checked.\n\n\nAs far as observed, all attestation enforcement uses validateCertificatePath when using MDS etc.","modified":"2026-09-04T17:45:04.701505094Z","published":"2026-09-04T17:33:20Z","database_specific":{"severity":"LOW","github_reviewed":true,"github_reviewed_at":"2026-09-04T17:33:20Z","nvd_published_at":null,"cwe_ids":["CWE-295","CWE-296"]},"references":[{"type":"WEB","url":"https://github.com/MasterKale/SimpleWebAuthn/security/advisories/GHSA-6hxq-p678-4hr2"},{"type":"WEB","url":"https://github.com/MasterKale/SimpleWebAuthn/commit/67a41fed3dfd96cab1dcf414f6d1792a72e06e35"},{"type":"WEB","url":"https://github.com/MasterKale/SimpleWebAuthn/commit/8a53d70f42bcbb7c744ef1b90e6db35bc4b26d06"},{"type":"WEB","url":"https://github.com/MasterKale/SimpleWebAuthn/commit/dd0d73c716a528e6645efafbd43a972b64df71f9"},{"type":"PACKAGE","url":"https://github.com/MasterKale/SimpleWebAuthn"},{"type":"WEB","url":"https://github.com/MasterKale/SimpleWebAuthn/releases/tag/v13.3.2"}],"affected":[{"package":{"name":"@simplewebauthn/server","ecosystem":"npm","purl":"pkg:npm/%40simplewebauthn/server"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"13.3.2"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 13.3.1","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-6hxq-p678-4hr2/GHSA-6hxq-p678-4hr2.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:A/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N"}]}