-
Media Server v0.2.5
Stablereleased this
2026-05-16 20:16:45 +03:00 | 16 commits to master since this releasev0.2.5 (2026-05-16)
Security
- Loopback-by-default + auto-generated token: Server now binds
127.0.0.1by default; first-run bootstrap generates a randomapi_tokenand refuses to bind a non-loopback interface without auth unless explicitly opted in. (bcc6d40) - Browser path-traversal hardening:
BrowserService.validate_pathnow rejects absolute paths, drive letters, UNC paths, and NUL bytes./api/browser/{play,metadata,thumbnail}require afolder_idplus a folder-relative path — arbitrary filesystem reads via the browser API are no longer possible. (bcc6d40) - Strict input validation on links/scripts: Pydantic validators reject non-http(s) URLs and any icon outside the
mdi:<slug>namespace. Create/update/delete on scripts, callbacks, and links is gated by the corresponding*_managementflags. (bcc6d40) - Hardened response headers + CORS: Strict
Content-Security-Policy,X-Frame-Options: DENY,Referrer-Policy: no-referrer,X-Content-Type-Options: nosniff. CORS locked tolocalhost:<port>+127.0.0.1:<port>by default; configurable for trusted origins. (bcc6d40) - Atomic config writes with restrictive permissions:
config.yamlwrites go through a temp file +os.replaceand land with0o600on POSIX, so a crash mid-write can never leave a half-written token on disk readable to other users. (bcc6d40) - Subprocess process-group isolation: Spawned scripts/callbacks now get their own process group (
CREATE_NEW_PROCESS_GROUPon Windows,start_new_session=Trueon POSIX), so a timeout actually kills the whole tree instead of orphaning child processes. (bcc6d40) - Frontend XSS hardening: Monitor name + details are
escapeHtml'd, the power button moved to a delegateddata-actionhandler, and remote MDI SVGs are parsed and sanitized (strip<script>,<foreignObject>,on*handlers,javascript:hrefs) before they touchinnerHTML. All dynamic URL segments now go throughencodeURIComponent. (bcc6d40) - CSP-compliant event wiring: Strict
script-src 'self'was blocking every inlineonclick/onchange/oninput/onsubmitin the UI, leaving buttons and forms silently dead. All 53 inline handler attributes inindex.htmlwere renamed todata-on*and a newwireInlineHandlers()inapp.jsparses each expression onDOMContentLoadedand attaches a realaddEventListener— supports no-arg calls, string/number/bool/null literals, and theeventtoken. Nounsafe-inlineorunsafe-hashesneeded. (eaeebb6)
Bug Fixes
- WebSocket reconnect robustness: Close the previous socket before opening a new one, clear the ping interval per-socket, clear
reconnectTimeoutup-front, retry ononline/visibilitychange, and wrapJSON.parsein try/catch — eliminates the stale-socket leaks and "stuck offline after sleep" cases. (bcc6d40) - Artwork fetch race:
AbortController+ generation guard so a rapid track change can no longer paint the previous track's artwork over the current one. (bcc6d40) - Audio analyzer no longer spins infinitely without a loopback device: A sticky
_unavailableflag short-circuits start/stop; cleared byset_device()so the user can recover once a device appears. (bcc6d40) - Volume short-circuit cache invalidation: Cache is now busted when the server reports a remote volume change, so the UI no longer ignores volume updates that happened outside the app. (bcc6d40)
- Browser thumbnail race: Per-folder generation counter +
isConnectedchecks; in-flight fetches are aborted on navigation, so thumbnails from a folder you already left can't paint into the current view. (bcc6d40) - Track-skip uses cached title instead of a full WinRT status round-trip — skip feedback is now instant. (bcc6d40)
- Browser list column alignment:
.browser-listswitched to CSS grid + subgrid so header and rows share column tracks, eliminating the misaligned columns when content widths differed between rows. Matching responsive column overrides applied at the parent. Root-folder SVG sizing (hardcoded 24×24 inbrowser.js) now fills the 56px icon box instead of rendering at ~43%. Compact-grid icon fills its thumb wrapper so the emoji centers instead of being stranded top-left. PrematureisConnectedbail removed fromloadThumbnail— the img element is intentionally detached when called fromrenderBrowserGrid/List, and the post-await checks already handle navigation-away correctly. (982dda4)
Performance
- Blocking IO off the event loop: Linux MPRIS/
pactlcalls,/api/displayDDC/CI handlers, andbrowse_directoryare all wrapped inasyncio.to_thread— slow SMB shares or laggy monitors can no longer stall the entire async runtime. (bcc6d40) - Windows status poll loop reuse: The 0.5s status poll now caches one asyncio loop per worker thread via
threading.localinstead ofnew_event_loop/closeon every tick. (bcc6d40) - WebSocket broadcast: serialize once:
broadcast()serializes JSON a single time and usessend_textto fan out to all clients. (bcc6d40) - Thumbnail cache cleanup actually runs: The hourly cleanup task was defined but never scheduled — it is now wired into the lifespan handler so the cache no longer grows unbounded. (bcc6d40)
- Progress drag listeners attached only while dragging — no more global
mousemovehandler firing on every cursor twitch. (bcc6d40)
UI/UX
- Copper accent consistency: Green leftover focus rings (
rgba(29,185,84,…)) replaced with copper (rgba(var(--copper-rgb),…)) across the UI. Dialogs now have square corners and a copper top hairline so they read as part of the editorial chrome..browser-itemis transparent with a copper hover border (was a filled card). (bcc6d40) - Audio device select uses
var(--sans)instead of the generic system font so it matches surrounding controls. (bcc6d40) - Mobile padding tuned for ≤480px screens. (bcc6d40)
- Accessible breadcrumb home: Now a real
<button>witharia-label, andaria-currentis set on the root. (bcc6d40) - i18n gaps filled:
display.msg.power_*,execution.*,scripts.params.execute,callbacks.emptynow have proper en + ru strings. (bcc6d40)
Development / Internal
Quality
- All
asyncio.get_event_loop()in coroutines migrated toget_running_loop()(the former is deprecated in Python 3.12+). (bcc6d40) ThreadPoolExecutors now shut down cleanly during lifespan teardown. (bcc6d40)config_managerdedup: 12 near-identical CRUD methods collapsed onto generic_upsert/_deletehelpers — about 290 lines removed with no behavior change. (bcc6d40)- Service worker no longer pass-throughs every fetch. (bcc6d40)
- M3U playlist written via
NamedTemporaryFileso a fixed-path symlink can no longer clobber it. (bcc6d40) __version__prefers livepyproject.tomlin dev checkouts sopip install -e .users see the source-of-truth version, not the stale metadata baked in at install time. (bcc6d40)_broadcast_after_openhardening: initialize status, swallow per-poll errors, and track background tasks in a strong-ref set with done-callback cleanup so they aren't garbage-collected mid-flight. (bcc6d40)
All Commits
Hash Message Author 982dda4 fix(browser): align list columns via subgrid and fix icon sizing alexei.dolgolyov eaeebb6 fix(csp): replace inline on* handlers with data-on* + JS wiring alexei.dolgolyov bcc6d40 fix: comprehensive security, bug, performance, and UI/UX audit alexei.dolgolyov Downloads
Platform File Windows (installer) MediaServer-v0.2.5-setup.exeWindows (portable) MediaServer-v0.2.5-win-x64.zipLinux MediaServer-v0.2.5-linux-x64.tar.gzDownloads
- Loopback-by-default + auto-generated token: Server now binds