• v0.2.5 527f3d0aa4

    Media Server v0.2.5
    Lint & Test / test (push) Has been skipped
    Release / create-release (push) Successful in 4s
    Release / build-linux (push) Successful in 44s
    Release / build-windows (push) Successful in 1m9s
    Stable

    alexei.dolgolyov released this 2026-05-16 20:16:45 +03:00 | 16 commits to master since this release

    v0.2.5 (2026-05-16)

    Security

    • Loopback-by-default + auto-generated token: Server now binds 127.0.0.1 by default; first-run bootstrap generates a random api_token and refuses to bind a non-loopback interface without auth unless explicitly opted in. (bcc6d40)
    • Browser path-traversal hardening: BrowserService.validate_path now rejects absolute paths, drive letters, UNC paths, and NUL bytes. /api/browser/{play,metadata,thumbnail} require a folder_id plus 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 *_management flags. (bcc6d40)
    • Hardened response headers + CORS: Strict Content-Security-Policy, X-Frame-Options: DENY, Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff. CORS locked to localhost:<port> + 127.0.0.1:<port> by default; configurable for trusted origins. (bcc6d40)
    • Atomic config writes with restrictive permissions: config.yaml writes go through a temp file + os.replace and land with 0o600 on 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_GROUP on Windows, start_new_session=True on 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 delegated data-action handler, and remote MDI SVGs are parsed and sanitized (strip <script>, <foreignObject>, on* handlers, javascript: hrefs) before they touch innerHTML. All dynamic URL segments now go through encodeURIComponent. (bcc6d40)
    • CSP-compliant event wiring: Strict script-src 'self' was blocking every inline onclick/onchange/oninput/onsubmit in the UI, leaving buttons and forms silently dead. All 53 inline handler attributes in index.html were renamed to data-on* and a new wireInlineHandlers() in app.js parses each expression on DOMContentLoaded and attaches a real addEventListener — supports no-arg calls, string/number/bool/null literals, and the event token. No unsafe-inline or unsafe-hashes needed. (eaeebb6)

    Bug Fixes

    • WebSocket reconnect robustness: Close the previous socket before opening a new one, clear the ping interval per-socket, clear reconnectTimeout up-front, retry on online/visibilitychange, and wrap JSON.parse in 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 _unavailable flag short-circuits start/stop; cleared by set_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 + isConnected checks; 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-list switched 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 in browser.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. Premature isConnected bail removed from loadThumbnail — the img element is intentionally detached when called from renderBrowserGrid/List, and the post-await checks already handle navigation-away correctly. (982dda4)

    Performance

    • Blocking IO off the event loop: Linux MPRIS/pactl calls, /api/display DDC/CI handlers, and browse_directory are all wrapped in asyncio.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.local instead of new_event_loop/close on every tick. (bcc6d40)
    • WebSocket broadcast: serialize once: broadcast() serializes JSON a single time and uses send_text to 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 mousemove handler 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-item is 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> with aria-label, and aria-current is set on the root. (bcc6d40)
    • i18n gaps filled: display.msg.power_*, execution.*, scripts.params.execute, callbacks.empty now have proper en + ru strings. (bcc6d40)

    Development / Internal

    Quality

    • All asyncio.get_event_loop() in coroutines migrated to get_running_loop() (the former is deprecated in Python 3.12+). (bcc6d40)
    • ThreadPoolExecutors now shut down cleanly during lifespan teardown. (bcc6d40)
    • config_manager dedup: 12 near-identical CRUD methods collapsed onto generic _upsert/_delete helpers — about 290 lines removed with no behavior change. (bcc6d40)
    • Service worker no longer pass-throughs every fetch. (bcc6d40)
    • M3U playlist written via NamedTemporaryFile so a fixed-path symlink can no longer clobber it. (bcc6d40)
    • __version__ prefers live pyproject.toml in dev checkouts so pip install -e . users see the source-of-truth version, not the stale metadata baked in at install time. (bcc6d40)
    • _broadcast_after_open hardening: 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.exe
    Windows (portable) MediaServer-v0.2.5-win-x64.zip
    Linux MediaServer-v0.2.5-linux-x64.tar.gz
    Downloads