From 10047158b4ff1fbf0df7174e8b12404e0cc10bde Mon Sep 17 00:00:00 2001 From: enoch85 Date: Tue, 4 Aug 2026 22:58:12 +0000 Subject: [PATCH 1/2] docs: document CORS_ALLOWED_ORIGINS and fix broken Security page links Add the CORS_ALLOWED_ORIGINS environment variable to the Installation env table (Maintainerr/Maintainerr#3407). The bundled UI is same-origin with the API and does not need it; it is only for a separate front end. The Security & Authentication page lives in the current docs (/next/security/), but API.md and Configuration.md linked to it with an absolute /security, which resolves to the site root where the page does not exist yet, so the build flagged both as broken. Switch them to version-relative links (./Security.md) so they resolve within whichever version is viewed. docusaurus build now reports no broken links. --- docs/API.md | 2 +- docs/Configuration.md | 2 +- docs/Installation.mdx | 17 +++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/API.md b/docs/API.md index 3ddbf16a9..21c88aa80 100644 --- a/docs/API.md +++ b/docs/API.md @@ -9,7 +9,7 @@ hide: --- :::danger -:fire: :fire: The API, and all of Maintainerr for that matter, does not have an authentication method. There are certain API calls, that if you make your instance public facing, will expose your entire settings configuration. This could include all of your service's API keys. Proceed with extreme caution if you choose to expose Maintainerr to the public. See the [Security & Authentication](/security) page for guidance on protecting your instance. :fire: :fire: +:fire: :fire: The API, and all of Maintainerr for that matter, does not have an authentication method. There are certain API calls, that if you make your instance public facing, will expose your entire settings configuration. This could include all of your service's API keys. Proceed with extreme caution if you choose to expose Maintainerr to the public. See the [Security & Authentication](./Security.md) page for guidance on protecting your instance. :fire: :fire: ::: diff --git a/docs/Configuration.md b/docs/Configuration.md index 153e18441..ef0b51809 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -18,7 +18,7 @@ All Base URL settings are to be entered without the leading slash. ::: :::info Exposing Maintainerr to the internet -Maintainerr has no built-in login, so anyone who can reach it can read the credentials you enter below. Running it locally or reaching it over a VPN is fine and needs nothing extra. Only if you plan to put it directly on the internet, add an authenticating reverse proxy in front first - see [Security & Authentication](/security). +Maintainerr has no built-in login, so anyone who can reach it can read the credentials you enter below. Running it locally or reaching it over a VPN is fine and needs nothing extra. Only if you plan to put it directly on the internet, add an authenticating reverse proxy in front first - see [Security & Authentication](./Security.md). ::: ## General diff --git a/docs/Installation.mdx b/docs/Installation.mdx index 2085c14bc..26088453f 100644 --- a/docs/Installation.mdx +++ b/docs/Installation.mdx @@ -293,14 +293,15 @@ services: A list of all available environment variables are below. No other env variables are officially supported by Maintainerr. These are added either into the compose file or your docker run command. -| Variable | Default Value | Description | -| ------------ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| TZ | _host timezone_ | Controls date formatting in logs. | -| UI_HOSTNAME | 0.0.0.0 | The listen host of the web server. Can be set to :: for IPv6. | -| UI_PORT | 6246 | The listen port of the web server. | -| BASE_PATH | | If reverse proxying with a subfolder you'll want to set this. Must be in the format of `/subfolder`. | -| LOG_LEVEL | info | Overrides the persisted log level for the current container process only. Accepted values are `debug`, `verbose`, `info`, `warn`, `error`, and `fatal`. | -| GITHUB_TOKEN | | GitHub Personal Access Token for higher API rate limits | +| Variable | Default Value | Description | +| -------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| TZ | _host timezone_ | Controls date formatting in logs. | +| UI_HOSTNAME | 0.0.0.0 | The listen host of the web server. Can be set to :: for IPv6. | +| UI_PORT | 6246 | The listen port of the web server. | +| BASE_PATH | | If reverse proxying with a subfolder you'll want to set this. Must be in the format of `/subfolder`. | +| LOG_LEVEL | info | Overrides the persisted log level for the current container process only. Accepted values are `debug`, `verbose`, `info`, `warn`, `error`, and `fatal`. | +| GITHUB_TOKEN | | GitHub Personal Access Token for higher API rate limits | +| CORS_ALLOWED_ORIGINS | | Comma-separated list of origins allowed to call the API cross-origin, e.g. `https://maintainerr.example.com`. Same-origin access (the bundled UI, including via localhost) always works and never needs this; only set it if a separate front end on a different origin calls the API. | :::tip If `BASE_PATH` is set, remember to prefix health-check probe paths accordingly (for example `/maintainerr/api/health/ready`). From be74cd5c1e366eb35e6f4461ac3a2b9c9d7ebea3 Mon Sep 17 00:00:00 2001 From: enoch85 Date: Tue, 4 Aug 2026 23:20:00 +0000 Subject: [PATCH 2/2] docs: normalize Configuration callouts and reflect app hardening Configuration page: use one consistent callout style - a single info box for the security note and uniform notes everywhere else, dropping the green tips that made the page a patchwork of colors. Move the Base URL note from the top of the page to the Radarr/Sonarr section where that field is entered. Security page: reflect the appliance hardening in Maintainerr/Maintainerr#3407 - cross-origin reads are blocked by default (CORS_ALLOWED_ORIGINS to opt in a separate front end), submitted settings are schema-validated, and container file permissions are least-privilege. --- docs/Configuration.md | 22 ++++++++-------------- docs/Security.md | 5 +++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index ef0b51809..db80ca431 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -9,14 +9,6 @@ All configuration is done inside the application. No extra config files are requ When you first access the web UI, you should be redirected to the settings page. If that does not happen, try refreshing the page. -:::note -All Base URL settings are to be entered without the leading slash. - -- Right: `tautulli` -- Wrong: `/tautulli` - -::: - :::info Exposing Maintainerr to the internet Maintainerr has no built-in login, so anyone who can reach it can read the credentials you enter below. Running it locally or reaching it over a VPN is fine and needs nothing extra. Only if you plan to put it directly on the internet, add an authenticating reverse proxy in front first - see [Security & Authentication](./Security.md). ::: @@ -44,7 +36,7 @@ Maintainerr's normal Plex setup uses Plex authentication and server discovery in After you authenticate with a Plex **admin** account, Maintainerr validates the token, loads the servers available to that account, and lets you choose from discovered connection candidates. This is the recommended setup because Maintainerr can use the discovered server details directly and keep automatic reconnection behavior enabled. -:::tip +:::note Proper DNS is preferred. Plex discovery and failover can depend on resolvable Plex endpoints, and Docker users in particular may run into intermittent connection or discovery problems when container DNS is unstable. If possible, make sure your environment has working DNS resolution for Plex-related hostnames and service names. ::: @@ -54,9 +46,7 @@ Proper DNS is preferred. Plex discovery and failover can depend on resolvable Pl | Server | Shows the currently selected discovered server, or lets you choose one from the discovered server list. | | Refresh icon | Re-runs Plex server discovery for the authenticated account. Use this if the server list is stale or discovery failed the first time. | -:::tip -`Test Connection` is disabled until you are authenticated and have either selected a discovered server or enabled manual override with saved settings. -::: +`Test Connection` stays disabled until you are authenticated and have either selected a discovered server or enabled manual override with saved settings.
Server list is empty (VPS, cloud host, or custom networking) @@ -71,7 +61,7 @@ Fix it on the Plex side by publishing a reachable address: Back in Maintainerr, press the **Refresh icon** next to the server selector; the server appears in the list. -:::tip +:::note If Maintainerr and Plex share a Docker network, you can instead skip discovery and point Maintainerr at Plex's **internal** address (e.g. `http://plex:32400`) with manual connection override below. ::: @@ -139,6 +129,10 @@ Seerr configuration is required if you want to use Seerr-related rule parameters Radarr's configuration is required to use its parameters in rules and to remove or unmonitor movies. +:::note +Enter the `Base URL` for Radarr and Sonarr without a leading slash - `radarr`, not `/radarr`. +::: + | Setting | Description | | -------------- | -------------------------------------------------------------- | | Server Name | A friendly name to help identify the server | @@ -220,7 +214,7 @@ How it works: - Removal is best-effort: a failure to reach the download client never blocks the Radarr/Sonarr deletion itself, so treat it as cleanup assistance rather than guaranteed download-client reconciliation. For more advanced usage; look elsewhere. - `Test Connection` verifies the URL and credentials against the qBittorrent Web UI before saving. -:::tip Troubleshooting: "403 Forbidden" after a successful login +:::note Troubleshooting: "403 Forbidden" after a successful login A `403 Forbidden` on the connection test (or in the logs) means qBittorrent accepted the credentials but its Web UI security then blocked the request — it is **not** a wrong username/password. The usual cause is that Maintainerr and qBittorrent run on different IPs (e.g. separate Docker containers). In qBittorrent go to **Options → Web UI → Security** and add Maintainerr's IP or subnet to **"Bypass authentication for clients in whitelisted IP subnets"**. A reverse proxy or host-header validation can also cause it. ::: diff --git a/docs/Security.md b/docs/Security.md index 36767a743..971a7e564 100644 --- a/docs/Security.md +++ b/docs/Security.md @@ -26,9 +26,10 @@ Maintainerr is built to keep your data on your own hardware and to be careful wi - **Everything stays local.** All configuration and state lives in a single SQLite database in your data directory (`/opt/data`). There is no cloud component, no telemetry, and no analytics - Maintainerr never phones home. The only outbound traffic goes to the services you configure (your media server, the \*arrs, Seerr, and so on) and to the metadata providers (TMDB/TVDB) used to match your library. - **Outbound connections use verified TLS.** Calls to your services and to metadata providers use HTTPS with normal certificate verification, which the app never disables on its own. Notification email can use TLS and can optionally be PGP-encrypted. - **Secrets are kept out of the logs.** Every log line passes through a sanitizer that masks API keys, tokens, `Authorization` headers, and credential-bearing URLs, so secrets do not leak into log files or error dumps. -- **The rules engine cannot run code or shell out.** Rules are evaluated by a typed comparator, never `eval`-ed. Database access is fully parameterized, so there is no SQL-injection surface, and the server runs no shell commands. +- **The rules engine cannot run code or shell out.** Rules are evaluated by a typed comparator, never `eval`-ed. Database access is fully parameterized, so there is no SQL-injection surface, and the server runs no shell commands. Settings you submit are schema-validated before they are saved. +- **Cross-origin access is locked down.** In production the API sends no CORS headers by default, so another website cannot read it from your browser. If you serve a separate front end from a different origin, allow it explicitly with `CORS_ALLOWED_ORIGINS`. - **Destructive actions are deliberately conservative.** Deletes are tied to explicit collection and rule actions, and the folder-cleanup path is fail-closed: it refuses unexpected paths, rejects symlinks and `..` traversal, canonicalizes with `realpath`, and only removes a folder once it has proven the folder is empty and safely inside the intended directory. -- **Non-root, pinned container image.** The official image runs as a non-root user, is built in multiple stages from a digest-pinned base, and pins security-sensitive dependencies. +- **The container is hardened.** The official image runs as a non-root user with least-privilege file permissions, is built in multiple stages from a digest-pinned base, and pins security-sensitive dependencies. **One important caveat:** the credentials you enter (Plex token, \*arr and Seerr keys, qBittorrent and SMTP passwords, notifier tokens) are stored **unencrypted** in that SQLite database - Maintainerr does not encrypt data at rest. So the database file, and any backup of it, is as sensitive as the credentials it holds: keep the data directory private, restrict its permissions, and encrypt your backups. And because Maintainerr has no login of its own, none of this replaces putting it behind an authenticating reverse proxy when you expose it (the rest of this page).