{"id":"GHSA-6hxq-p678-4hr2","title":"SimpleWebAuthn: Registration verification does not sufficiently ensure that attestation certificates chain to a trust anchor","summary":"SimpleWebAuthn: Registration verification does not sufficiently ensure that attestation certificates chain to a trust anchor","severity":"low","cwe":["CWE-295","CWE-296"],"vendor":"simplewebauthn","product":"@simplewebauthn/server","ecosystem":"npm","affected":["@simplewebauthn/server <= 13.3.1"],"patched":["@simplewebauthn/server 13.3.2"],"published":"2026-09-04","updated":"2026-09-04","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-6hxq-p678-4hr2","references":[{"url":"https://github.com/MasterKale/SimpleWebAuthn/security/advisories/GHSA-6hxq-p678-4hr2"},{"url":"https://github.com/MasterKale/SimpleWebAuthn/commit/67a41fed3dfd96cab1dcf414f6d1792a72e06e35"},{"url":"https://github.com/MasterKale/SimpleWebAuthn/commit/8a53d70f42bcbb7c744ef1b90e6db35bc4b26d06"},{"url":"https://github.com/MasterKale/SimpleWebAuthn/commit/dd0d73c716a528e6645efafbd43a972b64df71f9"},{"url":"https://github.com/MasterKale/SimpleWebAuthn/releases/tag/v13.3.2"},{"url":"https://github.com/advisories/GHSA-6hxq-p678-4hr2"}],"tags":["ghsa","npm"],"ingestedAt":"2026-09-04T18:25:57.348Z","slug":"GHSA-6hxq-p678-4hr2","body":"## Overview\n\n## 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;   // <-- 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, <configured Apple/Google/etc root> ]\n\nwalk:  forgedLeaf -> attackerSelfSignedRoot        (verifies, attacker controls both)\n       attackerSelfSignedRoot is self-signed        -> break\n       attackerSelfSignedRoot -> 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.\n\n## Affected packages\n\n- `@simplewebauthn/server <= 13.3.1`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `@simplewebauthn/server 13.3.2`","depth":"sunlit","depthScore":14,"depthScoreParts":{"impact":13.8,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}