Containing Client-Side AI Provider Keys: A Playbook
A containment and migration process for exposed AI provider keys, from rotation and abuse checks to a backend that enforces auth and tenant isolation.
Your AI Prototype Ships the Provider Key to the Client: A Containment Playbook
When this applies
Use this when an AI feature reaches the provider from code you don't control server-side:
- A browser extension, mobile app, or SPA that calls the LLM provider directly.
- A provider API key present in shipped client code, config, or build artifacts.
- A prototype "that just works" being promoted toward production.
- Per-user or per-team provider keys stored or entered client-side.
Severity & impact
| Signal | Severity | Blast radius |
|---|---|---|
| Live provider key present in distributed client code | SEV1 | Anyone can extract and spend on your account |
| Key scoped/limited but still client-side | SEV2 | Abuse capped but real; no per-user control |
| Prototype not yet distributed, key client-side | SEV3 | Fix before it ships |
Roles
- On-call engineer — rotates the key, stands up the backend proxy, migrates the client.
- Comms lead — for SEV1, notes the exposure window and any abuse to the account owner.
- Escalation — owner of the provider account and billing.
Triage steps
-
1
Confirm exposure
1 — Confirm the exposure. Determine whether the key is actually reachable in the client.
-
2
Assess abuse
2 — Assess abuse. Check the provider dashboard and your error monitoring for anomalous spend or usage from the exposure window. (We run error monitoring on the AI backend and verify events actually reach prod, precisely so anomalies are visible.)
-
3
Confirm no server
3 — Confirm there is no server in the path. If the client talks to the provider with no backend of yours between them, you have no auth, no tenant isolation, and no ability to rotate per user. That is the architecture to change.
- Inspect the shipped client (extension bundle, app package, network calls).
- Provider key visible in code/config/requests? yes -> SEV1, go to Mitigation A now.
- Requests hit the provider domain directly from the client? yes -> confirmed direct-call architecture.
Decision points
Mitigation menu
Ordered least → most invasive. A + B are the real fix; the rest complete it.
- A — Rotate and lock the key now: revoke the exposed key, issue a new one held only server-side, and cap limits during the window.
- B — Put your backend in the path: the client calls your backend, and the backend injects the provider key server-side. The client never holds a provider secret. This was the core of our extension migration.
- C — Add the endpoints the client needs: stand up generate, session-check, and logout on the backend, with CORS allow-listing and cross-site cookies so an authenticated client stays logged in without a provider key.
- D — Rewrite the client to call your backend: rewrite the extension/app's background, popup, and content code (or the app's API layer) to hit your endpoints instead of the provider.
- E — Enforce auth and tenant isolation: each team/user authenticates to your backend; keys and quotas are per-tenant and revocable independently.
Escalate when
Verification & recovery
- Confirm the client bundle contains no provider key and all provider calls now traverse your backend.
- Confirm the old key is revoked and the new key exists only server-side.
- Confirm auth, session-check, and logout work on real usage, and that one tenant's traffic can't use another's key.
- Confirm error and expiry paths fail gracefully (real 401, graceful provider errors), not with crashes.
Evidence & comms
Comms template:
AI key exposure — {SEV} — {status}. Exposure: provider key {client-side | rotated}. Abuse: {none | detail}. Fix: backend proxy holds the key, client migrated, auth + tenant isolation enforced. Window: {dates}.
Post-incident
Ready to Implement This Playbook?
Our team can implement these strategies for you, tailored to your specific business needs.
Schedule Consultation