security: fix pre-auth bypasses and restore Immich 3.x compatibility (#378)

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>
This commit is contained in:
Ralksta
2026-07-20 18:57:57 +02:00
committed by GitHub
parent cf2e8e6ece
commit 37f7f62157
30 changed files with 821 additions and 226 deletions

View File

@@ -59,6 +59,11 @@ Immich Folio acts as a **secure reverse proxy** between your visitors and your p
</details>
## Requirements
- **Immich 3.0 or newer.** Immich 3.0 changed how album assets are retrieved; earlier versions are not supported as of v0.9.0. On an older server, albums render with the correct title but no photos, and the map stays empty — the app logs a warning naming this as the likely cause.
- Node.js 20+ (or just use the Docker image)
## Quick Start
```bash
@@ -91,19 +96,23 @@ IMMICH_API_KEY=your-api-key
SITE_TITLE=My Photography # default: "Gallery"
SITE_SUBTITLE=A visual journal # default: empty
CACHE_TTL=300 # seconds, default: 300
RATE_LIMIT_RPM=120 # requests/min/IP, default: 120
RATE_LIMIT_RPM=1500 # requests/min/IP, default: 1500
AUTH_SECRET=long-random-string # required in production
TRUSTED_PROXY_HOPS=1 # reverse proxies in front, default: 0
ADMIN_PASSWORD=your-secure-password # enables /admin panel
```
> Behind a reverse proxy, set `TRUSTED_PROXY_HOPS` to the number of proxies in front of the app (nginx/Traefik/Caddy = 1; Cloudflare in front of nginx = 2). Without it the client IP is read from a header the client itself can set, which defeats the brute-force limits on the password endpoints. See [Trusted Proxies](docs/gallery-config.md#trusted-proxies).
### Immich API Key Permissions
Create a dedicated API key in Immich under **Account Settings → API Keys**. Immich Folio only needs **read access** — it never modifies your library.
| Permission | Required | Used for |
|---|---|---|
| `album.read` | ✅ Yes | List and fetch album metadata & photo lists |
| `asset.read` | ✅ Yes | Fetch asset metadata, EXIF data, thumbnails, previews, and originals |
| `asset.view` | ✅ Yes | Stream image/video files (thumbnail, preview, video playback) |
| Permission | Required | Used for |
| ------------ | -------- | -------------------------------------------------------------------- |
| `album.read` | ✅ Yes | List and fetch album metadata & photo lists |
| `asset.read` | ✅ Yes | Fetch asset metadata, EXIF data, thumbnails, previews, and originals |
| `asset.view` | ✅ Yes | Stream image/video files (thumbnail, preview, video playback) |
> **No write permissions needed.** `album.create`, `asset.upload`, `asset.delete`, etc. can all be left **off**.