/
/
# What does this implement/fix?
The MSX Bridge runs its own HTTP server bound to `0.0.0.0` so TVs on the
LAN can reach it. Its two audio routes accepted any caller:
- `GET /msx/audio/{player_id}?uri=…` started playback of whatever the
caller named and handed back the audio
- `GET /stream/{player_id}` served the player's current audio
The cross-site guard added in #4734 only covers the playback-control
endpoints, so nothing gated these. Player IDs aren't secret either —
they come from a caller-supplied `?device_id=`, and any content page
registers the player on demand.
`?uri=` was also unvalidated beyond containing `://`, so a bare
`http://…` URL resolved to the builtin provider and made the server
fetch and play whatever was named.
## Changes
- Each player gets a token, derived from one provider secret and
embedded in the audio URLs the bridge generates; both audio routes
require it
- `?uri=` (and `/api/play`'s `track_uri`) are checked against the
provider the uri *resolves* to, so a raw URL is rejected whether it is
spelled bare or as `builtin://<media_type>/<url>`
- The audio routes no longer send `Access-Control-Allow-Origin`, so a
cross-origin `fetch()` cannot read the audio
- The two local proxy stream modes pace their output at the same ceiling
as the core streamserver
- Test coverage for each of the above
## Notes
TVs are unaffected. They follow the URLs the bridge hands them, the web
player passes the pushed path through unchanged, and audio still plays
cross-origin because a media element does not need CORS — the kiosk
visualizer reads the stream same-origin. Tokens live for the provider's
lifetime rather than the player's, so an idle TV being unregistered does
not strand a URL a long-running kiosk has cached.
This does not make the bridge an authenticated server. The JSON pages
that carry the tokens have to stay readable by a credential-less TV, and
their CORS wildcard is load-bearing because the MSX app is hosted
off-server and loads them cross-origin — so `Sec-Fetch-Site` gating
cannot be extended to the MSX-facing routes either. What this does
close: requests that were never handed out, using the endpoint to play
an arbitrary URL, and reading the audio from another origin. A URL that
*was* handed out stays valid until the provider reloads, so this is not
a defence against a captured URL.
**Related issue (if applicable):**
- n/a — found during a stream-server hardening pass
## Types of changes
- [ ] Bugfix (non-breaking change which fixes an issue) — `bugfix`
- [ ] New feature (non-breaking change which adds functionality) —
`new-feature`
- [ ] Enhancement to an existing feature — `enhancement`
- [ ] New music/player/metadata/plugin provider — `new-provider`
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected) — `breaking-change`
- [ ] Refactor (no behaviour change) — `refactor`
- [ ] Documentation only — `documentation`
- [x] Maintenance / chore — `maintenance`
- [ ] CI / workflow change — `ci`
- [ ] Dependencies bump — `dependencies`
## Checklist
- [x] The code change is tested and works locally.
- [x] `pre-commit run --all-files` passes.
- [x] `pytest` passes, and tests have been added/updated under `tests/`
where applicable.
- [ ] For changes to shared models, the companion PR in
`music-assistant/models` is linked.
- [ ] For changes affecting the UI, the companion PR in
`music-assistant/frontend` is linked.
- [x] I have read and complied with the project's [AI
Policy](https://github.com/music-assistant/.github/blob/main/AI_POLICY.md)
for any AI-assisted contributions.
- [ ] I have [raised a PR against the documentation
repository](https://github.com/music-assistant/music-assistant.io/blob/main/CONTRIBUTING.md)
targeting the main or beta branch as appropriate.