chore: release v0.3.1
Lint & Test / test (push) Has been skipped
Release / create-release (push) Successful in 4s
Release / build-linux (push) Successful in 28s
Release / build-windows (push) Successful in 52s

This commit is contained in:
2026-05-25 23:45:08 +03:00
parent 9b9a2b5c9f
commit 82710c6457
2 changed files with 5 additions and 55 deletions
+4 -54
View File
@@ -1,60 +1,10 @@
## v0.3.0 (2026-05-22) ## v0.3.1 (2026-05-25)
Production-readiness hardening release: security, performance, accessibility, and observability. Substantial new functionality (HTTPS, audit log, OS mediaSession integration, rate limiter, X-Request-ID, ETag-cached artwork) alongside the security defaults flip described below. Hotfix for the v0.3.0 production-readiness release: the new WebSocket Origin allow-list rejected same-origin connections from any LAN IP, breaking the Web UI on `host: 0.0.0.0` deployments unless `cors_origins` was explicitly configured.
### Behavioral Changes (worth reading before upgrade)
- **Admin scope is now required for management endpoints**, and `scripts_management`, `callbacks_management`, `links_management`, `media_folders_management` default to `False`. Legacy bare-string `api_tokens` entries are auto-promoted to `admin` scope, so existing single-token deployments keep working. If you ran with a non-admin token and used CRUD on `/api/scripts`, `/api/callbacks`, `/api/links`, or `/api/media-folders`, you'll need an admin-scope token (see new `TokenSpec` format in `config.example.yaml`). ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **`cors_origins: ["*"]` is now refused at startup** — set explicit origins instead. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Thumbnail cache directory moved** from project-root `.cache` to `%LOCALAPPDATA%/media-server/cache` on Windows and `$XDG_CACHE_HOME/media-server/thumbnails` on POSIX. The old `.cache` directory can be deleted. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **WebSocket auth prefers the `Sec-WebSocket-Protocol: media-server.token.<T>` subprotocol** so the token no longer ends up in URL/history/Referer. The `?token=` query fallback is retained for HA integration back-compat. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
### Features
- **HTTPS support** via `ssl_certfile` + `ssl_keyfile` (+ optional `ssl_keyfile_password`); startup refuses to launch with only one of the pair set. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Reverse-proxy support**: `proxy_headers` + `forwarded_allow_ips` plumbed through `Settings` to `uvicorn.Config`. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **OS media session integration**: headset / lockscreen / Bluetooth media-key buttons now drive play/pause/next/prev/seek and the browser-level mediaSession shows track metadata + artwork. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Token scope hierarchy** (`read | control | admin`) with structured `TokenSpec` entries; legacy bare-string tokens promote to `admin`. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **In-process token-bucket rate limiter**: 5/min for failed auths, 10/min for `/api/scripts/execute` and `/api/callbacks/execute`. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **WebSocket Origin allow-list check** (CSWSH defence). ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Script parameter validation**: per-parameter `pattern` regex in `ScriptParameterConfig` plus `shell=False` (`shlex.split`) execution path to harden against parameter injection on `cmd.exe`. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **CSP tightened** with `form-action`, `worker-src`, `manifest-src` directives. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **`noopener noreferrer` + `no-referrer` referrerpolicy** applied to every outbound link in the WebUI. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Windows config.yaml ACL hardening** via `icacls` (current user + SYSTEM + Administrators only); `0600` continues to be enforced on POSIX. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **PWA installability**: `manifest.json` gets `id`, `scope`, and `theme_color` / `background_color` matching the Studio Reference base (`#0E0D0B`). ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Accessibility**: ARIA labels on mini-player icon buttons; inner SVGs marked `aria-hidden`. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
### Performance
- **Album-art read in `windows_media` gated by track key** — was decoding the WinRT thumbnail twice per second regardless of track changes. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **`/api/media/artwork` returns content-derived `ETag` + `Cache-Control`** so the browser sends `If-None-Match` and gets `304` on track repeats. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Foreground-service `ctypes` argtypes hoisted to one-time module init** — was re-declaring ~14 prototypes per probe. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **`display_service._static_cache` keyed by `(edid_hash, ...)` tuple** with eviction of disappeared monitors — fixes stale capabilities on hot-plug swaps where the new topology has the same monitor count. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Visualizer `requestAnimationFrame` loop paused on `document.hidden`, resumed on `visible`**. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
### Bug Fixes ### Bug Fixes
- **Lifespan rewritten as `try` / `yield` / `finally`** so a partial-startup failure cannot orphan background tasks or executors. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4)) - **WebSocket Origin check now accepts same-origin connections.** When `cors_origins` is unset, the default allow-list was hard-coded to `http://localhost:<port>` + `http://127.0.0.1:<port>`, so a browser opening the UI via the LAN IP (e.g. `http://192.168.2.100:8765`) had its WebSocket closed with code 4003 ("Origin not allowed") and never recovered. The endpoint now also accepts any `Origin` whose authority matches the request's `Host` header (with either `http://` or `https://` scheme) — same-origin connections are by definition not CSWSH, so the cross-origin defence introduced in v0.3.0 is preserved. ([9b9a2b5](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/9b9a2b5))
- **`_run_callback` in `routes/media.py` keeps a strong task reference (GC-safe)** and uses the dedicated callback executor instead of the default pool. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **`macos_media.set_volume()` no longer always returns `True`** regardless of the underlying AppleScript result. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **`TrayManager._restart_requested` initialised in `__init__`** and set before signalling exit so the main thread observes it correctly. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Missing `static_dir` now logs a `WARNING`** instead of silently disabling the UI. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **WebSocket volume handler clamps input** and never drops the socket on bad messages. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Gitea release tag validated against a strict SemVer regex** before being used in a release URL. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
### Observability
- **`X-Request-ID` middleware** — accepts an upstream id if it matches a safe regex, otherwise generates a `UUID4`. `request_id_var` added to `ContextVars` and included in every log line alongside the token label. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **Append-only JSONL audit log** for every script + callback execution (including `on_play` / `on_pause` / etc. event callbacks). Background-thread writer; queue capped; flushed in lifespan teardown. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
- **`token=...` stripped from uvicorn access logs**. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
---
### Development / Internal
#### Tests
- **35 new tests** across auth scopes, the rate limiter, browser path traversal (`../`, `NUL`, UNC, absolute paths), script-parameter validation including the regex, the Gitea tag whitelist, and atomic config writes + POSIX permissions. Suite: 47 passed / 4 skipped. ([d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4))
--- ---
@@ -63,6 +13,6 @@ Production-readiness hardening release: security, performance, accessibility, an
| Hash | Message | Author | | Hash | Message | Author |
|------|---------|--------| |------|---------|--------|
| [d131ba4](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/d131ba4) | fix: production-readiness hardening — security, perf, a11y, observability | alexei.dolgolyov | | [9b9a2b5](https://git.dolgolyov-family.by/alexei.dolgolyov/media-player-server/commit/9b9a2b5) | fix(ws): accept same-origin WebSocket connections in default Origin allow-list | alexei.dolgolyov |
</details> </details>
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "media-server" name = "media-server"
version = "0.3.0" version = "0.3.1"
description = "REST API server for controlling system-wide media playback" description = "REST API server for controlling system-wide media playback"
readme = "README.md" readme = "README.md"
license = { text = "MIT" } license = { text = "MIT" }