Approvals on secret reads
Supported modes: Hosted Hybrid Available in: Free Solo Teams (free for all tiers) SDK: 1.6.0+
Secrets are higher-stakes than tool calls. A leaked OPENAI_API_KEY costs more than a single bad rm. Approvals on secret reads give admins a checkpoint: when an agent fetches a credential, the admin can approve, deny, or scope the grant to a single user.
Canonical actions
| Action | Default policy | What it does |
|---|---|---|
Secrets:read <name> | Allow if explicitly granted | Agent fetches a credential value |
Secrets:list | Allow if explicitly granted | Agent enumerates secret names (no values) |
Secrets:write <name> | Hard-deny in starter policy | Admin operation; usually not for agents |
Secrets:delete <name> | Hard-deny in starter policy | Admin operation; usually not for agents |
Typical project policy:
version: '1'
rules:
- allow: Secrets:list
- deny: Secrets:read
escalate_on_deny: true
reason: 'credential reads need approval'
- deny: Secrets:write
- deny: Secrets:delete
The escalate_on_deny: true tag makes the deny approval-eligible.
SDK API
# Python (1.6.0+)
secret = client.get_secret("OPENAI_API_KEY")
secret = await client.get_secret_async("OPENAI_API_KEY")
Internally get_secret(name):
- SDK calls
client.guard("Secrets:read", resource=name). - If allow, fetch from secrets backend; return value.
- If deny and approval-eligible, call
client.request_approval(...). On approve, fetch and return. - If deny (final), raise
PolicyDeniedError.
Hybrid gate (defense in depth)
Two layers gate every secret read:
- SDK gate (fast path):
get_secret()consults the policy engine before touching the secrets backend. - Secrets backend gate (backstop): the secrets backend independently consults the policy engine on every read, catching non-SDK callers (curl, MCP server, browser ext).
A malicious SDK that bypasses its own gate still fails the backend check.
Value redaction (three layers)
The secret value NEVER appears in:
- Audit logs: only
secret_name+secret_value_sha256(64-char hex). DB CHECK constraint at the table layer rejects any row that violates this. - Telemetry: PostHog events for
Secrets:*actions are dropped entirely; no payload field passes a value-shape regex. - Errors + stack traces: explicit redaction in every exception path.
- Approver UI: drawer shows
value: <hidden, sha256: ab12...cd34>. Never the value itself.
If any layer attempts to log a value-shaped string for a Secrets:* action, the SDK raises E1309 SecretValueLeakInPayload and the request fails closed.
Approval drawer for secrets
Same drawer pattern as tool-call approvals, with three differences:
- Red-bordered "SECRET ACCESS" banner at the top.
- Secret name visible; value sha256 fingerprint visible; value never visible.
- "Last N reads of this secret" inline list for anomaly detection.
The approver verifies they're approving the right secret by matching the sha256 against the expected value, without ever seeing the value itself.
Default scope: user-scoped (opposite of tool-call default)
Tool-call Approve forever defaults to project-wide. Secret Approve forever defaults to user-scoped (condition.user IN [requestor_email]).
Reasoning: a long-lived secret rule for the whole project undermines the vault. Admin must explicitly remove the condition.user clause via "Edit before applying" to widen.
Rotation invalidates grants
When the secret value is rotated, a trigger on secrets.updated_at automatically sets revoked_at = NOW() on all hitl_grants rows for that secret_name. Reasoning: a grant was issued against a specific value; rotation invalidates it; next fetch needs a new approval.
When NOT to require approval on a secret
- Public config (e.g.
PUBLIC_ANALYTICS_KEY). Approval friction with no benefit. - Build-time-only credentials read once at boot. Approval would block boot.
- Mass-fetch helpers that pull dozens of secrets in a loop. Per-fetch approval would be unworkable.
Tag the secrets you'd lose sleep over if they leaked. Leave routine config un-tagged.
See also
- Approval Workflow. The parent concept
- Multi-user keys. Identity for shared API keys
- E1309 secret value leak
- E1310 secret approval required
- E1311 secret not found