Skip to content

Security Hardening

The default Compose templates work out of the box without any special security configuration. For users who want additional container-level hardening, the template below adds capability restrictions, privilege escalation prevention, and a noexec tmpfs mount.

Setting Effect
security_opt: no-new-privileges:true Prevents processes inside the container from gaining additional privileges (e.g., via setuid binaries)
cap_drop: ALL Drops all Linux capabilities by default
cap_add: [...] Re-adds only the capabilities each service actually needs
tmpfs: /tmp:noexec,nosuid Mounts /tmp as a tmpfs with execution and SUID disabled

These are always on and need no configuration; they are listed so you know what the app does on its own and where the environment variables above fit in.

Protection What it does
Session revocation Every request, page or API, checks the session cookie against the admin record. Changing credentials, linking/unlinking SSO, or logging out increments a version counter and every outstanding cookie stops working immediately — on all devices. A stolen cookie is dead the moment you log out.
Session lifetime 30 days, enforced on both the encrypted payload and the cookie’s Max-Age.
Secure cookie Set automatically on HTTPS requests (see COOKIE_SECURE in Installation).
CSRF Every state-changing /api/* request (POST, PUT, PATCH, DELETE) must carry an Origin or Referer on the app’s own host; a request from another site is rejected with 403. Combined with the SameSite=Lax cookie this also blocks a sibling app on the same parent domain. Requests with neither header (curl, scripts) are allowed — they carry no ambient cookie.
Rate limiting Login, Plex PIN, SSO and password-change endpoints allow 10 attempts per 15 minutes per client, plus a global ceiling of 60 per 15 minutes across all clients that no forged header can escape. See TRUST_PROXY_HEADERS.
Login timing The local login spends the same bcrypt cost whether or not the username exists, so response time does not reveal which usernames are real.
Secret masking API keys, tokens, the OIDC client secret, the AI key and the Discord webhook URL are never returned by the API once saved; the UI shows a placeholder and saving the placeholder keeps the stored value.
Content Security Policy frame-ancestors 'none' and X-Frame-Options: DENY prevent clickjacking; scripts and connections are restricted to the app itself and plex.tv. The production build does not permit eval.
docker-compose.yml
services:
librariarr:
image: ahembree/librariarr:latest
container_name: librariarr
ports:
- "${LIBRARIARR_PORT:-3000}:3000"
environment:
- DATABASE_URL=postgresql://librariarr:${DB_PASSWORD}@librariarr-db:5432/librariarr
- SESSION_SECRET=${SESSION_SECRET:-}
- TZ=${TZ:-UTC}
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
volumes:
- ./config:/config
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
- FOWNER
tmpfs:
- /tmp:noexec,nosuid
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 5s
start_period: 60s
retries: 3
depends_on:
librariarr-db:
condition: service_healthy
restart: unless-stopped
librariarr-db:
image: postgres:18-alpine
container_name: librariarr-db
environment:
POSTGRES_USER: librariarr
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: librariarr
volumes:
- librariarr-db:/var/lib/postgresql
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETUID
- SETGID
tmpfs:
- /tmp:noexec,nosuid
healthcheck:
test: ["CMD-SHELL", "pg_isready -U librariarr"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
librariarr-db:

If you use an existing PostgreSQL server, add the security settings to only the librariarr service:

services:
librariarr:
image: ahembree/librariarr:latest
container_name: librariarr
# ... your existing config ...
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- SETUID
- SETGID
- FOWNER
tmpfs:
- /tmp:noexec,nosuid

Librariarr containerCHOWN, SETUID, SETGID, FOWNER: needed to set up file ownership for the PUID/PGID user mapping and write to the /config volume.

PostgreSQL containerCHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID: needed by the PostgreSQL entrypoint to initialize the data directory and switch to the postgres user.