OpenClaw Secret Allowlists — Limit What Each Agent Can Read
An OpenClaw agent rarely needs every credential in your project. Here's how secret allowlists turn 'agent has read access' into 'agent has read access to exactly these three keys, and nothing else'.
The most common mistake in agent credential design is over-broad scope. The token has access to the whole project — the agent only ever reads two secrets. The other thirty are blast radius for a token compromise.
This post is about closing that gap with a feature that's small to set up and impossible to bypass: secret allowlists.
The default scope problem
When you create a typical machine credential — a CLI token, a CI/CD secret, a service account key — the implicit scope is everything. The token can read every secret in the project (or the org, or the cloud account, depending on how generous the default is).
That's fine for human users with judgment. It's bad for autonomous agents, because:
- Agents don't have judgment. If a prompt injection tells the agent to read
STRIPE_API_KEYand exfiltrate it, the agent might. The fact that the agent shouldn't isn't a defence; the fact that it can't is. - Compromise blast radius matches scope. If the agent token leaks, the attacker gets every secret the agent could have read. If the agent only had access to two secrets, the attacker gets two secrets.
- Audit gets noisier. Without an allowlist, every "agent read X" event is normal. With an allowlist, every "agent attempted to read Y" is a signal.
What a secret allowlist actually is
In secr, every agent identity has an optional secretAllowlist — a JSON array of secret keys the agent is permitted to read. If the field is null or empty, the agent is unrestricted (within its project/environment scope). If the field has values, every read path checks it.
The check happens in seven places, including:
GET /v1/secrets/:org/:project/:env— the bulk read returns only allowlisted keys, server-sideGET .../keys— the no-values listing also filters, so the agent can't even discover what other secret names existGET .../versions/:key— version history is gated; reading the history of a non-allowlisted secret returns 403PUT /v1/secrets/...— write of a non-allowlisted key returns 403 (forread_writeagents)POST .../bulk,.../bulk-delete,.../rollback/:key,.../promote— same thing across every mutation endpoint
This is the "server-enforced" claim. The SDK doesn't decide what's allowed; the API does, on every request, against the row in the database. There's no client trust boundary.
Setting an allowlist
Three places you can do it:
CLI
secr agent create slackbot \
--project support \
--env production \
--allowlist SLACK_BOT_TOKEN,SLACK_SIGNING_SECRET
Dashboard
The Create Agent modal has a multi-select against the project's known secret keys. You can also type a key that doesn't exist yet — the allowlist is on key names, not on existence.
API
await api.post(`/agents/${orgId}`, {
name: "slackbot",
permission: "read",
projectId,
environmentId,
secretAllowlist: ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"],
});
You can also update an allowlist later with PATCH /agents/:orgId/:agentId. Setting it to null removes the restriction; setting it to [] is equivalent to "deny all reads."
How the plugin handles it
With the OpenClaw plugin (@secr/openclaw-plugin) installed, the agent calls secr.get("KEY") or secr.materialize via MCP. The plugin forwards the request to the secr API, the API checks the allowlist, and either returns the value or refuses:
agent → secr.get({ key: "SLACK_BOT_TOKEN" }) → ✓ returns value
agent → secr.get({ key: "STRIPE_API_KEY" }) → ✗ 403 forbidden (not in allowlist)
There's no "fetch then filter on the client" — the server has already filtered. There's no way for the client to bypass it because the API never returns values for keys outside the allowlist.
If you're calling the broker directly (advanced / non-plugin path), the same allowlist is enforced:
import { OpenClawSecretBroker, loadIdentity } from "@secr/openclaw";
const broker = OpenClawSecretBroker.fromIdentity(parsed);
await broker.materializeEnv();
// process.env.SLACK_BOT_TOKEN ✓
// process.env.SLACK_SIGNING_SECRET ✓
// process.env.STRIPE_API_KEY → undefined (not in allowlist; not fetched)
Picking a good allowlist
A few practical rules from looking at OpenClaw deployments:
Start tight, expand on demand
It's easier to add a secret to an allowlist than to remove one. When you create the agent, list only the secrets the first version of the agent code uses. When you add a new feature that needs another credential, update the allowlist explicitly. The friction is the feature.
Match the persona
OpenClaw agents are usually built around a persona — SlackBot, SupportTriage, DevopsRunbook. The allowlist should match: only the credentials that persona needs. If SlackBot is asking for STRIPE_API_KEY, something is off. Either the persona is wrong, or the agent is being asked to do something it shouldn't.
Cross-environment is usually wrong
A common temptation: "we'll use the same agent for dev and prod, just with different secrets." Don't. Create one agent per environment, scope each to its environment, and give each its own allowlist. Otherwise an attacker who steals the dev token can ask the agent for prod secrets.
Audit unscoped agents quarterly
The nhiBaselines table records what each agent actually reads over time. Cross-reference that with the allowlist (or lack of one). Any agent reading the same 4 keys for 90 days should have its allowlist set to those 4 keys and nothing else.
The compliance report at /dashboard/nhi/compliance flags this automatically — including a specific posture rule for OpenClaw agents that don't have a project, environment, or allowlist.
What about machine tokens?
Machine tokens (secr_mt_*) are scoped to project + environment but don't currently support a secret-key allowlist. If you need allowlisting for a non-agent credential, use an agent identity instead — the only practical difference is the prefix and the session-exchange flow.
The 30-second version
# Without allowlist: this agent can read every secret in production
secr agent create slackbot --project support --env production
# With allowlist: this agent can read exactly two secrets
secr agent create slackbot --project support --env production \
--allowlist SLACK_BOT_TOKEN,SLACK_SIGNING_SECRET
The first version is fine until something breaks. The second version is fine when something breaks, because the blast radius is two known credentials instead of "everything that lives in production."
The fix is one flag. The improvement is a different category of system.
Read next
- Getting started with OpenClaw and secr — the 5-minute plugin install
- OpenClaw MCP approval queues — human approval for tool calls, not just secret reads
- Approval webhooks for OpenClaw agents — Slack, PagerDuty, Discord
- Approve OpenClaw tool calls from Telegram
- The /agents pillar — threat model + every integration
- The OpenClaw plugin — install reference, three hooks, ClawHub listing
The plugin is on ClawHub and free for 1 agent. Start here →
Ready to get started?
Stop sharing secrets over Slack. Get set up in under two minutes.
Create your account