Keep a token from the agent
An agent that can read a token can send it anywhere a prompt injection tells it to. A brokered secret stays with OpenMoat: programs the agent runs hold a placeholder such as moat-secret:gh:placeholder, and the real value is put into requests to one host only.
GitHub in one command
Section titled “GitHub in one command”-
Add the secret, in your own terminal
Terminal window moat secret add ghIt brokers
GITHUB_TOKENforapi.github.com, shows the change to your policy, asksApply? [y/N], then updates the agent sandbox. -
Start the agent from a shell that has the token
The value is read from the environment the agent starts in:
Terminal window export GITHUB_TOKEN="$(gh auth token)" # in your terminal, not in the agentclaude -
Use GitHub from the agent
ghand other tools inside the agent see the placeholder inGITHUB_TOKEN, and their requests toapi.github.comcarry the real token.
Can the agent read the token?
Section titled “Can the agent read the token?”| The agent tries | With moat secret add gh |
|---|---|
echo $GITHUB_TOKEN, printenv |
denied (env-secrets, env-dump) |
gh auth token, cat ~/.config/gh/hosts.yml |
denied (secrets-paths) |
A program it runs reads GITHUB_TOKEN |
gets the placeholder, not the value |
| Sends the placeholder to another host | worthless there: the value is put in only for api.github.com (through moat proxy, such a request is also refused) |
Another service
Section titled “Another service”Add the secret, and allow its host: a secret opens nothing by itself.
moat allow --site api.stripe.commoat secret add stripe --host api.stripe.com --env STRIPE_API_KEYThe value goes in the Authorization header unless you pass --header <name>.
Make sure the agent cannot read the source
Section titled “Make sure the agent cannot read the source”A brokered value the agent can read anyway protects nothing, so moat secret add checks where the value comes from. When no rule denies it yet, it adds one, secret-<id>, in the same diff you approve:
| Source | Already denied? | What moat secret add does |
|---|---|---|
--env NAME ending in _KEY, _TOKEN, _SECRET, _PASSWORD, or starting with AWS_ |
yes, env-secrets |
nothing more |
--env NAME with another name (DATABASE_URL, STRIPE_LIVE) |
no | adds secret-<id> with an env.read deny of it |
--file PATH under ~/.ssh, ~/.aws, ~/.config/gh and the other secret folders |
yes, secrets-paths |
nothing more |
--file PATH anywhere else (~/.config/example/token) |
no | adds secret-<id> with an fs.read deny of it |
The diff shows the rule, and the line The deny rule secret-<id> keeps the agent from reading the value at its source.
Where it works today
Section titled “Where it works today”| Agent | HTTPS requests | Plain HTTP |
|---|---|---|
| Claude Code, sandboxed shell commands | yes: Claude Code’s own sandbox broker puts the value in | yes, with plain_http: true in the policy |
Codex, Cursor, moat run (through moat proxy) |
not yet: the request keeps the placeholder and the server rejects it | yes, with plain_http: true |
What this opens
- Programs the agent runs can call the API with your token
What stays protected
- The agent itself never holds the value, and cannot print or read it
- A request carrying the placeholder or the value to any other host is refused
- Its source is denied too:
moat secret addadds the rule when none covers it
Think about
- The API host can still be told to do anything the token allows: give the token the smallest scope that works