VYPR
Moderate severityNVD Advisory· Published Mar 12, 2026· Updated Mar 12, 2026

@backstage/plugin-auth-backend: OAuth redirect URI allowlist bypass

CVE-2026-32235

Description

Backstage is an open framework for building developer portals. Prior to 0.27.1, the experimental OIDC provider in @backstage/plugin-auth-backend is vulnerable to a redirect URI allowlist bypass. Instances that have enabled experimental Dynamic Client Registration or Client ID Metadata Documents and configured allowedRedirectUriPatterns are affected. A specially crafted redirect URI can pass the allowlist validation while resolving to an attacker-controlled host. If a victim approves the resulting OAuth consent request, their authorization code is sent to the attacker, who can exchange it for a valid access token. This requires victim interaction and that one of the experimental features is explicitly enabled, which is not the default. This vulnerability is fixed in 0.27.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@backstage/plugin-auth-backendnpm
< 0.27.10.27.1

Affected products

1

Patches

1
6042dd0c7f07

Merge commit from fork

https://github.com/backstage/backstageBen LambertMar 11, 2026via ghsa
3 files changed · +46 14
  • .changeset/fix-redirect-uri-userinfo-bypass.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +'@backstage/plugin-auth-backend': patch
    +---
    +
    +Improved redirect URI validation in the experimental OIDC provider to match against normalized URLs rather than raw strings.
    
  • plugins/auth-backend/src/service/OidcService.test.ts+27 0 modified
    @@ -283,6 +283,33 @@ describe('OidcService', () => {
             );
           });
     
    +      it('should reject redirect URIs containing userinfo', async () => {
    +        const { service } = await createOidcService({
    +          databaseId,
    +          config: {
    +            auth: {
    +              experimentalDynamicClientRegistration: {
    +                allowedRedirectUriPatterns: ['http://localhost:*'],
    +              },
    +            },
    +          },
    +        });
    +
    +        await expect(
    +          service.registerClient({
    +            clientName: 'Evil Client',
    +            redirectUris: ['http://localhost:3000@attacker.example/callback'],
    +          }),
    +        ).rejects.toThrow('Invalid redirect_uri');
    +
    +        await expect(
    +          service.registerClient({
    +            clientName: 'Evil Client',
    +            redirectUris: ['http://user:pass@example.com/callback'],
    +          }),
    +        ).rejects.toThrow('Invalid redirect_uri');
    +      });
    +
           it('should create a client with default values', async () => {
             const { service } = await createOidcService({ databaseId });
     
    
  • plugins/auth-backend/src/service/OidcService.ts+14 14 modified
    @@ -29,6 +29,18 @@ import matcher from 'matcher';
     import { OfflineAccessService } from './OfflineAccessService';
     import { validateCimdUrl, fetchCimdMetadata } from './CimdClient';
     
    +function validateRedirectUri(
    +  redirectUri: string,
    +  allowedPatterns: string[],
    +): void {
    +  const parsed = new URL(redirectUri);
    +  const normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
    +
    +  if (!allowedPatterns.some(pattern => matcher.isMatch(normalized, pattern))) {
    +    throw new InputError('Invalid redirect_uri');
    +  }
    +}
    +
     export class OidcService {
       private readonly auth: AuthService;
       private readonly tokenIssuer: TokenIssuer;
    @@ -162,13 +174,7 @@ export class OidcService {
         ) ?? ['*'];
     
         for (const redirectUri of opts.redirectUris ?? []) {
    -      if (
    -        !allowedRedirectUriPatterns.some(pattern =>
    -          matcher.isMatch(redirectUri, pattern),
    -        )
    -      ) {
    -        throw new InputError('Invalid redirect_uri');
    -      }
    +      validateRedirectUri(redirectUri, allowedRedirectUriPatterns);
         }
     
         return await this.oidc.createClient({
    @@ -305,13 +311,7 @@ export class OidcService {
         });
     
         if (opts.redirectUri) {
    -      if (
    -        !cimd.allowedRedirectUriPatterns.some(pattern =>
    -          matcher.isMatch(opts.redirectUri!, pattern),
    -        )
    -      ) {
    -        throw new InputError('Invalid redirect_uri');
    -      }
    +      validateRedirectUri(opts.redirectUri, cimd.allowedRedirectUriPatterns);
     
           if (!cimdClient.redirectUris.includes(opts.redirectUri)) {
             throw new InputError('Redirect URI not registered');
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.