Problem
The appid and appidExclude FIDO extensions are accepted and acted on without any FacetID/AppID authorization. The AppID URL is never compared to the caller origin or rp.id, so a caller from https://example.org can pass an AppID like https://example.net/u2f.
appid (auth). validate_appid in libwebauthn/src/ops/webauthn/get_assertion.rs only rejects empty strings and non-https:// URLs, and never sees the caller origin. The value feeds GetAssertionRequest::try_downgrade, which signs each credential under SHA-256(appid) on the U2F downgrade path. A cross-site AppID yields assertions under an unrelated site's legacy application parameter.
appidExclude (registration). Not validated at all. MakeCredentialRequest::from_idl_model passes extensions through verbatim. The value reaches ctap2_preflight_with_appid (libwebauthn/src/proto/ctap2/preflight.rs) via make_credential_fido2 (libwebauthn/src/webauthn.rs) and probes each excludeList credential under the AppID, enabling enumeration under another site's AppID.
appidExclude output missing. MakeCredentialResponse::build_client_extension_results never emits it. The field is commented out and AuthenticationExtensionsClientOutputsJSON in idl/response.rs has no appidExclude member. (The appid auth output is emitted correctly.)
Why it matters
WebAuthn L3 §10.1.1 (appid) and §10.1.2 (appidExclude) require the client to run the FacetID authorization check and return SecurityError when the AppID is not authorized. For web callers this is a same-site host comparison between the AppID and the caller origin. Without it, a site can borrow another site's legacy AppID to get assertions or enumerate excluded credentials.
What needs doing
Problem
The
appidandappidExcludeFIDO extensions are accepted and acted on without any FacetID/AppID authorization. The AppID URL is never compared to the caller origin orrp.id, so a caller fromhttps://example.orgcan pass an AppID likehttps://example.net/u2f.appid(auth).validate_appidinlibwebauthn/src/ops/webauthn/get_assertion.rsonly rejects empty strings and non-https://URLs, and never sees the caller origin. The value feedsGetAssertionRequest::try_downgrade, which signs each credential underSHA-256(appid)on the U2F downgrade path. A cross-site AppID yields assertions under an unrelated site's legacy application parameter.appidExclude(registration). Not validated at all.MakeCredentialRequest::from_idl_modelpasses extensions through verbatim. The value reachesctap2_preflight_with_appid(libwebauthn/src/proto/ctap2/preflight.rs) viamake_credential_fido2(libwebauthn/src/webauthn.rs) and probes eachexcludeListcredential under the AppID, enabling enumeration under another site's AppID.appidExcludeoutput missing.MakeCredentialResponse::build_client_extension_resultsnever emits it. The field is commented out andAuthenticationExtensionsClientOutputsJSONinidl/response.rshas noappidExcludemember. (Theappidauth output is emitted correctly.)Why it matters
WebAuthn L3 §10.1.1 (
appid) and §10.1.2 (appidExclude) require the client to run the FacetID authorization check and returnSecurityErrorwhen the AppID is not authorized. For web callers this is a same-site host comparison between the AppID and the caller origin. Without it, a site can borrow another site's legacy AppID to get assertions or enumerate excluded credentials.What needs doing
appid: the AppID host must be a registrable-domain suffix of, or equal to, the caller origin host. Reuseis_registrable_domain_suffix_or_equal/rp_id_authorised, and wire the caller origin and settings intovalidate_appid.appidExcludein the make-credential path. Add anInvalidAppIdvariant toMakeCredentialPrepareError.SecurityErrorat the WebAuthn boundary.appidExcludeBoolean output: add the field toAuthenticationExtensionsClientOutputsJSONand populate it inbuild_client_extension_results.https://example.org, AppIDhttps://example.net/...) for both extensions, accept a same-site one (https://www.example.org/...), and assertappidExclude: truewhen a credential is excluded.