filament-bridge is designed for self-hosted LAN use. Its security layer is intentionally minimal — single-account, no roles, no multi-user support.
Authentication is controlled by the AUTH_ENABLED environment variable
(default true). When disabled, the app is fully open.
When enabled:
- On first visit the frontend detects no password is set and shows a Setup screen.
- The user sets an admin password. The backend stores only the bcrypt hash in
BridgeConfig(admin_password_hashkey). Plaintext is never stored or logged. - Subsequent visits show a Login screen. On success the backend sets an
httpOnly, SameSite=Lax cookie named
fb_sessioncontaining an itsdangerousTimestampSigner-signed payload. The cookie is markedSecureonly when the request arrives over HTTPS (LAN deployments commonly use plain HTTP). - Sessions are stateless — there is no sessions table or migration. The server validates the signature and checks max-age (30 days) on every request.
- The signing secret (
auth_secret) is auto-generated on first startup and persisted inBridgeConfigso sessions survive container restarts.
All /api/* routes require authentication except:
GET /api/healthGET /api/auth/statusPOST /api/auth/loginPOST /api/auth/setup
The React SPA (/, /assets/*, and all client-side routes) is always public — the
frontend renders the login/setup screen itself.
An optional single API token enables machine access (CI, Moonraker, scripts):
- Enable: Settings → Security → Generate token → toggle enabled.
- Use: send
Authorization: Bearer <token>orX-API-Key: <token>on any protected/api/*request. - Validation: constant-time compare via
secrets.compare_digest. - The token value is stored in
BridgeConfig(api_token) and displayed (masked) in the Security section of Settings so users can copy it. This is an intentional tradeoff for a single-user self-hosted app — the token is no more secret than the database file itself. - Tokens do not expire automatically. Use Regenerate token to rotate.
There is no in-app password reset. If locked out:
- Set
AUTH_ENABLED=falsein your environment (.envfile or Docker env). - Restart the container.
- Open the app (auth bypassed), go to Settings → Security, set a new password.
While
AUTH_ENABLED=falsethe change-password endpoint does not require the old password — that's what makes recovery from a forgotten password possible. - Restore
AUTH_ENABLED=trueand restart again.
Note: change-password and api-token/regenerate require an authenticated session
when AUTH_ENABLED=true (a known current password alone is not sufficient).
| Concern | Choice | Rationale |
|---|---|---|
| Session integrity | itsdangerous TimestampSigner (HMAC-SHA256) |
Ships with Starlette; signed cookie expires via 30-day max-age |
| Password hashing | bcrypt (cost=12, auto-generated salt) | Industry standard for password storage; intentionally slow |
| Secret generation | secrets.token_urlsafe(32) |
Python stdlib CSPRNG |
| Token comparison | secrets.compare_digest |
Constant-time to prevent timing attacks |
A bridge backup export (GET /api/backup/export) includes auth_secret,
admin_password_hash, and api_token alongside mappings and config. This is
intentional — a restore (POST /api/backup/import) must be full-fidelity so
sessions and API tokens continue to work without requiring the user to re-set
credentials. Store backup files with the same care as the bridge database volume.
- Multi-user support
- Password reset tokens / email flow
- Account lockout after failed attempts (bcrypt cost blunts brute force)
- Signed HTTPS enforcement (LAN deployment assumption)
- CSRF protection (SameSite=Lax cookie + JSON-only API mitigate standard CSRF)