Veil Keys Docs

Policies & approvals

Scoping which secrets an agent can reach is half the job. The other half is controlling how and when each one may be used — and making sure a secret can never be pointed somewhere it doesn’t belong. Veil gives every credential its own policy, and binds every credential to a single domain as a hard boundary. Together they let you hand an agent a live production key and still sleep at night.

Policies: allow, require approval, deny

Every credential carries one of three policies. It applies on every use — through the agent broker, the database proxy, or SSH signing.

  • allow (default) — the credential is used immediately whenever a caller has permission.
  • require_approval — each use pauses for a human. An approval request is created and an admin must approve it; the agent receives a “pending approval” message in the meantime. Approvals expire after about 15 minutes, so a stale request can’t be rubber-stamped later.
  • deny — the credential is always refused, no matter who asks. A fast kill-switch for a single secret without revoking a whole token.
claude code
agent: call_api("prod-stripe", "/v1/refunds", method="POST")
→ pending approval · awaiting an admin in Veil
(request expires in 15m)
✓ approved by alex@acme.com · 200 OK

This is the human-in-the-loop you want for the dangerous verbs: refunds, deletes, anything against production. The agent proposes; a person approves; Veil executes and logs it.

Domain binding: a wall, not a label

Every credential is bound to exactly one host. A key bound to api.stripe.com is physically refused against any other host — the binding is enforced at the moment of use, not advertised as a convention.

AI agent
tries another host
call_api(path only)
Veil Keys
refuses · wrong host
blocked
evil.example
never contacted
Through the broker the agent can only supply a path — never a host — so a stripe key can only ever reach Stripe.

This is what makes prompt injection a non-event here. Even if an attacker convinces the agent to exfiltrate a key, there’s nowhere to send it: through call_api the agent supplies only a path, the host comes from the credential’s binding, and private or loopback hosts are rejected outright.

Putting it together

A practical production setup:

  1. Read-only keys (reporting APIs, the database proxy) → allow, so the agent is fast and autonomous where the stakes are low.
  2. Write-capable production keys (payments, deletes) → require_approval, so a human signs off on every mutation.
  3. A key you’re retiring or investigating → deny, an instant per-credential off switch.

Layer these on top of access control (which secrets a token can touch at all) and scoping (which workspaces and permissions a token holds) for defense in depth.