Files
felhom.eu/documentation/audits/proxyauth-spike-2026-10-18/DESIGN-NOTE.md
T

60 lines
4.7 KiB
Markdown

# R-1100 — one sign-in for the dashboard and FileBrowser: the spike (2026-10-18 evening)
Measured on scratch 9202 (demo-hp, Tier 0): FileBrowser Quantum `gtstef/filebrowser:1.5.6-stable` (source read at tag
`v1.5.6-stable`, commit `5d9b4df2`), Traefik v3.7.13, controller 0.331.0-rc.1. Nothing was built; every change on 9202 was
removed (`03-teardown.txt`). Evidence: `01-strip.txt` (every request and answer), `02-teardown-users.txt`.
## What FileBrowser's proxy auth is (source, at the pinned tag)
- The config key is **`auth.methods.proxy`** (`backend/common/settings/auth.go:58`, json `proxy` — not `proxyAuth`, as the
row said), with `enabled` and `header` (`auth.go:75-78`: „FileBrowser blindly accepts the header value as username").
- Any request that carries the header is that user (`backend/http/middleware.go:302-307` on login, `:384-401` on every API
call, `:494-520` `getProxyUser`). **A name FileBrowser has never seen is created on the spot**
(`backend/http/auth.go:81-106`), with the user defaults. A name that exists as a password user is refused
(`auth.go:107-108`, `ErrWrongLoginMethod`) — so the household's `admin` cannot be reached by a header; a header user
is a separate FileBrowser user.
## What was measured
| Request | Answer |
|---|---|
| a route with no middleware, forged `X-Felhom-User: spike-stranger` | **200 — the drive's listing**; FileBrowser created the user |
| Traefik `forwardAuth` that answers 200 and sets NO user header, `authResponseHeaders: [X-Felhom-User]`; forged header (also lower-case, also twice) | **401** from FileBrowser („no token present") — Traefik removed the client's header |
| `forwardAuth` that sets `X-Felhom-User: household-sso` only for a valid cookie; cookie ok + forged `attacker` | 200 as **`household-sso`** (FileBrowser's own request log names the user) — the auth answer replaced the forged value |
| the same, wrong cookie + forged header | 401 from the auth step; FileBrowser never reached |
| **from inside the Nextcloud container**, straight to `http://filebrowser:80`, forged header | **200** — no Traefik on that path |
So: **Traefik can pass the header only for a valid session and strips one a client sends — proven.** But the header is
also trusted from any container that reaches FileBrowser's port, and every app sits on `traefik-public` with it.
## The three things a build must solve
1. **The session cannot be read on `files.<domain>`.** The dashboard cookie `felhom_session` has no `Domain`
(`controller/internal/web/auth.go:197-205`), so a browser never sends it to `files.<domain>`, and a forward-auth there
sees nothing. **Do not widen it to `Domain=<domain>`**: every app's subdomain — third-party code — would then receive the
dashboard session. Instead a files-only ticket: „Fájlok ↗" goes through the dashboard, which mints a one-time,
~60-second token; `files.<domain>/_felhom/sso?t=…` (a Traefik route to the controller) swaps it for a host-only,
HttpOnly, Secure cookie for `files.<domain>`; the forward-auth checks that cookie and answers the header.
2. **Any app container can forge the header** (measured). Two cheap closes, either enough, both better:
(a) **the header name is a per-box secret** (`X-Felhom-User-<32 hex>`), written only into FileBrowser's config and
Traefik's dynamic file — an app that does not know the name cannot send it; (b) FileBrowser **off `traefik-public`**:
Traefik joins `felhom-filebrowser` instead (and OnlyOffice too, for its `server.internalUrl`, R-1018). (b) changes the
R-1126 gate's shape; (a) does not.
3. **Header users are new FileBrowser users.** The household's files permissions for that user must be set by the box (the
user defaults decide today); shares the household made as `admin` stay `admin`'s; logout needs
`logoutRedirectUrl` to the dashboard.
## Unchanged by it
- **The password reveal (R-513)** stays: `admin` remains a password user — the fallback when the ticket path fails, and
the operator's. The „Fájlok ↗" button would no longer need it.
- **The R-1126 gate** keeps checking `admin`'s default login; header users do not touch it.
## Cost and recommendation
Controller only (no agent, no hub): a ticket endpoint and a forward-auth endpoint, one Traefik route + middleware, the
FileBrowser config block with a secret header name, the header user's permissions, tests for each refusal (forged header
via Traefik; forged header from an app container — with a secret name; stale/replayed ticket; wrong host). About one
release. **Recommendation: build it with (1) the ticket cookie and (2a) the secret header name**; do not ship header
trust without 2a — the measured app-container path would hand every app the household's files.