Files
vibesonic/docs/external-auth.md
owenandClaude Sonnet 5 789bf91eed Complete Phase 4: security hardening, player/playlist/admin UI, docs
Security:
- New internal/netguard package: podcast feed/enclosure fetching now
  refuses to dial non-public IPs (loopback/RFC1918/link-local/cloud
  metadata/CGNAT), validated per-connection including across redirects,
  closing an SSRF gap where a malicious or compromised feed URL could
  probe the server's internal network. Configurable via
  VIBESONIC_PODCASTS_ALLOW_PRIVATE_NETWORKS for intentional LAN feeds.
- Podcast episode downloads capped at 4GB (was unbounded, disk-exhaustion risk)
- Cover art decoding now rejects images above a 40-megapixel budget
  before full decode (decompression-bomb guard)
- HTTP server sets ReadHeaderTimeout (slowloris hardening)
- Admin folder-scan redirect now validated as same-site instead of
  trusting the raw Referer header (open-redirect fix)
- Legacy Subsonic token/salt auth now uses a constant-time comparison

Web UI completion:
- Persistent player: shuffle and repeat (off/all/one) controls, wired
  into queue advance/prev logic and persisted like other player state
- Admin user-permissions form: adminRole toggle (with a guard against
  demoting the last remaining admin) and per-user music-folder access
  checkboxes, backed by existing schema/enforcement that was previously
  unreachable from the UI
- Playlist M3U8 import/export

Docs: added docs/client-compatibility.md (honest tracking template —
no client testing has been performed), docs/deployment.md,
docs/security.md, docs/backup.md; refreshed README and cross-links;
added a footnote in docs/subsonic-compatibility.md distinguishing
spec-standard from VibeSonic-original OpenSubsonic extensions.

Also added Go fuzz tests for the external-auth CIDR/IP parser and the
LRC lyrics parser.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014r98AtcBbevpscWLsfwVvv
2026-08-23 09:18:17 -05:00

3.6 KiB

External authentication

VibeSonic supports optional Navidrome-style externalized authentication: a trusted reverse proxy authenticates the browser user (via whatever mechanism it likes — OIDC, SAML, HTTP Basic Auth, a VPN identity, ...) and asserts that identity to VibeSonic via an HTTP header. VibeSonic never implements OIDC or talks to an identity provider itself — the proxy handles authentication, VibeSonic handles users and authorization.

Configuration

Variable Default Description
VIBESONIC_EXTAUTH_ENABLED false Enable external authentication
VIBESONIC_EXTAUTH_TRUSTED_SOURCES (none) Comma-separated list of IPv4/IPv6 CIDRs (or bare IPs) that are trusted to assert identity, e.g. 127.0.0.1/32,10.0.0.5/32
VIBESONIC_EXTAUTH_USER_HEADER Remote-User Header name carrying the authenticated username
VIBESONIC_EXTAUTH_LOGOUT_URL (none) Optional URL to send users to on logout (e.g. the proxy's own logout endpoint), instead of VibeSonic's local /login
VIBESONIC_EXTAUTH_AUTO_CREATE_USERS true Automatically create a VibeSonic user the first time a trusted identity is seen

The critical security property

The identity header is only trusted when the actual TCP connection peer address matches a configured trusted CIDR. This check (internal/auth/extauth.go) reads http.Request.RemoteAddr — the real socket peer — never a header.

Headers like X-Forwarded-For, X-Real-IP, or Forwarded are never consulted to decide whether the peer is trusted. Those headers are attacker-controlled: any client can set them to anything. If VibeSonic trusted them, an arbitrary internet client could add X-Forwarded-For: 127.0.0.1 and a Remote-User: admin header to its own direct request and be treated as an authenticated administrator.

This is why VIBESONIC_EXTAUTH_TRUSTED_SOURCES has no default — not 0.0.0.0/0, not ::/0, not RFC1918 ranges. External auth trusts nothing until you explicitly name a network (typically 127.0.0.1/32 for a proxy on the same host, or the proxy's specific address on a Docker bridge network).

See internal/auth/extauth_test.go for the regression tests covering this, including a test that a spoofed X-Forwarded-For/X-Real-IP/ Forwarded header from an untrusted peer is ignored entirely.

How it resolves to a user

A trusted identity resolves to an ordinary VibeSonic user (with external_auth = 1), never a separate "external user" type. If VIBESONIC_EXTAUTH_AUTO_CREATE_USERS=true and no user with that username exists yet, one is created with no local password. If a user with that username already exists (created locally or by a previous external login), it's reused.

Interaction with native auth

External auth and native session/password login coexist: the Authenticate middleware tries the session cookie first, then falls back to the trusted-proxy header. A user can still log in with a local password on VibeSonic's own /login page even when external auth is enabled, unless their account has no password hash set (auto-created external users start this way).

/rest/* (the Subsonic/OpenSubsonic API) never consults external auth at all: it authenticates exclusively via Subsonic's own token/salt, password, or apiKey scheme (see internal/subsonic/auth.go), so real Subsonic/OpenSubsonic clients keep working unmodified even when external auth fronts the browser UI. This is also why a reverse proxy enforcing SSO in front of VibeSonic should let /rest/* bypass that SSO layer — see docs/deployment.md for example proxy configuration.