Summary
The Bitwarden web vault caches the /api/config response in localStorage
(key: global_config_byServer). When a self-hosted server administrator
changes server-side configuration (e.g., toggles a feature flag or
registration policy), the cached config persists on existing clients
through page refreshes, hard refreshes, and even network cache bypass
in DevTools. The new configuration only takes effect after the user
manually clears site data.
Observed behavior
Reproduced on Safari 17.x (macOS) against a self-hosted Bitwarden-
compatible server (Vaultwarden-derived web vault):
- Initial page load with
disableUserRegistration: false— “Create
account” link is visible (correct). - Administrator changes server env var to
DISABLE_USER_REGISTRATION=true. - Server endpoint
/api/confignow returnsdisableUserRegistration: true
when accessed directly in the browser. - Web vault page still shows “Create account” link after:
- Normal page refresh
- Hard refresh (Cmd+Shift+R / Cmd+Option+R)
- Web Inspector → Network → “Disable caches” toggle + reload
- “Create account” link disappears only after clearing site data via
Safari Settings → Privacy → Manage Website Data → Remove.
Chrome on the same server showed the updated configuration immediately.
Expected behavior
At least one of the following:
- The cached config should have a shorter TTL that guarantees eventual
consistency within a reasonable window (e.g., 5–15 minutes). - The client should revalidate the cached config on navigation events
(login page load, vault unlock, etc.), possibly using HTTP conditional
requests (If-None-Match/ETag) so a revalidation is cheap. - The client should expose a mechanism for a self-hosted administrator
to invalidate client caches (e.g., by bumping a config version/epoch
field in the response that clients compare and use to invalidate local
cache). - At minimum, a forced hard-refresh (Cmd+Shift+R) should bypass the
localStorage cache for config, since users reasonably expect this
shortcut to refetch from origin.
Why this matters
This behavior is particularly problematic for self-hosted deployments
(Vaultwarden, Bitwarden self-hosted, Warden-Worker, etc.) where admins
routinely change server-side configuration. The current behavior creates
a confusing mismatch between server state and client state that is:
- Not diagnosable through standard HTTP caching controls — no
Cache-ControlorETagheader from the server can override this
because the cache lives in application-level localStorage. - Not cleared by familiar browser operations (“clear cache”, hard
refresh, disable cache in DevTools). - Discoverable only by inspecting the Storage tab in DevTools, which
most users (and even many administrators) will not think to check.
For end users, the most visible symptom is stale feature flag state;
for administrators, it’s extended debugging sessions (“I changed the
setting, why isn’t it working?”).
Suggested implementation approaches
- TTL reduction: Set a conservative TTL on the config cache (e.g.,
10 minutes for authenticated state, shorter for the login page where
registration visibility is decided). - Conditional revalidation: Send
If-Modified-SinceorIf-None-Match
headers on cached config; server returns 304 if unchanged (cheap)
or 200 with new body if changed. - Version/epoch field: Include a
configVersionorserverEpoch
field in the config response. Client compares against cached value on
each session; mismatch forces re-fetch and cache update. - Hard-refresh bypass: Detect
Cache-Control: no-cacheon the
navigation request (which is what hard refresh sends) and skip the
localStorage read path for that load.
Environment
- Browser: Safari 17.x on macOS 14.x (behavior also observed on other
versions) - Server: Self-hosted Vaultwarden-compatible server
(qaz741wsd856/warden-worker fork, /api/config returns standard Bitwarden
config shape) - Web vault: Bundled via bw_web_builds v2025.12.0
Happy to provide additional reproduction details or test proposed fixes.
