Skip to content

[LiveComponent] Fix svg re-render - #557

Merged
weaverryan merged 1 commit into
symfony:2.xfrom
norkunas:svg
Nov 28, 2022
Merged

weaverryan merged 1 commit into
symfony:2.xfrom
norkunas:svg

Conversation

@norkunas

Copy link
Copy Markdown
Contributor
Q A
Bug fix? no
New feature? no
Tickets #556
License MIT

@weaverryan

Copy link
Copy Markdown
Contributor

Thanks for the reproducer test - super useful. I can't think, immediately, what the problem might be - maybe something in morphdom? Unless someone beats me to it (which would be great!) I'll check it out when I have some time.

@norkunas

Copy link
Copy Markdown
Contributor Author

Thought about morphdom too, but they have svg tests,so not sure how this could happen currently..

@norkunas
norkunas force-pushed the svg branch 3 times, most recently from a1e6ba9 to dbc93e8 Compare November 23, 2022 08:23
@norkunas norkunas changed the title [LiveComponent] Add failing test for SVG updates after re-render [LiveComponent] Fix svg re-render Nov 23, 2022
@norkunas

Copy link
Copy Markdown
Contributor Author

Found the root cause and added a fix.

if (
!(fromEl instanceof HTMLElement || fromEl instanceof SVGElement) ||
!(toEl instanceof HTMLElement || toEl instanceof SVGElement)
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice find. SVG's not being part of HTMLElement is such a pain in the butt :). Thanks for the test case to prevent it from slipping through in the future.

@weaverryan

Copy link
Copy Markdown
Contributor

Thanks Tomas!

@weaverryan
weaverryan merged commit e27d318 into symfony:2.x Nov 28, 2022
@norkunas
norkunas deleted the svg branch November 28, 2022 16:10
fabpot pushed a commit that referenced this pull request May 29, 2026
…ent CSRF (Kocal)

This PR was merged into the ux-2.x branch.

Discussion
----------

[LiveComponent] Require X-Requested-With header to prevent CSRF

## Summary

Related to #390.

`LiveComponentSubscriber::isLiveComponentRequest()` previously gated requests on the `Accept: application/vnd.live-component+html` header, with a comment claiming this acted as CSRF protection.
That claim is wrong: `Accept` is a [CORS-safelisted request header](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) — a cross-origin `fetch()` can set it without triggering a
preflight, so the header provides no protection against forged requests.

### Attack illustration

Before the fix, a page hosted on `evil.com` could invoke any `#[LiveAction]` on a victim's session:

```http
POST /_components/UserSettings/deleteAccount HTTP/1.1
Host: app.example.com
Accept: application/vnd.live-component+html
Cookie: PHPSESSID=<victim session>
Content-Type: multipart/form-data; boundary=...

data={"props":{}}
```

The browser sends this request directly (no preflight) because every header used is CORS-safelisted. In practice the attack is mitigated by `SameSite=Lax` session cookies (Symfony default), but
apps using `SameSite=None` or `credentials: 'include'` remain exposed.

### Fix

Also require `X-Requested-With: XMLHttpRequest`. This header is non-safelisted, so the browser issues a preflight `OPTIONS` request for any cross-origin attempt. Symfony does not advertise CORS
for LiveComponent endpoints, the preflight fails, and the browser blocks the real request before it ever reaches the application.

The bundled JS client already sends `X-Requested-With` on every request (`src/LiveComponent/assets/src/Backend/RequestBuilder.ts`), so apps relying on the standard Stimulus controller see no change.

### After the fix

Cross-origin request with only `Accept` is rejected (treated as not-a-LiveComponent-request → 404):

```http
POST /_components/UserSettings/deleteAccount HTTP/1.1
Host: app.example.com
Accept: application/vnd.live-component+html

→ HTTP/1.1 404 Not Found
```

Same request with the non-safelisted header — only reachable from same-origin or via an explicit CORS allow-list — proceeds as before:

```http
POST /_components/UserSettings/deleteAccount HTTP/1.1
Host: app.example.com
Accept: application/vnd.live-component+html
X-Requested-With: XMLHttpRequest

→ HTTP/1.1 200 OK   (or 204 on redirect)
```

### BC break

Minor. Clients that call LiveComponent endpoints from a different origin must add `X-Requested-With` to their CORS `Access-Control-Allow-Headers` allow-list. Apps using the bundled Stimulus
controller are not affected.

Commits
-------

aed7493 [LiveComponent] Require X-Requested-With header to prevent CSRF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants