Bring Your Own Keys, Safely: The BYOK Security Model Behind ModelPlane
If you're building on top of LLMs, you've already made the security decision that matters most: you're not training your own models. You're renting intelligence from someone else's API. The question is how you manage the credentials that unlock that intelligence.
Most teams hardcode a provider key into a service, or worse, share one key across an entire organization. When that key leaks—and it will—you're not just out a few dollars. You're exposed to prompt injection attacks, data exfiltration, and a billing nightmare that takes weeks to untangle.
The solution isn't to stop using LLMs. It's to route your traffic through a gateway that treats credentials as first-class, tenant-isolated secrets. That's the BYOK (Bring Your Own Key) model at the heart of ModelPlane. It's not just about convenience—it's about building a security boundary between your application and the providers you depend on.
The Wrong Unit of Integration
Before we talk about keys, let's talk about what you're actually integrating with. Most teams think in terms of providers: "We use OpenAI," or "We're on Anthropic now." That's the wrong mental model.
The provider is the wrong unit of integration. Your application shouldn't care whether a request is answered by gpt-4o, claude-3-5-sonnet, or deepseek-v3. It should care about the capability: a fast chat model, a reasoning model, a cheap batch model. That capability is what we call a model group.
A model group is a name you control—like prod-chat—that maps to one or more provider backends with a routing strategy. When your app sends a request to model="prod-chat", ModelPlane decides which backend answers, based on fallback rules, load-balancing weights, or conditional logic.
This abstraction changes the security conversation. Instead of managing N provider keys across M services, you manage one gateway key per environment, and the gateway manages the provider keys for you. The keys become infrastructure, not application logic.
Two Keys, Two Jobs
The first thing to understand about ModelPlane's security model is that there are two entirely different types of credentials in play. Confusing them is the root of most LLM gateway security failures.
Gateway API Key (gw-*): This is what your application sends to ModelPlane. It starts with gw- and is a bearer token that authenticates your tenant. It's how we know which workspace is making the request, which model groups it can access, and which billing account to charge.
Backend Credential: This is the actual provider API key—your OpenAI key, your Anthropic key, your DeepSeek key. You upload these to ModelPlane via the Backends page or API, and we store them encrypted.
The critical rule: the gateway key is for authentication only. It is never forwarded upstream. When your request hits our edge, the Authorization header carrying gw-* is stripped before the request is routed to any provider. Forwarding it would be a bug—and it's a bug we've designed against at the middleware level.
This separation means a leaked gateway key gives an attacker access to your ModelPlane usage, not to your underlying provider accounts. They could burn your credits, but they can't exfiltrate your OpenAI or Anthropic keys. That's a meaningful reduction in blast radius.
How Backend Credentials Are Stored
When you upload a provider key to ModelPlane, it doesn't go into a database table where a SQL injection or a rogue admin could read it. It goes into Cloudflare Workers KV, encrypted.
The encryption model is worth understanding because it's the difference between "we encrypt your data" and "we encrypt your data in a way that actually protects you."
Each credential is stored under a key that includes your user ID: cred:{userId}:{credId}. The payload is encrypted with AES-GCM, using a root key from a Worker secret. But here's the important part: that root key isn't used directly. It's fed through HKDF with your user ID as the salt, deriving a per-user encryption key.
This means two things. First, your credentials are encrypted at rest with a key that's unique to you. Second, when the gateway needs to use a credential, it decrypts only the keys belonging to the current tenant. Cross-tenant access—even by accident—is rejected at the hydration layer.
We also never cache decrypted credentials. The routing cache may hold model-group metadata, but the API keys themselves are only decrypted in memory for the duration of a request, then discarded.
What This Means for Your Threat Model
Let's be concrete about what this architecture protects against.
Scenario 1: A gateway key leaks. An attacker gets a gw-* token from a compromised service. They can make requests through your model groups, spending your credits. But they cannot extract your provider keys, and they cannot access your other tenants' data. You revoke the key, and the attack surface closes.
Scenario 2: A database backup leaks. Supabase, Workers KV, S3—any of these could theoretically be compromised. But the provider keys in KV are AES-GCM encrypted with per-user keys. The gateway key hashes in Supabase are SHA-256, which means the plaintext is unrecoverable. A backup leak is a nuisance, not a catastrophe.
Scenario 3: An insider threat. A ModelPlane employee with database access still can't read your provider keys. The encryption keys are in Worker secrets, not in the database. This is the BYOK promise: you're not trusting us with your keys, you're trusting us with encrypted keys.
The Practical Setup
Here's what this looks like in practice. You sign up for ModelPlane, add your first backend, and point your OpenAI-compatible client at our endpoint.
First, add a backend credential. You can do this through the portal or via the API:
curl -X POST https://modelplane.dev/api/backends \
-H "Authorization: Bearer $GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "openai",
"apiKey": "sk-your-openai-key",
"options": { "models": ["gpt-4o", "gpt-4o-mini"] }
}'
The response gives you a backendId. You reference that ID in a model group, which defines the routing strategy. Then your application only ever sees the gateway key.
Here's a complete Python example using the standard openai client:
from openai import OpenAI
client = OpenAI(
base_url="https://modelplane.dev/v1",
api_key="gw-your-gateway-key", # never a provider key
)
# "prod-chat" is a model group, not a provider model ID
response = client.chat.completions.create(
model="prod-chat",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain BYOK security in one paragraph."},
],
)
print(response.choices[0].message.content)
Notice what's missing: there's no provider key in this code. There's no sk- or anthropic- token. If this file leaks to GitHub, the attacker gets a gateway key that can be revoked in seconds, not a provider key that gives them direct access to your OpenAI account.
The Gateway Key Lifecycle
Gateway keys are designed to be ephemeral and revocable. When you create one via POST /api/keys, the plaintext is returned exactly once. After that, only the SHA-256 hash is stored in Supabase. There's no "forgot my key" recovery—you create a new one and rotate.
This is a feature, not a bug. It means a key that's been exposed in logs, in a leaked .env file, or in a commit history is worthless to anyone who finds it later. You can't look up the plaintext, and neither can we.
ModelPlane also distinguishes between Private and Shared keys. Private keys belong to an individual member and are scoped to their permissions. Shared keys are created by org owners or admins and can be used across a workspace. This lets you give a CI/CD pipeline its own key, then revoke it without affecting your developers' keys.
Why This Matters for Teams
If you're a solo developer, the security model matters because it's one less thing to worry about. If you're an engineering lead at a company with 50 engineers, it's a compliance requirement.
The multi-tenant design means each workspace gets its own isolated credential store. Your team's keys are encrypted with your workspace's derived keys. Another team on the same ModelPlane instance—even another workspace in your own organization—cannot access them.
This is the difference between a gateway that's bolted onto your stack and one that's designed for multi-tenancy from the ground up. The tenancy model (User/Workspace/Billing Account) is baked into every resource: API keys, backends, model groups, usage logs. When you audit who has access to what, the answer is clear.
The Honest Tradeoffs
No security model is perfect, and we're not going to pretend otherwise.
First, BYOK means you're responsible for your provider keys. If you upload a key to ModelPlane and then leak it elsewhere, that's on you. We encrypt it at rest, but we can't protect you from your own .env file hygiene.
Second, there's a trust boundary you're accepting: you're trusting ModelPlane to handle your keys correctly. We've designed the system so that a compromise of our infrastructure doesn't expose your plaintext keys, but you're still relying on our encryption implementation being correct.
Third, the latency/accuracy tradeoff in billing means usage accounting is eventually consistent. That's a billing concern, not a security one, but it's worth knowing that the balance gate is a pre-request snapshot, and deductions happen asynchronously.
The Bottom Line
The provider key is the crown jewel of your LLM infrastructure. It's the credential that can read your prompts, generate your responses, and spend your money. Treating it like a regular API key—hardcoded in services, shared across teams, stored in plaintext—is a risk that will eventually materialize.
ModelPlane's BYOK model gives you a clean separation: gateway keys for your applications, encrypted backend credentials for your providers, and a routing layer that never confuses the two. The gateway key is auth; the backend key is access. Keeping those separate is the foundation of a secure LLM stack.
Start free with $5 in credits—no card required. Add your first backend, create a model group, and see how the security model holds up under real traffic. Your provider keys will thank you.
This post is part of our series on building production-grade LLM infrastructure. Here's what we've covered and what's coming:
- One endpoint, every model — why we built ModelPlane and the core abstractions of model routing
- Routing strategies, explained — fallback, load-balance, and conditional routing patterns
- Model groups — the one abstraction that decouples your app from providers
- Bring your own keys, safely — the BYOK security model (you are here)
- High availability for LLM apps — a fallback playbook for provider outages
- Stop paying twice — route your coding-plan quota into production
- Price-aware routing — cut your LLM bill without changing models
- Credits, usage & billing, explained — one transparent bill across all providers
- 1600+ models, one API — the ModelPlane provider catalog
- One gateway, two regions — routing to global and China models
- ModelPlane for teams — orgs, workspaces & shared keys
- Meet the assistant — an AI helper that lives in your gateway dashboard
- Inside the ModelPlane routing engine — how a single request flows from auth to upstream
- One thinking parameter, every model — unified reasoning across providers
- The system prompt belongs at the router — per-model-group system-prompt injection
Top comments (0)