angular / angular/angular-cli

Angular SSR route-policy confusion can expose client-only data under public cache headers

Open
#33,555 0 comments 0 reactions 0 assignees View on GitHub
area: @angular/ssr gemini-triaged
Dominant language
TypeScript
Stars
27k
Forks
11.8k
Avg merge
14h 23m
Merged PRs (30d)
162

Description

### Description

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:

```text
/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`.

### Minimal Reproduction

### Minimal configuration

```ts
import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
{
path: 'profile',
renderMode: RenderMode.Client,
headers: {
'Cache-Control': 'private, no-store',
},
},
{
path: 'profile/public',
renderMode: RenderMode.Server,
headers: {
'Cache-Control': 'public, max-age=300',
},
},
{
path: '**',
renderMode: RenderMode.Server,
headers: {
'Cache-Control': 'public, max-age=300',
},
},
];
```

The `/profile` component reads a benign request-derived marker through the SSR `REQUEST` token.

### Steps to reproduce

Request the normal client-only route:

```bash
curl -i \
-H 'Cookie: session=PRIVATE_VALUE' \
http://localhost:4000/profile
```

The initial HTML does not contain the request-derived value.

Request either crafted path:

```bash
curl -i \
-H 'Cookie: session=PRIVATE_VALUE' \
'http://localhost:4000/profile;'
```

```bash
curl -i \
-H 'Cookie: session=PRIVATE_VALUE' \
http://localhost:4000/profile//public
```

### Actual behavior

The crafted requests can:

- render the `/profile` component on the server;
- expose request-derived data in the initial HTML;
- apply `Cache-Control: public` metadata belonging to another server route.

### Expected behavior

The route used to select `renderMode`, status, and response headers must always correspond to the route whose body is rendered.

A route configured as `RenderMode.Client` should not be server-rendered through an alternative URL representation.

### Your Environment

```text
Angular 22.X
```

### Anything else relevant?

This was previously reported at https://issuetracker.google.com/u/1/issues/518988455

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the mismatch with the two curl requests and the minimal ServerRoute configuration. Trace how Angular SSR selects renderMode and response headers for /profile; and how Angular Router renders /profile; and /profile//public. Done means the rendered body, renderMode, and cache headers always correspond to the same route, with no request-derived value exposed for the client-only route.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, typescript
Domain
backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.