Fix 500 on every provider resolution when BYOK key matches the demo key

The demo-key backstop added in 1dd3108 keyed its check on
`api_key == DEMO_API_KEY` rather than on `using_demo`. That looked stricter but
was wrong: the demo key is an ordinary OpenRouter key, so a user can
legitimately paste that same value into their own Settings as BYOK. The guard
then raised on every resolve_provider_config() call for that account.

Because me_payload() resolves a provider config, this 500'd GET /api/auth/me —
the SPA's bootstrap call — so the frontend's `me` never resolved and the nav
(including the AI Chat link) never rendered, on top of chat itself failing.

`using_demo` is the flag that actually means "the server is paying", and only
resolve_provider_config's demo branch sets it, so the pinning guarantee is
unchanged: server-funded turns still can't reach an off-whitelist model.

Adds a regression test for a BYOK user whose key equals the demo key value, and
corrects the test that had asserted the buggy behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FGY1yvzSeKgTtRfeVtDmx
This commit is contained in:
parththakkar106
2026-07-26 20:25:56 +05:30
co-authored by Claude Opus 5
parent 1dd31086c1
commit 49e2dde5cf
2 changed files with 45 additions and 14 deletions
+14 -8
View File
@@ -89,14 +89,20 @@ class ProviderConfig:
using_demo: bool
def __post_init__(self) -> None:
# Belt and braces around the shared demo key. resolve_provider_config()
# already pins the model, but this makes it a property of the config
# object itself: however it was built, and by whichever caller, the
# server-funded key can never be paired with an off-whitelist (i.e.
# possibly paid) model. Unreachable by design — a 500 here means a new
# code path tried to bypass the pinning, which is worth failing loudly
# rather than silently billing.
if DEMO_API_KEY and self.api_key == DEMO_API_KEY and self.model not in DEMO_MODELS:
# Belt and braces around server-funded turns: resolve_provider_config()
# already pins the model, and this makes it a property of the config
# object too, so a future caller can't construct an unpinned one.
# Unreachable by design — a raise here means a new code path bypassed
# the pinning, which is worth failing loudly rather than billing.
#
# The test is `using_demo`, NOT `api_key == DEMO_API_KEY`. Keying it on
# the key value looks stricter but is wrong: the demo key is a normal
# OpenRouter key, so a user can legitimately paste that same key into
# their own Settings as BYOK — and then every resolution raised, 500ing
# even GET /auth/me and taking the whole SPA down with it. `using_demo`
# is what actually means "the server is paying", and only the demo
# branch below sets it.
if self.using_demo and self.model not in DEMO_MODELS:
raise ValueError(
f"Refusing to use the shared demo key with non-whitelisted model {self.model!r}"
)