seekrit
Docs/Claude Managed Agents

Sync to Claude Managed Agents

A Claude Managed Agents session runs in a sandbox Anthropic hosts, so there is no process of yours to inject into. What Anthropic offers instead is a vault: you store a credential once, attach the vault to a session, and the agent's sandbox sees only an opaque placeholder in the environment variable. Anthropic swaps the real value in as a request leaves the sandbox — and only on requests to the hosts you named. A binding owns the environment-variable credentials seekrit writes into one vault.

At a glance
What seekrit writesenvironment_variable credentials in one vault
Addressed byThe vault ID, vlt_…
Connection carriesNothing — an API key plus the vault ID is the whole address
Key permissionAn Anthropic API key from the vault's own workspace
Takes effectNew sessions that attach the vault; running sessions pick up a rotation without restarting
Value visibilityWrite-only — and never visible inside the agent's sandbox, only at egress

New to sync? Read Third-party sync first — the decryption grant, name mapping, deletions, and failure handling are the same on every destination.

note

The workload never holds the value either. Like every sync destination, seekrit's servers decrypt the environment to push it. Unlike most — OpenAI Vault is the other — the thing that uses the value never receives it: code the agent runs — including code a prompt injection writes — can read the variable and gets a placeholder. That is why a binding here must say where each value may be used. The host list is the boundary, and seekrit writes it on every push.

1. Create an API key

In the Claude Console, open Settings → API keys and create a key in the workspace that owns the vault. Vaults are workspace-scoped: a key from another workspace gets a 404 for a vault that exists.

printf '%s' "$ANTHROPIC_API_KEY" \
  | seekrit sync connect --name acme-claude --provider claude-managed-agents

That is the whole connection. There is nothing to scope: the key belongs to one workspace, and a vault ID is globally unique.

note

Claude Platform on AWS is not covered yet. It runs Managed Agents too, but authenticates with AWS SigV4 rather than an API key, so it needs its own connection type. Bedrock, Vertex AI, and Foundry do not offer Managed Agents.

2. Bind an environment

Create the vault first if you do not have one — in the Console, or with ant beta:vaults create. Then say which hosts each secret may be used on:

seekrit sync enable --connection acme-claude --provider claude-managed-agents \
  --vault vlt_01ABCdef \
  --allowed-hosts-for STRIPE_SECRET_KEY=api.stripe.com \
  --allowed-hosts-for OPENAI_API_KEY=api.openai.com \
  --allowed-hosts-for GITHUB_TOKEN=api.github.com,uploads.github.com \
  --app support-agent --env production --acknowledge-decryption

--vault is the vlt_… ID, not the vault's display name. --app is the seekrit application the environment belongs to. seekrit sync verify takes the same flags and checks that the key can read the vault and that it is not archived.

3. Attach the vault to sessions

A vault does nothing until a session names it. Pass vault_ids when you create the session:

curl https://api.anthropic.com/v1/sessions \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  -H "content-type: application/json" \
  -d '{"agent": "agent_…", "environment_id": "env_…", "vault_ids": ["vlt_01ABCdef"]}'

Inside the session, the agent reads $STRIPE_SECRET_KEY like any environment variable and sends it as usual. vault_ids can only be set when a session is created, not added to a running one.

Where each value may be used

Every name a binding pushes needs a host rule, and there are two places to put one:

FlagDashboard fieldApplies to
--allowed-hosts-for NAME=hosts (repeatable)Hosts per secret, one NAME = hosts per lineThat one name — by its name in the vault, after any prefix or rename
--allowed-hosts hostsAllowed hostsEvery name without its own rule

A name covered by neither is not pushed. It fails with a reason that names it, rather than going out unrestricted — so a secret someone adds to the environment next month cannot quietly widen where values flow. If seekrit had already pushed that name under a rule that no longer covers it, the credential it wrote is removed from the vault rather than left live under the old rule; the agent loses that key until you add a rule.

Per-name rules are the better tool: a Stripe key belongs on api.stripe.com and nowhere else, and a shared list lets every key in it be substituted on every host in it.

A host is a bare hostname (api.stripe.com), a wildcard (*.stripe.com), or an IPv4 address — up to 16 per rule. URLs, ports, paths, and IPv6 addresses are refused. A wildcard matches every subdomain but not the domain itself, so a service reached at its apex needs both stripe.com and *.stripe.com.

The literal unrestricted lets a value be substituted on any host the agent's environment allows. It exists for tools that reach hosts you cannot list in advance; it is never what an empty field means.

caution

The environment must allow the host too. A credential's host list decides which requests get the value, not which requests are allowed out. The Managed Agents environment the session runs in has its own network policy, and a host missing from it blocks the request whatever the credential says. seekrit cannot see that policy — check it when a substituted request fails.

Headers, body, or both

--injection-location (the dashboard's Substitute into) picks which parts of an outbound request the placeholder is swapped in:

ValueSubstituted inUse for
header (default)Request headersAlmost every API key — Authorization, x-api-key
bodyRequest bodyA client that sends the secret in a form or JSON body
bothHeaders and bodyBoth of the above in one binding

header is the default because request bodies are often built from content the agent is working with, which makes the body the broader surface. A placeholder in a location that is off is sent as-is, and the service rejects it. The setting applies to the whole binding; to give one secret body injection, bind it separately with --include.

How a push behaves

  • seekrit lists the vault, then writes. The vault API keeps creating and updating apart, so each run reads the vault's credential names once, updates the ones that exist, and creates the rest. Values are never read — the API does not return them.
  • Every run writes every name. With no way to compare values, seekrit sends each one. That is cheap here: nothing restarts, and running sessions pick up the new value on their own.
  • Host rules are rewritten every push. A host list or injection setting edited in the Console reverts to the binding's on the next run — the binding is the source of truth for where a value may go, as it is for the value. Editing a binding's rules keeps its history, so names it wrote are still removed later; moving it to another vault starts that history afresh.
  • The vault can be shared. seekrit touches only environment-variable credentials whose names this binding pushes or pushed. MCP credentials beside them are never read or changed. An environment-variable credential that already has a name the binding pushes is taken over, as on every destination.
  • Removals run first, so renaming a secret in a full vault frees the old slot before the new name needs it.
  • Credentials are marked with managed-by: seekrit metadata, so the Console says who owns them; ones seekrit creates are also named NAME · managed by seekrit, while a credential it takes over keeps its display name. Nothing derived from a value is ever written there. The marker is also how seekrit tells its own credentials from yours: it only ever removes a name's credential for a lost host rule when the marker is on it.
  • A value Anthropic echoes back in an error is scrubbed before the reason is stored on the run row.
caution

A vault holds at most 20 credentials, MCP credentials included. A create refused at the limit says so in the run ledger. Split a larger environment across two vaults — two bindings with --include/--exclude — and attach both to the session.

Name and client rules

Substitution happens as a request leaves the sandbox, never inside it. That has three consequences:

  • Variables the sandbox reads itself are refused. PATH, HOME, PWD, SHELL, USER, TMPDIR, LD_PRELOAD, LD_LIBRARY_PATH, the proxy variables (HTTPS_PROXY and friends, either case), and the CA-bundle variables (SSL_CERT_FILE, NODE_EXTRA_CA_CERTS, …) are never sent to a host, so a placeholder there could only break the sandbox. seekrit reports the refusal against that name alone.
  • Clients that inspect the secret locally see the placeholder. A CLI that checks a key's format at startup may reject it, and anything that signs with the secret — AWS SigV4 is the common case — produces a bad signature. Vault credentials work for clients that send the value verbatim.
  • An exchange hands back a real token. If a client uses the stored secret to fetch a session token (an OAuth client-credentials grant, say), the returned token reaches the sandbox unredacted. Do the exchange elsewhere and sync the resulting token instead.

Self-hosted sandboxes

Anthropic does not support environment-variable vault credentials in self-hosted Managed Agents environments — substitution needs Anthropic's own egress. On a self-hosted worker, keep decryption on your side instead: run the agent under seekrit run, or put seekrit-proxy in front of it so it holds placeholders the proxy fills in. The Sprites guide walks through one such setup.

Troubleshooting

SymptomCauseFix
"rejected the API key"The key was revoked, or pasted wrongCreate a new one under Settings → API keys and re-create the connection
"no vault … visible to this API key" (404)The display name in the ID slot, a deleted vault, or a key from another workspaceUse the vlt_… ID, and create the key in the vault's workspace
403 on the vaultThe organization does not have Managed Agents accessCheck access in the Console
"was archived"Archiving is permanent and purges the vault's credentialsCreate a new vault and edit the binding's --vault
One name failed with "no allowed hosts"The name has no per-name rule and there is no default. If seekrit had pushed it before, its credential was removedAdd --allowed-hosts-for NAME=…, set --allowed-hosts, or --exclude the name
One name failed, "read inside the sandbox"The mapped name is one the sandbox uses itselfRename it, or fix the prefix
A create failed with "holds 20 credentials"The vault is fullSplit the environment across two vaults
The agent's request is rejected with the placeholder visibleThe host is not in the credential's rule, or the injection location is off for where the client sends itAdd the host, or switch to body/both
The agent's request never leaves the sandboxThe environment's network policy blocks the hostAllow it on the environment

See also