You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Angular SSR route-policy confusion can expose client-only data under public cache headers #33555
Angular SSR can select server-route metadata from one route while Angular Router renders a different route when the request URL contains certain ambiguous path forms.
Example:
/profile;
/profile//public
In both cases, a route configured as RenderMode.Client can be unexpectedly rendered on the server while inheriting public cache headers from another ServerRoute.
Confirming this independently, and apologising for the duplicate: I filed #34090 two months after this and my prior-art sweep missed it. A single search for "route-policy" finds this issue immediately. I had searched my own vocabulary, "route-tree tokenisation" and "matcher divergence", rather than the words a reporter would use. That is on me, and I am closing #34090 in favour of this one.
Posting the additional coverage here so it is not lost, since some of it is not in the original report.
The other grammar has a name. The divergence is RouteTree.getPathSegments():
against @angular/router's DefaultUrlSerializer, in which (, ), ; and // are metacharacters and unparseable input is silently discarded. Verified against the published @angular/router 22.1.6, serialize(parse(x)):
input
router resolves to
/page)
/page
/page(
/page
/page;
/page
/(page)
/page
/a/1//b
/a/1
/a/b)c/d
/a/b
Parentheses are part of the class. This issue names ; and //. ( and ) do the same thing, and /(profile) is a valid spelling of /profile because angular/angular#64507 deliberately made an unnamed (...) group mean the primary outlet. Enumerated over printable ASCII, the single-character triggers are exactly (, ) and ;. A differential sweep of 1575 paths produced 80 divergent paths in two root shapes.
The attacker picks which route's policy to borrow, not just the catch-all. The mangled segment list is matched normally, so any route pattern that fits it is selectable. With a long-lived public sub-route under a private prefix, which applications routinely add for share links and print views:
Line three is the account page under a year-long immutable label.
Content-Type and status are attacker-selected too, because handleRendering spreads the matched route's headers after Content-Type and takes status from the matched route.
What is NOT affected:canActivate and the other router guards still run, because @angular/router sees an ordinary path. Not an authorization bypass.
There is a fix in #34091, which normalises the pathname through the router's own serializer in ServerRouter.match before tokenising, so both matchers agree. It covers ;, // and the parentheses in one place rather than case by case, the way stripMatrixParams handled the matrix-parameter symptom in 85c18b4. Tests cover the divergent spellings and were confirmed to fail before the change. I will repoint it at this issue.
Activity
glivter commentedon Sep 14, 2026
Confirming this independently, and apologising for the duplicate: I filed #34090 two months after this and my prior-art sweep missed it. A single search for "route-policy" finds this issue immediately. I had searched my own vocabulary, "route-tree tokenisation" and "matcher divergence", rather than the words a reporter would use. That is on me, and I am closing #34090 in favour of this one.
Posting the additional coverage here so it is not lost, since some of it is not in the original report.
The other grammar has a name. The divergence is
RouteTree.getPathSegments():against
@angular/router'sDefaultUrlSerializer, in which(,),;and//are metacharacters and unparseable input is silently discarded. Verified against the published@angular/router22.1.6,serialize(parse(x)):/page)/page/page(/page/page;/page/(page)/page/a/1//b/a/1/a/b)c/d/a/bParentheses are part of the class. This issue names
;and//.(and)do the same thing, and/(profile)is a valid spelling of/profilebecause angular/angular#64507 deliberately made an unnamed(...)group mean the primary outlet. Enumerated over printable ASCII, the single-character triggers are exactly(,)and;. A differential sweep of 1575 paths produced 80 divergent paths in two root shapes.The attacker picks which route's policy to borrow, not just the catch-all. The mangled segment list is matched normally, so any route pattern that fits it is selectable. With a long-lived public sub-route under a private prefix, which applications routinely add for share links and print views:
Line three is the account page under a year-long immutable label.
Content-Typeand status are attacker-selected too, becausehandleRenderingspreads the matched route'sheadersafterContent-Typeand takesstatusfrom the matched route.What is NOT affected:
canActivateand the other router guards still run, because@angular/routersees an ordinary path. Not an authorization bypass.There is a fix in #34091, which normalises the pathname through the router's own serializer in
ServerRouter.matchbefore tokenising, so both matchers agree. It covers;,//and the parentheses in one place rather than case by case, the waystripMatrixParamshandled the matrix-parameter symptom in 85c18b4. Tests cover the divergent spellings and were confirmed to fail before the change. I will repoint it at this issue.fix(@angular/ssr): resolve the request path through the router's URL …