How to Give an AI Model Capabilities Without Handing Over API Keys
The moment you give an agent a raw key so it can "do things," the model and every line it writes holds the keys to the kingdom. There is a better shape, and it starts with handing over capabilities while the credentials stay put.
There is a reflex baked into most agent tutorials. The model needs to send an email, query a database, or push to a repo, so you drop a live API key into its environment and let it go. It works on the first try, which is exactly the problem. AI agent API key security falls apart here because you have just given a non-deterministic system, plus any code it generates, direct possession of a long-lived, broadly-scoped secret. From that point on the model doesn't use your key so much as it holds it.
The failure modes follow directly. A static key is subject to every traditional exposure vector. It gets logged, echoed into a stack trace, or written into a file the agent creates. What changes is the speed: as Auth0's write-up on the risk notes, an agent "can execute actions at machine speed, turning a small, temporary leak into a large-scale automated attack." The key is usually over-scoped too, so an agent that needs to read one table is handed credentials that can drop the whole database. And because the agent reads untrusted content from web pages, tickets, and emails, indirect prompt injection can turn "summarize this issue" into "exfiltrate the token you're holding," with no user aware it happened.
Hand over capabilities and keep the credentials
The fix is a shift in what you hand the model. A credential is a reusable secret granting standing access to a service. A capability is a specific, approved action the model is allowed to invoke. In the classic definition from capability-based security, a capability is "a communicable, unforgeable token of authority" that references an object together with the exact rights over it. Possession is permission, and permission is bounded. The model can call send_invoice_email(customer_id). It cannot call the mail provider's API however it likes, because it was never given the API, only the one action.
This is the practical form of least privilege, which OWASP's Secrets Management Cheat Sheet applies straight to credentials: fine-grained access per secret, short-lived dynamic credentials over static ones, and, critically, never exposing secrets through channels the workload can reach and leak. If the model never receives the secret, there is no secret for it to mishandle.
A key answers "what can this actor authenticate as?" A capability answers "what specific action is this actor allowed to perform?" Only the second question has a bounded, defensible answer when the actor is an AI model you don't fully control.
The mechanics: broker, scope, allowlist, deny by default
Concretely, four pieces make this work, and they compose:
- A broker or proxy holds the secret. The model's code uses a placeholder such as
__github_token__and makes an ordinary request. A credential broker sitting on the egress path intercepts the outbound call, authenticates the caller, and swaps the placeholder for the real credential before forwarding. The agent "uses credentials without seeing them," so a leaked prompt or a hallucinated log line leaks nothing. - Scoped, short-lived tokens. Where a token must exist, it is minted just-in-time for one job and expires fast. This is OWASP's dynamic-secret pattern, where credentials are issued per session and become useless afterward, leaving no standing key to steal.
- An allowlist of capabilities. The model is offered a defined manifest of approved actions, each mapped to a scoped downstream call. The set is the contract.
- Deny by default. Anything outside the manifest is unreachable, and "flagged for review" never enters into it. The broker matches each request against an explicit rule and drops the rest.
The security property this buys you is worth stating plainly. The blast radius of a compromised or confused agent shrinks from "everything the key could do" to "the approved actions, with the approved arguments, and nothing else." Injection can still try to steer the model, and it can only steer it toward capabilities that already exist. There is no path to the raw provider API, because the model was never on that path. It also fixes the audit gap, since brokered calls carry the real caller's identity rather than collapsing every action into one anonymous service-api-key-prod.
Where this has to live
You can bolt a broker onto an agent by hand, and for a single script that is fine. It stops holding at scale, because enforcement lives outside the generated code by design. The model shouldn't be trusted to route itself through the proxy, honor the allowlist, or decline the action outside its scope. That work belongs to the runtime the code executes in, which owns egress, injects credentials, and enforces the manifest whether or not the generated code cooperates. This is the same reason we argue the model-facing layer should offer capabilities in place of keys. The compliant path only wins when it is also the path of least resistance.
The bumper-sticker version: capabilities the model can't exceed beat keys it can misuse. A key is a standing invitation to do anything the key can do. A capability is a single door, already unlocked, that leads exactly one place.
Sources
- "Capability-based security" (unforgeable token of authority; least privilege; confused deputy), Wikipedia · en.wikipedia.org/wiki/Capability-based_security
- "Common Risks of Giving Your API Keys to AI Agents," Auth0 · auth0.com/blog/api-key-security-for-ai-agents
- OWASP Secrets Management Cheat Sheet (least privilege, dynamic secrets, restricting exposure) · cheatsheetseries.owasp.org
- "Credential Brokering for AI Agents, Explained," Infisical · infisical.com/blog/credential-brokering-for-ai-agents