8 min
AI Agent Security: Granting Access Without Giving Away the Keys
An AI agent can call APIs and query databases without ever holding a credential. We built the proxy that makes it possible and open-sourced its core.
Simon Willison has a name for the setup that gets AI agents into the most trouble: the lethal trifecta. In short, if an agent has access to private data, exposure to untrusted content, and a way to talk to the outside world, anyone who can slip it instructions can easily trick it into handing over that data. Oh joy, most coding agents check all three boxes before noon! That framing shaped how we built access for our own agents.
Just as valuable as the data to an attacker are the many credentials an agent is usually given to do its tasks. Each one of these secrets opens more doors, allowing access into services that have nothing to do with its task. Keys also get forgotten: most teams have at least one that should have been revoked. AI agents add to the number of credentials a team has to manage.
With that in mind, let's consider a realistic scenario: an agent fixing a new bug report opens a file that a stranger seeded with instructions instead of the expected reproduction steps. It follows them. Suddenly the agent has dumped its environment and tries to send every key it finds to an outside server. That works whenever an agent's keys sit in an environment variable or a config file. No one has found a reliable way to stop a model from following instructions hidden in what it reads, so we can't count on the agent to say no.
In the same scenario at TRM, the agent finds __vault_API_KEY__ and nothing else. That placeholder comes from Gatehouse, our credential proxy for AI agents. Every real secret stays outside the agent's sandbox, and Gatehouse attaches it only to requests an approved catalog allows.
This post covers how Gatehouse swaps in keys, how it stops a hijacked agent, how it knows who's calling when the agent holds nothing, and where we're taking it next. We built Gatehouse for our own agents and open-sourced it so you can build the same for yours.

Key takeaways
- Agents at TRM call APIs, query databases, and work in GitHub while holding only placeholders, never credentials
- A reviewed catalog decides what each agent can do, and every request names who or what started it
- Gatehouse is open source, and we're building toward a fresh credential for every call, so a leaked key is useless the moment it's used
Giving an agent access
There are three ways to let an agent use a credential:
- Hand it the key. This is how most agents run today. One injected instruction or one careless transcript log leaks it, and revoking it means finding every copy.
- Hand it a short-lived key. The leak expires sooner, but the agent still holds the key, and an attacker needs only seconds.
- Hand it nothing. The agent sends its requests, and something it can't reach adds the key on the way out. A hijacked agent has nothing to leak.
We built the third.

The swap happens outside the sandbox
The agent calls an API exactly as it would with a real key, but sends a placeholder:
# The sandbox's proxy points at Gatehouse, and the sandbox trusts Gatehouse's certificate
curl <https://api.example.test/v1/search> -H "X-Api-Key: __vault_API_KEY__"
Gatehouse sits between the sandbox and everything it calls. It verifies who is calling, checks the request against its catalog, and then reads the real key from a secrets manager before swapping it in. This is one API's whole entry:
- name: catalog-search
host: api.example.test
pathPrefixes: [/v1/search]
methods: [GET]
header: X-Api-Key
placeholder: __vault_API_KEY__
key: {mount: gatehouse, path: vendors/catalog-search, field: key}
pools: [research-agents]
Adding a destination is one entry to the catalog, in one pull request, and Gatehouse picks it up within 30 seconds. The agent can't widen an entry; only the next reviewed change can.
How one request moves through Gatehouse
When that curl arrives, Gatehouse runs seven steps in order. A failure in any of the first five stops the request before anything reaches the destination, and the sixth stops a reply that carries the key:
- Verify the caller
- Check the request
- Match the catalog and fetch the key
- Swap in the key
- Log the attempt
- Forward the request and screen the reply
- Log the outcome before replying

Now replay it with a hijacked agent that knows about the placeholder. It sends the placeholder to its own server, and the third step finds no catalog entry for that host: blocked. Or it asks an approved API to echo the key back, and the sixth step finds the key in the reply as plain text or in any common encoding: blocked. It tries a redirect: blocked. Gatehouse never forwards a credential to a new host.

Gatehouse guards the key, and that's all it guards. Whatever an approved API sends back, the agent can read, so each key should still reach only what the agent needs. In Security, we build layers so one failure doesn't become a breach. The name fits: a gatehouse is the guarded gate a castle routes everyone through.
When something around it breaks, it fails closed
A credential proxy that fails open is worse than not having one, so when Gatehouse can't do its job safely, it refuses:
- The secrets manager is unreachable. A key read in the last minute keeps working for five more minutes, then Gatehouse refuses every call.
- The audit record can't be written. Gatehouse returns an error and sends nothing so that no request goes out unrecorded.
- The connection drops mid-request. Gatehouse logs the outcome as unknown and never replays it since the first attempt may have already changed something.
- A temporary database user may have been left behind. Gatehouse asks the secrets manager to revoke it and stops connecting to that database only if the revoke fails.
Our first version closed the database until a person cleared it, which made a person the bottleneck for every agent that needed it. Now Gatehouse reopens the database by itself once a later check comes back clean. Everything else raises an alert, so we hear about a problem before agents do.
Databases and browsers need their own brokers
Many databases want a login instead of an API key. So Gatehouse has the secrets manager create a fresh database user for each connection and removes it when the connection closes. The database accepts connections only from Gatehouse; otherwise an agent could change that user's password and connect on its own.
Browsers are the hardest case, because a signed-in session can do anything its user can. So the agent's browser never signs in. It opens the app with a login state that holds only a placeholder. When the app calls its API, Gatehouse signs in as a service account, never as a person, and swaps in the real token. Cookies never pass in either direction.

Each of these is a broker, the trusted piece that logs in on the agent's behalf, and all of them follow one rule: sign-in happens where the agent can't see or change it.
Who is calling, if the agent holds nothing?
If nothing inside the sandbox holds a secret, the agent has nothing to prove who it is. If the agent could show it, an attacker in control of it could copy it too. So the sandbox never speaks for itself. Something outside has to vouch for it. At TRM that's any of three things:
- Kubernetes workers. Gatehouse checks the calling Pod, its owner, and the exact image it runs.
- A shared sandbox cluster. A proxy outside the sandboxes identifies which sandbox is calling and names the person who started it.
- Cursor's self-hosted workers. Cursor signs a short-lived token naming the person who started the task, and Gatehouse accepts it only from the worker running that task.

Gatehouse's first version granted access per pool of workers, so the audit could say only which pool called. Now it names who or what started the work, on every request, even though the agent never held anything that says who it is. We didn't even have to invent a new identity for this: Cursor and Kubernetes already sign standard OpenID Connect (OIDC) tokens, so Gatehouse just checks each one against the issuer's published keys. Gatehouse is as much an identity verification system as a credential proxy.
Where it runs today
Right now Gatehouse serves two agent platforms at TRM: Cursor's self-hosted workers, which use both the Pod check and Cursor's token, and a shared sandbox cluster where Claude Code runs. Through Gatehouse, agents query databases, call our model gateway, and work in GitHub with no token of their own. They push only to their own branches, and the catalog lists what each repository allows, such as opening or commenting on a pull request.

It also runs itself. A restart lets requests under way finish. Gatehouse sets a bad catalog entry aside, names it in the log, and loads the rest. When a bad deploy is rolled back, a scheduled job replaces any Gatehouse instance left stuck on it within minutes.
Where we're taking Gatehouse
When each agent carries keys, risk grows with every agent you add. With Gatehouse in front, it doesn't. Even the millionth agent adds no new secrets to itself. Humans decide in reviewed code what agents may do, and nothing an agent reads can change it. The same pattern could work for anything with a login: caches, queues, internal admin tools. Each is just one more broker, and once it exists, any team can use it by adding a catalog entry.
Every request already passes through one place, so a credential is one thing to change instead of hundreds of copies to chase. Rotate a key once in the secrets manager and every agent using it picks up the new one within a minute, with no redeploy. Now take it to the extreme: every call could carry a freshly minted credential that's useless the moment it's used. A leaked credential would no longer be a P0 incident with assumed breach. Engineers would find the problem and stop it without tracking down a service owner to rotate a credential at 3:00am.
That's what we're building toward for agents and for any other system that runs on credentials at TRM.
Gatehouse starts from Infisical's Agent Vault, an open-source project whose contributors built the foundation. We forked it because we needed more than HTTP and a prebuilt key store. Our fork adds a database broker, browser sign-in, GitHub access scoped per repository, identity from Cursor and Kubernetes, broader support for third-party secrets managers, a reviewed catalog, and a signed audit trail. An earlier post explained why agents should never hold credentials; Gatehouse shows how.
You can watch it work in one container on your own machine. It brings test copies of a secrets manager and a database, has no network access, and proves the core behavior: placeholder swaps, refusals, the audit trail, and rotating and deleting keys mid-test. The credential proxy guide lists what a deployment needs.
git clone <https://github.com/trmlabs/trm-gatehouse> && cd trm-gatehouse
docker build -f examples/credential-proxy/Dockerfile -t credential-proxy-verification .
docker run --rm --network none credential-proxy-verification
If you run any sort of automatic agent workflow, or even want to secure existing scripted automations, give Gatehouse a try today. It's open source at github.com/trmlabs/trm-gatehouse, and we'd love issues, fixes, and pull requests on the fork. And if you'd rather build this kind of secure infrastructure for a living, come build with us at TRM.




