Credential registry — the single source of truth (Prax ⇄ proxy, no drift)

Every credential Prax supports is registered in exactly one place — — and classified by whether/how the secrets-proxy can hold it instead of Prax. This page mirrors that registry for humans; the code is authoritative.

Synced from Prax at f62d7985 View source ↗

← Security · Related: The secrets proxy

Every credential Prax supports is registered in exactly one place — prax/services/credential_registry.py — and classified by whether/how the secrets-proxy can hold it instead of Prax. This page mirrors that registry for humans; the code is authoritative.

Why this exists (the never-drift contract)

Keyless Prax only works if there’s a complete, authoritative answer to “what credentials does Prax use, and can the proxy handle each?” If a new key gets added to settings.py and nobody updates the proxy story, a secret silently stays in Prax’s env that the operator believed was gone — drift, and a security regression.

So the invariant is enforced by a test, not by discipline: tests/test_credential_registry.py fails CI if any *_KEY / *_TOKEN / *_SECRET / *_API field exists in settings.py without a matching row in the registry. You cannot add a credential and forget to classify it. (It has already earned its keep — it caught NEO4J_PASSWORD and two SSH keys on first run.)

Adding a credential (the whole procedure)

  1. Add the Field(..., alias="NEW_KEY") in prax/settings.py.
  2. Add a Credential(...) row in credential_registry.py, choosing a class:
    • PROXY_MODEL — a model provider reached via a base-URL override (proxied today).
    • PROXY_FORWARD — an ordinary HTTPS REST API; proxyable only via the transparent forward proxy (Tier-2, planned) — until then the key stays in Prax.
    • PROXY_LOCAL — in-process signing, an inbound token, or Prax’s own co-located infra; the proxy cannot and should not hold it.
  3. If it’s MODEL/FORWARD, wire the proxy side to inject it.

The test enforces step 2. Keep the classification honest.

The three classes

  • Tier 1 — model providers (proxied today). Base-URL override (OPENAI_BASE_URL / ANTHROPIC_BASE_URL); the proxy swaps the presented token for the real key. This is what ships in prax-secrets-proxy now.
  • Tier 2 — REST APIs (planned). These SDKs don’t expose a base-URL knob, so they can only be proxied by a transparent forward proxy (HTTPS_PROXY + a CA Prax trusts) that injects auth by destination host. Until that ships, these keys stay in Prax. See secrets-proxy.md → Tier 2.
  • Not proxyable. In-process (FLASK_SECRET_KEY), inbound (MCP_BEARER_TOKEN), Prax’s own infra (NEO4J_PASSWORD, sandbox/TeamWork), or non-HTTP (git-over-SSH keys). By design these live only in Prax.

The registry (mirrors the code — 29 credentials)

Tier 1 — model providers · proxied TODAY via base-URL override

Env Service Host Inject
OPENAI_KEY OpenAI (or OpenAI-compatible base URL) api.openai.com bearer
ANTHROPIC_KEY Anthropic api.anthropic.com x-api-key
OPENROUTER_API_KEY OpenRouter (cheap-eval) openrouter.ai bearer¹

¹ Proxyable via the openai leg (PROXY_OPENAI_BASE_URL=…openrouter…) or its own upstream; needs Prax’s openrouter base URL pointed at the proxy.

Tier 2 — REST APIs · proxyable only via the transparent forward proxy (PLANNED)

Env Service Purpose Host Inject
BRAVE_API_KEY Brave Search web search api.search.brave.com X-Subscription-Token
TAVILY_API_KEY Tavily web search api.tavily.com bearer
SERPER_DEV_API_KEY Serper.dev web search google.serper.dev X-API-KEY
JINA_API_KEY Jina AI URL reader + search r.jina.ai bearer (reader works keyless)
GOOGLE_API_KEY Google programmable search / Gemini vision www.googleapis.com ?key= ⚠️
GOOGLE_CSE_ID Google CSE search engine id www.googleapis.com ?cx= ⚠️
VISION_API_KEY Vision provider image analysis bearer
ELEVENLABS_API_KEY ElevenLabs text-to-speech api.elevenlabs.io xi-api-key
AMADEUS_API_KEY Amadeus travel search (OAuth id) api.amadeus.com basic
AMADEUS_API_SECRET Amadeus travel search (OAuth secret) api.amadeus.com basic
TWITTER_API X / Twitter v2 fetch tweets/threads api.twitter.com bearer
THREADS_API Meta Threads fetch Threads posts graph.threads.net bearer
NYT_PASSWORD New York Times news access www.nytimes.com basic
HF_TOKEN_RO Hugging Face gated dataset fetch² huggingface.co bearer
TWILIO_ACCOUNT_SID Twilio SMS/voice (basic user) api.twilio.com basic
TWILIO_AUTH_TOKEN Twilio SMS/voice (basic pass) api.twilio.com basic

² Used at dataset-fetch time (scripts), not agent runtime — low priority.

⚠️ Google keys are deliberately UNSET (2026-07-22). They remain classified PROXY_FORWARD (forward-proxyable in principle), but we hold no Google key. Google Cloud billing is not a hard-capped pay-as-you-go product — a runaway loop or a prompt-injected agent could run up unbounded charges with no ceiling to stop it, which is a categorically worse failure mode than a metered per-call API. So the safe default is to hold no Google key at all: Prax degrades to the keyless search providers and non-Google vision. Only set GOOGLE_API_KEY behind a Google Cloud budget/quota cap you have configured yourself.

Verified live 2026-07-22: Serper, OpenAI, and Twilio (basic auth) all inject + work through the forward proxy; ElevenLabs injects correctly but the held key is stale (401 → rotate).

Not proxyable — stays in Prax by design

Env Why it stays local
DISCORD_BOT_TOKEN non-HTTP — the bot gateway is a websocket carrying the token in its IDENTIFY payload (no header to inject), and REST wants Authorization: Bot <token> (prefix generic injection omits → 401). Verified 401 through the proxy 2026-07-22.
FLASK_SECRET_KEY in-process session signing; never egresses
MCP_BEARER_TOKEN inbound — authenticates other agents to Prax
SANDBOX_DAEMON_TOKEN / SANDBOX_CLIENT_KEY Prax’s own remote-sandbox infra (bearer / mTLS key)
TEAMWORK_API_KEY Prax’s own co-located UI (loopback/tailnet)
NEO4J_PASSWORD Prax’s own graph DB over bolt://
GPU_POWER_BROKER_TOKEN infra control token (reclassify FORWARD if it ever calls a third party)
PRAX_SSH_KEY_B64 / PLUGIN_REPO_SSH_KEY_B64 git-over-SSH — not an HTTP API the egress proxy can inject

Not in the registry: per-repo deploy keys

Attaching a git repo to a Library space generates an ed25519 deploy key per repository (~/.prax/git-keys/{user}/{space}/{repo}, mode 0600). They are not registry rows because they are not env-var credentials — nothing in settings.py names them, so the drift-guard has nothing to check.

They belong to the same class as PRAX_SSH_KEY_B64: git-over-SSH, not proxyable. The mitigation is scope rather than injection — a deploy key is registered on exactly one repository, so a leak exposes that repo and not everything the account can reach. Write access is off until a human toggles it, per repository. Full design: space-git-repos.md.

DISCORD_BOT_TOKEN — on Prax by necessity: less risky, not risk-free

This is the one third-party credential the keyless-Prax invariant genuinely does not cover, so be honest about it. It can’t move to the proxy (the bot needs the token locally to open its gateway websocket — see the table), so a prompt-injected or compromised Prax can read it from its own env.

Why that’s less dangerous than a leaked cloud/model key: a stolen bot token can’t spend money and can’t reach any other provider — its blast radius is bounded to the Discord guilds the bot is already in. Why it’s still dangerous: within that radius it grants full impersonation of the bot — read and send in every channel it can see, DM users — until the token is regenerated. So treat it with care: give the bot minimal permissions/intents, and rotate the token the moment Prax is suspected of compromise. “Keyless except Discord” is the accurate claim, not “keyless.”

Honest status

Today, keyless covers the 3 model keys (the highest-value theft target) — that’s real and worth doing. The 16 Tier-2 REST keys can all move to the proxy via the transparent forward proxy (built, unit-tested, first providers verified live — see the ledger); the 10 local credentials — DISCORD_BOT_TOKEN and the 9 in-process / inbound / own-infra keys above — fundamentally can’t. So “Prax holds zero secrets” is the direction, and this registry is the honest, enforced map of how far along the path each credential is — with a test making sure the map never lies.