fix(platform-server): avoid stripping unicode whitespace during url resolution

Avoid trimming urlStr with String.prototype.trim() in resolveUrl to ensure URL parsing and resolution align with the WHATWG URL standard.

(cherry picked from commit 3e924cc8db)
This commit is contained in:
Alan Agius
2026-08-25 11:27:58 +00:00
committed by leonsenft
parent e8378dfeab
commit 71e52d1396
3 changed files with 28 additions and 2 deletions
-2
View File
@@ -57,8 +57,6 @@ export function resolveUrl(
return originUrl || null;
}
urlStr = urlStr.trim();
// Fast-path: if the URL is a valid, standard absolute URL, parse and return it immediately.
let resolved: URL | undefined;
try {
@@ -1582,6 +1582,17 @@ class HiddenModule {}
});
});
it('should resolve non-breaking space prefixed URLs as relative paths on the same origin', async () => {
ref.injector.get(NgZone).run(() => {
http.get('\u00A0//attacker.example/collect').subscribe((body) => {
expect(body).toEqual('success!');
});
mock
.expectOne('http://localhost:4000/%C2%A0//attacker.example/collect')
.flush('success!');
});
});
it('should reject backslash bypass SSRF attempts in relative requests and throw a suspicious origin error', async () => {
const badUrls = [
'/\\attacker.com',
+17
View File
@@ -68,6 +68,22 @@ describe('resolveUrl', () => {
const url = 'ht\ntp://evil.com/path';
expect(() => resolveUrl(url, 'http://test.com')).toThrowError(/NG05703/);
});
it('should not trim unicode whitespace into protocol-relative URLs', () => {
const urls = ['\u00A0//attacker.example/collect', '\uFEFF//attacker.example/collect'];
for (const urlStr of urls) {
const urlWithProtocolRelative = resolveUrl(urlStr, 'http://test.com', {
allowProtocolRelative: true,
});
expect(urlWithProtocolRelative.origin).toBe('http://test.com');
expect(urlWithProtocolRelative.pathname).toContain('//attacker.example/collect');
const urlWithoutProtocolRelative = resolveUrl(urlStr, 'http://test.com');
expect(urlWithoutProtocolRelative.origin).toBe('http://test.com');
expect(urlWithoutProtocolRelative.pathname).toContain('//attacker.example/collect');
}
});
});
describe('without origin', () => {
@@ -76,6 +92,7 @@ describe('resolveUrl', () => {
expect(resolveUrl('deep/path')).toBeNull();
expect(resolveUrl('/\\attacker.com/deep/path')).toBeNull();
expect(resolveUrl('\\\\attacker.com/deep/path')).toBeNull();
expect(resolveUrl('\u00A0//attacker.com/deep/path')).toBeNull();
});
it('should parse valid absolute URLs', () => {