From 1249e9c7a548d8ba4d9fe210dfa557b8c15feb9f Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Thu, 14 May 2026 10:34:47 -0400 Subject: [PATCH] [PM-36616] Fix fido2 script injection not respecting blocked domains (#20551) * fix fido2 script injection not respecting blocked domains * use same exact-match pattern as excluded domains check --- .../fido2/fido2-client.service.spec.ts | 47 +++++++++++++++++++ .../services/fido2/fido2-client.service.ts | 8 ++++ 2 files changed, 55 insertions(+) diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts index 08f8abb1549..b81d4aa5ff8 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.spec.ts @@ -85,6 +85,7 @@ describe("FidoAuthenticatorService", () => { configService.serverConfig$ = of({ environment: { vault: VaultUrl } } as any); vaultSettingsService.enablePasskeys$ = of(true); domainSettingsService.neverDomains$ = of({}); + domainSettingsService.blockedInteractionsUris$ = of({}); authService.activeAccountStatus$ = of(AuthenticationStatus.Unlocked); windowReference = Utils.newGuid(); }); @@ -710,6 +711,52 @@ describe("FidoAuthenticatorService", () => { }; } }); + + describe("isFido2FeatureEnabled", () => { + const hostname = "sub.example.com"; + const origin = "https://sub.example.com"; + + it("returns false when the hostname exactly matches a `blockedInteractionsUris` entry", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ "sub.example.com": null }); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(false); + }); + + it("returns true when the hostname is a subdomain of a `blockedInteractionsUris` entry", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ "example.com": null }); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(true); + }); + + it("returns true when `blockedInteractionsUris` is empty", async () => { + domainSettingsService.blockedInteractionsUris$ = of({}); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(true); + }); + + it("returns true when no `blockedInteractionsUris` entry matches the hostname", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ "bitwarden.com": null }); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(true); + }); + + it("rejects via `blockedInteractionsUris` regardless of `neverDomains` state", async () => { + domainSettingsService.blockedInteractionsUris$ = of({ "sub.example.com": null }); + domainSettingsService.neverDomains$ = of({}); + + const result = await client.isFido2FeatureEnabled(hostname, origin); + + expect(result).toBe(false); + }); + }); }); /** This is a fake function that always returns the same byte sequence */ diff --git a/libs/common/src/platform/services/fido2/fido2-client.service.ts b/libs/common/src/platform/services/fido2/fido2-client.service.ts index b80023b8bc2..8d724134261 100644 --- a/libs/common/src/platform/services/fido2/fido2-client.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-client.service.ts @@ -90,6 +90,14 @@ export class Fido2ClientService< return false; } + const blockedInteractionsUris = await firstValueFrom( + this.domainSettingsService.blockedInteractionsUris$, + ); + const isBlockedDomain = blockedInteractionsUris != null && hostname in blockedInteractionsUris; + if (isBlockedDomain) { + return false; + } + const neverDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); const isExcludedDomain = neverDomains != null && hostname in neverDomains;