From 68c117d186ced8413e96d5c4b20b810b60b059db Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Mon, 23 Mar 2026 23:02:54 +0100 Subject: [PATCH 1/2] fix: normalize host-only allowed origins to https:// scheme Host-only entries in allowed_origins (e.g. "example.com") were matched against the incoming origin's host without checking the scheme or port, allowing origins like https://example.com:8443 to bypass validation. Since WebAuthn requires TLS, host-only entries are now normalized to https://{host} in the constructor and go through the full origin match (scheme + host + port), closing this bypass. Fixes #817 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/CeremonyStep/CheckAllowedOrigins.php | 38 +++---------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php b/src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php index 95e3b106..1a6ec9a6 100644 --- a/src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php +++ b/src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php @@ -23,19 +23,12 @@ final readonly class CheckAllowedOrigins implements CeremonyStep { /** - * Full origin entries (scheme://host[:port]) from allowed origins that include a scheme. + * Full origin entries (scheme://host[:port]) from allowed origins. * * @var string[] */ private array $fullOrigins; - /** - * Host-only entries from allowed origins without a scheme (backward compatibility). - * - * @var string[] - */ - private array $hostOrigins; - /** * @param string[] $allowedOrigins * @param string[] $securedRelyingPartyId RP IDs that are allowed to use HTTP (e.g. localhost for development) @@ -46,19 +39,19 @@ public function __construct( private array $securedRelyingPartyId = [], ) { $fullOrigins = []; - $hostOrigins = []; foreach ($allowedOrigins as $allowedOrigin) { $parsed = parse_url($allowedOrigin); $parsed !== false || throw new InvalidArgumentException(sprintf('Invalid origin: %s', $allowedOrigin)); if (isset($parsed['scheme'], $parsed['host'])) { $fullOrigins[] = self::buildOrigin($parsed['scheme'], $parsed['host'], $parsed['port'] ?? null); } else { - $hostOrigins[] = $parsed['host'] ?? $allowedOrigin; + // Host-only entries are normalized to https:// since WebAuthn requires TLS + $host = $parsed['host'] ?? $allowedOrigin; + $fullOrigins[] = self::buildOrigin('https', $host, null); } } $this->fullOrigins = array_unique($fullOrigins); - $this->hostOrigins = array_unique($hostOrigins); } public function process( @@ -77,7 +70,7 @@ public function process( ); $originHost = $parsedOrigin['host'] ?? $C->origin; - $hasAllowedOrigins = count($this->fullOrigins) !== 0 || count($this->hostOrigins) !== 0; + $hasAllowedOrigins = count($this->fullOrigins) !== 0; if ($hasAllowedOrigins) { // Full origin match (scheme + host + port) @@ -92,15 +85,8 @@ public function process( } } - // Host-only match (backward compatibility for entries without scheme) - if (in_array($originHost, $this->hostOrigins, true)) { - return; - } - // Subdomain matching - $isFullOriginSubdomain = $this->isSubdomainOfFullOrigins($parsedOrigin); - $isHostSubdomain = $this->isSubdomain($this->hostOrigins, $originHost); - $isSubDomain = $isFullOriginSubdomain || $isHostSubdomain; + $isSubDomain = $this->isSubdomainOfFullOrigins($parsedOrigin); if ($this->allowSubdomains && $isSubDomain) { return; @@ -215,16 +201,4 @@ private function getFacetId( return (is_string($appId) && $wasUsed === true) ? $appId : $rpId; } - /** - * @param string[] $origins - */ - private function isSubdomain(array $origins, string $domain): bool - { - foreach ($origins as $allowedOrigin) { - if ($this->isSubdomainOf($domain, $allowedOrigin)) { - return true; - } - } - return false; - } } From bfb2f334f779fd44a073138ca5421c865cf7535f Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Mon, 23 Mar 2026 23:11:33 +0100 Subject: [PATCH 2/2] fix: coding standards Co-Authored-By: Claude Opus 4.6 (1M context) --- src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php b/src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php index 1a6ec9a6..a31f0175 100644 --- a/src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php +++ b/src/webauthn/src/CeremonyStep/CheckAllowedOrigins.php @@ -200,5 +200,4 @@ private function getFacetId( return (is_string($appId) && $wasUsed === true) ? $appId : $rpId; } - }