LizardByte / LizardByte/Sunshine
PIN pairing fails with "Missing CSRF token" - web UI never fetches/attaches the token
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 41.3k
- Forks
- 2.1k
- Avg merge
- 23h 47m
- Merged PRs (30d)
- 124
Description
### Is there an existing issue for this?
- [x] I have searched the existing issues
### Is your issue described in the documentation?
- [x] I have read the documentation
### Is your issue present in the latest beta/pre-release?
I'm too lazy to test
### Describe the Bug
Pairing a Moonlight client via the web UI's PIN Pairing page (/pin) always fails with a "CSRF Protection Error" toast, even though `csrf_allowed_origins` in sunshine.conf already lists the exact origin being used to access the UI.
I confirmed this is not an origin allow-list problem. Using the browser's dev tools, I instrumented `window.fetch` on the /pin page and captured the exact request the "Send" button issues:
POST ./api/pin
Headers: { "Content-Type": "application/json" }
Body: {"pin":"1234","name":"test-device"}
There is no `X-CSRF-Token` header, and the page never calls GET /api/csrf-token beforehand to obtain one. The server correctly rejects this with `{"error":"Missing CSRF token","status":false,"status_code":400}`.
I confirmed the CSRF mechanism itself works fine server-side: manually fetching a token and attaching it as X-CSRF-Token on the same POST /api/pin request succeeds (200, `{"status":true}`). So the bug is specifically that the PIN Pairing page's own frontend code never fetches or attaches the CSRF token before submitting.
### Expected Behavior
Entering a valid PIN and Device Name and clicking "Send" should successfully pair the Moonlight client, the same way manually attaching a fetched X-CSRF-Token to the same request does.
### Additional Context
Steps to reproduce:
1. Configure csrf_allowed_origins in sunshine.conf to include the exact origin used to access the web UI (ruling out an origin-allowlist problem).
2. 2. Navigate to /pin, enter any PIN and device name, click Send.
3. 3. Observe the "CSRF Protection Error" toast (added in #4855 / v2026.516.143833) and a 400 response from POST /api/pin with body {"error":"Missing CSRF token",...}.
Workaround that confirms the CSRF mechanism itself is fine: call GET /api/csrf-token (Basic Auth) to obtain a token, then POST /api/pin with that token in an X-CSRF-Token header. That request succeeds normally. It's specifically the /pin page's own frontend code that never performs step 1 of that flow.
Related but distinct issues I found while searching before filing this: #4855 (about the error being invisible to users, fixed by making the toast appear - which is how I could see this error at all) and #5059 (a feature request to disable CSRF entirely for LAN/dynamic-IP use, not this bug). Neither covers the frontend not sending the token in the first place.
Happy to test a fix or provide more info - this is on a fresh Flatpak install with only sunshine.conf hand-edited for csrf_allowed_origins, nothing else customized.Pairing a Moonlight client via the web UI's PIN Pairing page (/pin) always fails with a "CSRF Protection Error" toast, even though `csrf_allowed_origins` in sunshine.conf already lists the exact origin being used to access the UI.
I confirmed this is not an origin allow-list problem. Using the browser's dev tools, I instrumented `window.fetch` on the /pin page and captured the exact request the "Send" button issues:
POST ./api/pin
Headers: { "Content-Type": "application/json" }
Body: {"pin":"1234","name":"test-device"}
There is no `X-CSRF-Token` header, and the page never calls GET /api/csrf-token beforehand to obtain one. The server correctly rejects this with `{"error":"Missing CSRF token","status":false,"status_code":400}`.
I confirmed the CSRF mechanism itself works fine server-side: manually fetching a token and attaching it as X-CSRF-Token on the same POST /api/pin request succeeds (200, `{"status":true}`). So the bug is specifically that the PIN Pairing page's own frontend code never fetches or attaches the CSRF token before submitting.
### Host Operating System
Linux
### Operating System Version
SteamOS 3.8.16 (build 20260716.1) on ROG Ally X
### Architecture
amd64/x86_64
### Package
Linux - flathub/flatpak
### GPU Type
AMD
### GPU Model
AMD Radeon 780M (integrated, Ryzen Z1 Extreme APU, Phoenix1)
### GPU Driver/Mesa Version
Mesa 25.3.0.213835.radeonsi_25.3.0 (radeonsi)
### Capture Method
None
### Apps
```json
```
### Log output
```shell
[2026-08-01 11:01:13.275]: Info: Sunshine version: 2026.516.143833 commit: 14ffa6fdaa53f7b51512be2b3d24f3939695403c
[2026-08-01 11:01:13.275]: Info: Package Publisher: LizardByte
[2026-08-01 11:01:13.275]: Info: Publisher Website: https://app.lizardbyte.dev
[2026-08-01 11:01:13.275]: Info: Get support: https://app.lizardbyte.dev/support
[2026-08-01 11:01:13.275]: Info: config: 'csrf_allowed_origins' = [redacted]
[2026-08-01 11:01:13.275]: Error: Couldn't load cuda: -1
[2026-08-01 11:01:13.275]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:13.276]: Info: [wayland] Found interface: zxdg_output_manager_v1(32) version 3
[2026-08-01 11:01:13.276]: Info: [wayland] Found interface: zwp_linux_dmabuf_v1(57) version 5
[2026-08-01 11:01:13.276]: Info: [wayland] Found interface: wl_output(64) version 4
[2026-08-01 11:01:13.276]: Warning: [wlgrab] Missing Wayland wire for wlr-export-dmabuf
[2026-08-01 11:01:13.276]: Error: Failed to gain CAP_SYS_ADMIN
[2026-08-01 11:01:13.276]: Info: /dev/dri/card0 -> amdgpu
[2026-08-01 11:01:13.278]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:13.278]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:13.278]: Info: [wayland] Found interface: zxdg_output_manager_v1(32) version 3
[2026-08-01 11:01:13.278]: Info: [wayland] Found interface: zwp_linux_dmabuf_v1(57) version 5
[2026-08-01 11:01:13.278]: Info: [wayland] Found interface: wl_output(64) version 4
[2026-08-01 11:01:13.278]: Info: [wayland] Resolution: 1920x1080
[2026-08-01 11:01:13.278]: Info: [wayland] Offset: 0x0
[2026-08-01 11:01:13.279]: Info: [wayland] Logical size: 1920x1080
[2026-08-01 11:01:13.279]: Info: [wayland] Name: eDP-1
[2026-08-01 11:01:13.279]: Info: [wayland] Found monitor: Thermotrex Corporation eDP-1-TL070FVXS01-0
[2026-08-01 11:01:13.279]: Info: -------- Start of KMS monitor list --------
[2026-08-01 11:01:13.279]: Warning: Mismatch on expected Resolution compared to actual resolution: 0x0 vs 1920x1080
[2026-08-01 11:01:13.279]: Info: Monitor 0 is eDP-1: Thermotrex Corporation eDP-1-TL070FVXS01-0
[2026-08-01 11:01:13.279]: Info: --------- End of KMS monitor list ---------
[2026-08-01 11:01:13.279]: Info: [portalgrab] Loaded portal restore token from disk
[2026-08-01 11:01:13.295]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:13.295]: Info: [wayland] Found interface: zxdg_output_manager_v1(32) version 3
[2026-08-01 11:01:13.295]: Info: [wayland] Found interface: zwp_linux_dmabuf_v1(57) version 5
[2026-08-01 11:01:13.295]: Info: [wayland] Found interface: wl_output(64) version 4
[2026-08-01 11:01:13.295]: Info: [wayland] Resolution: 1920x1080
[2026-08-01 11:01:13.295]: Info: [wayland] Offset: 0x0
[2026-08-01 11:01:13.295]: Info: [wayland] Logical size: 1920x1080
[2026-08-01 11:01:13.295]: Info: [wayland] Name: eDP-1
[2026-08-01 11:01:13.295]: Info: [wayland] Found monitor: Thermotrex Corporation eDP-1-TL070FVXS01-0
[2026-08-01 11:01:13.295]: Warning: [portalgrab] Falling back to position 0x0 for stream with resolution 1920x1080on pipewire node 108
[2026-08-01 11:01:13.297]: Info: [portalgrab] Found stream for display id/name: 'eDP-1' position: 0x0 resolution: 1920x1080
[2026-08-01 11:01:13.314]: Warning: Gamepad ds5 is disabled due to Permission denied
[2026-08-01 11:01:13.314]: Info: // Testing for available encoders, this may generate errors. You can safely ignore those errors. //
[2026-08-01 11:01:13.314]: Info: Trying encoder [nvenc]
[2026-08-01 11:01:13.314]: Info: Screencasting with XDG portal
[2026-08-01 11:01:13.314]: Info: [pipewire] Requested frame rate [60/1, approx. 60 fps]
[2026-08-01 11:01:13.314]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:13.330]: Info: [portalgrab] Loaded portal restore token from disk
[2026-08-01 11:01:13.343]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:13.343]: Info: [wayland] Found interface: zxdg_output_manager_v1(32) version 3
[2026-08-01 11:01:13.343]: Info: [wayland] Found interface: zwp_linux_dmabuf_v1(57) version 5
[2026-08-01 11:01:13.343]: Info: [wayland] Found interface: wl_output(64) version 4
[2026-08-01 11:01:13.343]: Info: [wayland] Resolution: 1920x1080
[2026-08-01 11:01:13.343]: Info: [wayland] Offset: 0x0
[2026-08-01 11:01:13.343]: Info: [wayland] Logical size: 1920x1080
[2026-08-01 11:01:13.343]: Info: [wayland] Name: eDP-1
[2026-08-01 11:01:13.343]: Info: [wayland] Found monitor: Thermotrex Corporation eDP-1-TL070FVXS01-0
[2026-08-01 11:01:13.343]: Warning: [portalgrab] Falling back to position 0x0 for stream with resolution 1920x1080on pipewire node 108
[2026-08-01 11:01:13.345]: Info: [[2026-08-01 11:08:57.882]: Info: Web UI: [192.168.0.80] -- not authorized
[2026-08-01 11:10:08.420]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://192.168.0.40:47990
[2026-08-01 11:10:08.420]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:10:18.764]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://192.168.0.40:47990
[2026-08-01 11:10:18.764]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:11:23.828]: Info: Web UI: [192.168.0.80] -- not authorized
[2026-08-01 11:11:50.561]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://sunshine.home.arpa:47990
[2026-08-01 11:11:50.561]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:13:32.696]: Info: Web UI: [192.168.0.80] -- not authorized
[2026-08-01 11:13:32.722]: Info: Web UI: [192.168.0.80] -- not authorized
[2026-08-01 11:13:32.745]: Info: Web UI: [192.168.0.80] -- not authorized
[2026-08-01 11:14:23.846]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://sunshine.home.arpa:47990
[2026-08-01 11:14:23.846]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:14:43.730]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://sunshine.home.arpa:47990
[2026-08-01 11:14:43.730]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:14:48.982]: Info: Web UI: [192.168.0.80] -- not authorized
[2026-08-01 11:14:58.773]: Info: Web UI: [192.168.0.80] -- not authorized
[2026-08-01 11:15:54.450]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://sunshine.home.arpa:47990
[2026-08-01 11:15:54.450]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:16:08.213]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://sunshine.home.arpa:47990
[2026-08-01 11:16:08.213]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:18:57.052]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://192.168.0.40:47990
[2026-08-01 11:18:57.053]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:19:11.575]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://192.168.0.40:47990
[2026-08-01 11:19:11.575]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:19:26.695]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://192.168.0.40:47990
[2026-08-01 11:19:26.695]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
[2026-08-01 11:21:35.346]: Error: Web UI: [192.168.0.80] -- CSRF protection blocked request from origin: https://192.168.0.40:47990
[2026-08-01 11:21:35.346]: Error: Web UI: To allow this origin, add it to the 'csrf_allowed_origins' option in your Sunshine configuration
et: 0x0
[2026-08-01 11:01:14.898]: Info: [wayland] Logical size: 1920x1080
[2026-08-01 11:01:14.898]: Info: [wayland] Name: eDP-1
[2026-08-01 11:01:14.898]: Info: [wayland] Found monitor: Thermotrex Corporation eDP-1-TL070FVXS01-0
[2026-08-01 11:01:14.900]: Info: [pipewire] Connected to pipewire version 1.6.4
[2026-08-01 11:01:14.917]: Info: [pipewire] Pipewire Error, id:2 seq:13 message: no more input formats
[2026-08-01 11:01:16.414]: Info: Creating encoder [h264_vulkan]
[2026-08-01 11:01:16.414]: Info: Color coding: SDR (Rec. 601)
[2026-08-01 11:01:16.414]: Info: Color depth: 8-bit
[2026-08-01 11:01:16.414]: Info: Color range: JPEG
[2026-08-01 11:01:16.462]: Info: Streaming bitrate is 1000000
[2026-08-01 11:01:16.462]: Info: Vulkan encode using GPU: AMD Ryzen Z1 Extreme (RADV PHOENIX)
[2026-08-01 11:01:16.474]: Info: Creating encoder [hevc_vulkan]
[2026-08-01 11:01:16.474]: Info: Color coding: SDR (Rec. 601)
[2026-08-01 11:01:16.474]: Info: Color depth: 8-bit
[2026-08-01 11:01:16.474]: Info: Color range: JPEG
[2026-08-01 11:01:16.514]: Info: Streaming bitrate is 1000000
[2026-08-01 11:01:16.515]: Info: Vulkan encode using GPU: AMD Ryzen Z1 Extreme (RADV PHOENIX)
[2026-08-01 11:01:16.526]: Info: Creating encoder [av1_vulkan]
[2026-08-01 11:01:16.526]: Info: Color coding: SDR (Rec. 601)
[2026-08-01 11:01:16.526]: Info: Color depth: 8-bit
[2026-08-01 11:01:16.526]: Info: Color range: JPEG
[2026-08-01 11:01:16.565]: Info: Streaming bitrate is 1000000
[2026-08-01 11:01:16.566]: Info: Vulkan encode using GPU: AMD Ryzen Z1 Extreme (RADV PHOENIX)
[2026-08-01 11:01:16.575]: Info: Screencasting with XDG portal
[2026-08-01 11:01:16.575]: Info: [pipewire] Requested frame rate [60/1, approx. 60 fps]
[2026-08-01 11:01:16.575]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:16.583]: Info: [portalgrab] Loaded portal restore token from disk
[2026-08-01 11:01:16.598]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:16.599]: Info: [wayland] Found interface: zxdg_output_manager_v1(32) version 3
[2026-08-01 11:01:16.599]: Info: [wayland] Found interface: zwp_linux_dmabuf_v1(57) version 5
[2026-08-01 11:01:16.599]: Info: [wayland] Found interface: wl_output(64) version 4
[2026-08-01 11:01:16.599]: Info: [wayland] Resolution: 1920x1080
[2026-08-01 11:01:16.599]: Info: [wayland] Offset: 0x0
[2026-08-01 11:01:16.599]: Info: [wayland] Logical size: 1920x1080
[2026-08-01 11:01:16.599]: Info: [wayland] Name: eDP-1
[2026-08-01 11:01:16.599]: Info: [wayland] Found monitor: Thermotrex Corporation eDP-1-TL070FVXS01-0
[2026-08-01 11:01:16.599]: Warning: [portalgrab] Falling back to position 0x0 for stream with resolution 1920x1080on pipewire node 108
[2026-08-01 11:01:16.601]: Info: [portalgrab] Using first available stream as no matching stream was found for: ''
[2026-08-01 11:01:16.601]: Info: [pipewire] Streaming display '' offset: 0x0 resolution: 1920x1080
[2026-08-01 11:01:16.601]: Info: [wayland] Found display [wayland-0]
[2026-08-01 11:01:16.601]: Info: [wayland] Found interface: zxdg_output_manager_v1(32) version 3
[2026-08-01 11:01:16.601]: Info: [wayland] Found interface: zwp_linux_dmabuf_v1(57) version 5
[2026-08-01 11:01:16.601]: Info: [wayland] Found interface: wl_output(64) version 4
[2026-08-01 11:01:16.601]: Info: [wayland] Resolution: 1920x1080
[2026-08-01 11:01:16.601]: Info: [wayland] Offset: 0x0
[2026-08-01 11:01:16.601]: Info: [wayland] Logical size: 1920x1080
[2026-08-01 11:01:16.601]: Info: [wayland] Name: eDP-1
[2026-08-01 11:01:16.601]: Info: [wayland] Found monitor: Thermotrex Corporation eDP-1-TL070FVXS01-0
[2026-08-01 11:01:16.603]: Info: [pipewire] Connected to pipewire version 1.6.4
[2026-08-01 11:01:16.621]: Info: [pipewire] Pipewire Error, id:2 seq:13 message: no more input formats
[2026-08-01 11:01:18.115]: Info: Creating encoder [hevc_vulkan]
[2026-08-01 11:01:18.115]: Info: Color coding: SDR (Rec. 709)
[2026-08-01 11:01:18.115]: Info: Color depth: 10-bit
[2026-08-01 11:01:18.115]: Info: Color range: JPEG
[2026-08-01 11:01:18.164]: Info: Streaming bitrate is 1000000
[2026-08-01 11:01:18.166]: Info: Vulkan encode using GPU: AMD Ryzen Z1 Extreme (RADV PHOENIX)
[2026-08-01 11:01:18.181]: Info: Creating encoder [av1_vulkan]
[2026-08-01 11:01:18.181]: Info: Color coding: SDR (Rec. 709)
[2026-08-01 11:01:18.181]: Info: Color depth: 10-bit
[2026-08-01 11:01:18.181]: Info: Color range: JPEG
[2026-08-01 11:01:18.218]: Info: Streaming bitrate is 1000000
[2026-08-01 11:01:18.219]: Info: Vulkan encode using GPU: AMD Ryzen Z1 Extreme (RADV PHOENIX)
[2026-08-01 11:01:18.230]: Info:
[2026-08-01 11:01:18.230]: Info: // Ignore any errors mentioned above, they are not relevant. //
[2026-08-01 11:01:18.230]: Info:
[2026-08-01 11:01:18.230]: Info: Found H.264 encoder: h264_vulkan [vulkan]
[2026-08-01 11:01:18.230]: Info: Found HEVC encoder: hevc_vulkan [vulkan]
[2026-08-01 11:01:18.230]: Info: Found AV1 encoder: av1_vulkan [vulkan]
[2026-08-01 11:01:18.231]: Info: Starting system tray
[2026-08-01 11:01:18.231]: Fatal: Couldn't bind RTSP server to port [48010], Endereço já em uso
[2026-08-01 11:01:18.232]: Error: Failed to create client: Daemon not running
[2026-08-01 11:01:18.258]: Info: System tray created
[2026-08-01 11:01:18.258]: Info: Starting main loop
```
### Online logs
_No response_
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at the frontend implementation for the /pin page and inspect the Send flow with the browser network panel, focusing on GET /api/csrf-token and POST /api/pin. Done means the page obtains a CSRF token, includes it in the X-CSRF-Token header, and successfully pairs a client without the missing-token error.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100