diff --git a/packages/router/src/url_tree.ts b/packages/router/src/url_tree.ts index ee6853cc0872..3a9e62175d28 100644 --- a/packages/router/src/url_tree.ts +++ b/packages/router/src/url_tree.ts @@ -453,7 +453,8 @@ export class DefaultUrlSerializer implements UrlSerializer { /** Converts a `UrlTree` into a url */ serialize(tree: UrlTree): string { - const segment = `/${serializeSegment(tree.root, true)}`; + // Empty leading path segments would make the result protocol-relative in browsers. + const segment = `/${serializeSegment(tree.root, true).replace(/^\/+/, '')}`; const query = serializeQueryParams(tree.queryParams); const fragment = typeof tree.fragment === `string` ? `#${encodeUriFragment(tree.fragment)}` : ''; diff --git a/packages/router/test/create_url_tree.spec.ts b/packages/router/test/create_url_tree.spec.ts index 8132136d1ed5..668c3fe4902f 100644 --- a/packages/router/test/create_url_tree.spec.ts +++ b/packages/router/test/create_url_tree.spec.ts @@ -107,6 +107,12 @@ describe('createUrlTree', () => { expect(serializer.serialize(t)).toEqual('/'); }); + it('should not serialize leading empty path commands as a protocol-relative URL', async () => { + const p = serializer.parse('/'); + const t = await createRoot(p, ['/', '', 'attacker.example', 'collect'], {session: 'secret'}); + expect(serializer.serialize(t)).toEqual('/attacker.example/collect?session=secret'); + }); + it('should error when navigating to the root segment with params', async () => { const p = serializer.parse('/'); await expectAsync(createRoot(p, ['/', {p: 11}])).toBeRejectedWithError(