Seven security issues from an audit, plus album pages and the map coming up empty against Immich 3.x. Verified against a real Immich 3.0.3 instance in a production build. Security: - Admin session forgery via a hardcoded 'dev-fallback-secret' signing key that bypassed the production AUTH_SECRET guard - /api/map ignored album passwords and returned working asset tokens; rewriting ?size=thumbnail to ?size=original downloaded full-resolution originals from protected albums - Admin login had no rate limit, and the client IP came from a spoofable header, so rotating it bypassed every limit in the app - Raw Immich asset UUIDs were serialized into the RSC payload - Album tokens never expired server-side; a captured token replayed forever - Path traversal into the Immich API via an unvalidated album id - /admin ran with no enforced CSP (only Report-Only reached it) TRUSTED_PROXIES is replaced by TRUSTED_PROXY_HOPS. The old variable matched proxy IPs against the socket peer address, which self-hosted Next.js never exposes, so the check could never pass: unset, the limiter read a spoofable header; set, every request shared one bucket and a single client could rate-limit the whole site. Migrates automatically. Immich 3.x: GET /albums/:id no longer embeds assets. Assets now come from POST /search/metadata with withExif: true — without that flag EXIF is omitted and grid aspect ratios, the lightbox panel and map markers stay empty. Drops support for Immich below 3.0, which now logs a warning. Upgrade: requires Immich 3.0+; set TRUSTED_PROXY_HOPS (nginx = 1); visitors of protected areas and admin sessions must log in once more. 91 unit tests (25 new); the new admin-auth and rate-limit cases were confirmed to fail against the pre-fix code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
2.4 KiB
Plaintext
49 lines
2.4 KiB
Plaintext
# ── Immich Server Connection (required) ──────────────────────────
|
|
# Validated by Zod at startup — missing or invalid values produce clear errors.
|
|
|
|
# Base URL of your Immich instance (must be a valid URL)
|
|
IMMICH_API_URL=http://your-immich-server:2283
|
|
|
|
# Immich API key (from Admin → API Keys)
|
|
IMMICH_API_KEY=your-api-key-here
|
|
|
|
# ── Optional Settings ────────────────────────────────────────────
|
|
# All have sensible defaults and are type-checked by Zod.
|
|
|
|
# Site title shown in the header (string, default: "Gallery")
|
|
SITE_TITLE=My Photography
|
|
|
|
# Subtitle displayed below the title (string, default: "")
|
|
SITE_SUBTITLE=A visual journal
|
|
|
|
# Cache TTL in seconds (integer >= 0, default: 300 = 5 minutes)
|
|
CACHE_TTL=300
|
|
|
|
# Rate limit for image proxy (integer >= 1, requests per minute per IP, default: 120)
|
|
# RATE_LIMIT_RPM=120
|
|
|
|
# Number of reverse proxies in front of the app (nginx/Traefik/Caddy = 1,
|
|
# Cloudflare in front of nginx = 2). Required for correct rate limiting behind
|
|
# a proxy — without it the client IP is read from a spoofable header.
|
|
# The app must be reachable ONLY through the proxy (bind to 127.0.0.1).
|
|
# TRUSTED_PROXY_HOPS=1
|
|
|
|
# Secret for signing auth cookies and encrypting asset tokens (string, min 16 chars).
|
|
# If not provided, IMMICH_API_KEY will be used as a fallback (not recommended).
|
|
# AUTH_SECRET=your-super-long-random-secret-here
|
|
|
|
# Webhook secret for cache invalidation (optional, string).
|
|
# Set this to a long random string and configure Immich to send webhooks to
|
|
# POST /api/webhook with an x-immich-signature header (HMAC-SHA256 of the body).
|
|
# If not set, the webhook endpoint returns 501 and is effectively disabled.
|
|
# WEBHOOK_SECRET=your-long-random-webhook-secret
|
|
|
|
# ── Admin Panel ──────────────────────────────────────────────────
|
|
# Set a password to enable the visual admin panel at /admin.
|
|
# Leave unset or empty to disable the admin UI entirely.
|
|
# ADMIN_PASSWORD=your-secure-admin-password
|
|
|
|
# ── Album configuration ──────────────────────────────────────────
|
|
# Albums, subpages, hero image, and passwords are defined in content/gallery.yaml
|
|
# See content/gallery.yaml.example for the full reference.
|