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>
45 lines
1.1 KiB
JSON
45 lines
1.1 KiB
JSON
{
|
|
"name": "immich-lightbox",
|
|
"version": "0.9.0",
|
|
"license": "MIT",
|
|
"scripts": {
|
|
"dev": "next dev",
|
|
"build": "next build",
|
|
"start": "next start",
|
|
"lint": "eslint .",
|
|
"format": "prettier --write .",
|
|
"format:check": "prettier --check .",
|
|
"test": "vitest",
|
|
"test:unit": "vitest run",
|
|
"test:coverage": "vitest run --coverage",
|
|
"test:e2e": "playwright test"
|
|
},
|
|
"dependencies": {
|
|
"@dnd-kit/core": "^6.3.1",
|
|
"@dnd-kit/sortable": "^10.0.0",
|
|
"@dnd-kit/utilities": "^3.2.2",
|
|
"js-yaml": "^4.1.1",
|
|
"leaflet": "^1.9.4",
|
|
"next": "16.2.4",
|
|
"react": "19.2.6",
|
|
"react-dom": "19.2.6",
|
|
"thumbhash": "^0.1.1"
|
|
},
|
|
"devDependencies": {
|
|
"@playwright/test": "^1.58.2",
|
|
"@types/js-yaml": "^4.0.9",
|
|
"@types/leaflet": "^1.9.21",
|
|
"@types/node": "^20",
|
|
"@types/react": "^19",
|
|
"@types/react-dom": "^19",
|
|
"@vitest/coverage-v8": "^4.0.18",
|
|
"eslint": "^9",
|
|
"eslint-config-next": "16.2.4",
|
|
"eslint-config-prettier": "^10.1.8",
|
|
"eslint-plugin-prettier": "^5.5.5",
|
|
"prettier": "^3.8.3",
|
|
"typescript": "^5",
|
|
"vitest": "^4.0.18"
|
|
}
|
|
}
|