Repository navigation
Conversation
…tion header Jellyfin 12 removed the legacy X-Emby-Token / api_key authentication, so the plugin request returned 401 and every page failed to load. Send the Authorization: MediaBrowser header (also accepted by older servers) alongside the legacy header.
Without JELLYFIN_API_KEY the frontend sends the signed-in user's own Jellyfin token to a small backend (server/). The backend verifies the token with Jellyfin, copies only that user's PlaybackActivity rows into an in-memory SQLite database (sql.js) and runs the page's unchanged SQL against it, read-only, with a time limit and a row cap. With JELLYFIN_API_KEY set, behavior is unchanged.
- per-user request rate limit and per-user concurrent query cap - failed sign-in limit per client address (verified users unaffected) - trusted-proxy aware client address (TRUST_PROXY_HOPS) - cap on users loading data from Jellyfin at once - result size cap in bytes, and a SQLite heap limit so a query that builds huge strings fails instead of exhausting the process - request timeouts, JSON event log (never logs tokens or SQL), stricter response headers
- server/README.md: design, security model, every setting, proxy setup - docker-entrypoint.sh writes the /api proxy config at start (validated URL) - Apache: no version banner, TRACE off, basic security headers - CI job for the server (tests and image build) - root npm scripts server:install, server:test, server:dev - hardened example compose for server mode
ProxyTimeout is not allowed inside <Location>, so httpd failed to start in server mode.
The plugin sends no column names when a query returns no rows, so the per-user database was created with an empty column list and every request for a user without history failed. Fall back to the table layout.
The app sends a burst of queries when a page loads. Anything beyond the per-user running limit was refused with a 429 and the client retries only once, so some pages could end up without data. Run up to MAX_INFLIGHT_PER_USER at once, hold up to MAX_QUEUED_PER_USER more, and refuse only past that.
Files copied from public/ keep the mode they had in the checkout. Built with a restrictive umask, vite.svg ended up unreadable and returned 403.
npm audit fix, no version range changes. Clears axios, follow-redirects, form-data, lodash and react-router advisories in the production tree.
JELLYFIN_API_KEY_FILE names a file holding the key. The key then stays out of the process environment, docker inspect output and crash reports. Setting both variables is an error.
- Load only the Apache modules the site uses; refuse everything but GET and HEAD on the static site, and GET and POST on /api/ - Content-Security-Policy limited to this site and the Jellyfin server, no inline scripts (the app settings are served as /env.js instead of injected into the page) - Cross-origin isolation and permissions headers; dotfiles and source maps denied - Request size, header and read-time limits; fewer worker threads - Hashed assets cached for good, index.html always revalidated; a missing asset is a real 404 instead of index.html - Runtime files go to one directory, so the rest of the image can be read-only - The entrypoint validates every value it writes and warns when the admin key would be sent to browsers
PidFile has its own default under logs/, which DefaultRuntimeDir does not change, so Apache could not start with a read-only filesystem.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on ◇
feat: optional server mode that keeps the admin API key off the browser. Based on that branch.Summary
Server mode keeps the admin key out of the browser, but the backend still took it from an environment variable and the Apache image ran with its defaults. This reads the key from a file and locks down the web server for public hosting. It also updates dependencies with known vulnerabilities and fixes two unreadable-text problems on the device stats page found while testing.
Changes
JELLYFIN_API_KEY_FILE, so it no longer appears in the container environment or indocker inspect.GETandHEADare allowed (Require method, sinceLimitExceptinside an outerRequire all granteddoes not deny);FallbackResource /index.htmlis disabled under/assets, so a missing asset returns 404; dotfiles and.mapfiles are denied; security headers including a CSP. The CSP keepsstyle-src 'unsafe-inline'because stitches and radix inject inline styles.env.js,csp.confandproxy.confare written at start-up into a generated directory (a tmpfs in Docker). The ApachePidFileis set to that directory too, since it is not covered byDefaultRuntimeDir.--gray-11on a violet background; it is now white. The pie chart legend sat outside the SVG and was clipped, and its text and the chart titles had no fill. The legend now sits below each pie and the text is white.Testing
/api/healthreturns ok;/,/moviesand/env.jsreturn 200; CSP header presentenv.jsPOST /api/playback-reportwithout a token returns 401;POST /returns 403; a missing asset returns 404;/.envreturns 403