Merge commit from fork

* Fix redirect URI allowlist bypass via URL userinfo syntax

* Validate redirect URIs against normalized origin+pathname
This commit is contained in:
Ben Lambert
2026-03-11 13:33:00 +01:00
committed by GitHub
parent b9a41c0a22
commit 0f9d673e82
3 changed files with 46 additions and 14 deletions
@@ -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 });
+14 -14
View File
@@ -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');