Skip to content

Commit 86cbf91

Browse files
committed
fix(auth): prevent javascript url injection in oauth endpoints
1 parent f657ead commit 86cbf91

File tree

3 files changed

+50
-6
lines changed

3 files changed

+50
-6
lines changed

src/client/auth.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
OpenIdProviderDiscoveryMetadataSchema
1313
} from "../shared/auth.js";
1414
import { OAuthClientInformationFullSchema, OAuthMetadataSchema, OAuthProtectedResourceMetadataSchema, OAuthTokensSchema } from "../shared/auth.js";
15-
import { checkResourceAllowed, resourceUrlFromServerUrl } from "../shared/auth-utils.js";
15+
import { checkResourceAllowed, resourceUrlFromServerUrl, isValidOAuthScheme } from "../shared/auth-utils.js";
1616
import {
1717
InvalidClientError,
1818
InvalidGrantError,
@@ -820,6 +820,9 @@ export async function startAuthorization(
820820
let authorizationUrl: URL;
821821
if (metadata) {
822822
authorizationUrl = new URL(metadata.authorization_endpoint);
823+
if (!isValidOAuthScheme(authorizationUrl)) {
824+
throw new Error(`Invalid authorization_endpoint URL scheme: ${authorizationUrl.protocol}. Only http: and https: are allowed.`);
825+
}
823826

824827
if (!metadata.response_types_supported.includes(responseType)) {
825828
throw new Error(
@@ -911,9 +914,15 @@ export async function exchangeAuthorization(
911914
): Promise<OAuthTokens> {
912915
const grantType = "authorization_code";
913916

914-
const tokenUrl = metadata?.token_endpoint
915-
? new URL(metadata.token_endpoint)
916-
: new URL("/token", authorizationServerUrl);
917+
let tokenUrl: URL;
918+
if (metadata?.token_endpoint) {
919+
tokenUrl = new URL(metadata.token_endpoint);
920+
if (!isValidOAuthScheme(tokenUrl)) {
921+
throw new Error(`Invalid token_endpoint URL scheme: ${tokenUrl.protocol}. Only http: and https: are allowed.`);
922+
}
923+
} else {
924+
tokenUrl = new URL("/token", authorizationServerUrl);
925+
}
917926

918927
if (
919928
metadata?.grant_types_supported &&
@@ -998,6 +1007,9 @@ export async function refreshAuthorization(
9981007
let tokenUrl: URL;
9991008
if (metadata) {
10001009
tokenUrl = new URL(metadata.token_endpoint);
1010+
if (!isValidOAuthScheme(tokenUrl)) {
1011+
throw new Error(`Invalid token_endpoint URL scheme: ${tokenUrl.protocol}. Only http: and https: are allowed.`);
1012+
}
10011013

10021014
if (
10031015
metadata.grant_types_supported &&
@@ -1069,6 +1081,9 @@ export async function registerClient(
10691081
}
10701082

10711083
registrationUrl = new URL(metadata.registration_endpoint);
1084+
if (!isValidOAuthScheme(registrationUrl)) {
1085+
throw new Error(`Invalid registration_endpoint URL scheme: ${registrationUrl.protocol}. Only http: and https: are allowed.`);
1086+
}
10721087
} else {
10731088
registrationUrl = new URL("/register", authorizationServerUrl);
10741089
}

src/shared/auth-utils.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { resourceUrlFromServerUrl, checkResourceAllowed } from './auth-utils.js';
1+
import { resourceUrlFromServerUrl, checkResourceAllowed, isValidOAuthScheme } from './auth-utils.js';
22

33
describe('auth-utils', () => {
44
describe('resourceUrlFromServerUrl', () => {
@@ -58,4 +58,18 @@ describe('auth-utils', () => {
5858
expect(checkResourceAllowed({ requestedResource: 'https://example.com/folder', configuredResource: 'https://example.com/folder/' })).toBe(false);
5959
});
6060
});
61+
62+
describe('isValidOAuthScheme', () => {
63+
it('should accept http and https URLs', () => {
64+
expect(isValidOAuthScheme(new URL('https://auth.example.com/oauth'))).toBe(true);
65+
expect(isValidOAuthScheme(new URL('http://localhost:8080/token'))).toBe(true);
66+
});
67+
68+
it('should reject dangerous schemes', () => {
69+
expect(isValidOAuthScheme(new URL('javascript:alert("XSS")'))).toBe(false);
70+
expect(isValidOAuthScheme(new URL('data:text/html,<script>alert("XSS")</script>'))).toBe(false);
71+
expect(isValidOAuthScheme(new URL('file:///etc/passwd'))).toBe(false);
72+
expect(isValidOAuthScheme(new URL('ftp://malicious.com/file'))).toBe(false);
73+
});
74+
});
6175
});

src/shared/auth-utils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Utilities for handling OAuth resource URIs.
2+
* Utilities for handling OAuth resource URIs and security validation.
33
*/
44

55
/**
@@ -52,3 +52,18 @@ export function resourceUrlFromServerUrl(url: URL | string ): URL {
5252

5353
return requestedPath.startsWith(configuredPath);
5454
}
55+
56+
/**
57+
* Validates that a URL uses a safe scheme for OAuth endpoints (http or https only).
58+
*
59+
* This prevents XSS (Cross-Site Scripting) and RCE (Remote Code Execution) attacks
60+
* where malicious authorization servers could return endpoints with dangerous schemes
61+
* like javascript:, data:, file:, etc. that could lead to code execution when
62+
* processed by OAuth clients.
63+
*
64+
* @param url - The URL to validate
65+
* @returns true if the URL scheme is safe (http: or https:), false otherwise
66+
*/
67+
export function isValidOAuthScheme(url: URL): boolean {
68+
return ['https:', 'http:'].includes(url.protocol);
69+
}

0 commit comments

Comments
 (0)