Claude Agent SDK
The Claude Agent SDK is
Claude Code as a library: built-in Read, Edit, Bash, Grep. Two facts
about it decide how you should hand it credentials.
First, it reads ANTHROPIC_API_KEY from the environment of the process that
runs your agent, and does not load .env files — so process wrapping isn't a
workaround here, it's the documented path.
Second, this agent reads files and runs commands. A .env sitting in the working
directory is not inert configuration; it is inside the agent's reach, along with
git log -p and docker compose config. The safest place for a credential in
this framework is an environment variable in the child process and nowhere else.
Start here if seekrit is new: three
commands put the keys in
an environment, mint a token bound to it, and export SEEKRIT_TOKEN. A token
reads everything in its
environment, so
there is no per-key or per-framework setup to do before any of the below.
1. Wrap the process
seekrit run -- python agent.py
seekrit run -- npx tsx agent.ts
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage
async def main():
async for message in query(
prompt="Review utils.py for bugs that would cause crashes. Fix any issues you find.",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Edit", "Glob"],
permission_mode="acceptEdits",
),
):
if isinstance(message, AssistantMessage):
print(message.content)
asyncio.run(main())
Install is pip install claude-agent-sdk or
npm install @anthropic-ai/claude-agent-sdk.
2. Resolve in code
import os
import seekrit
seekrit.Client().into_env() # before the first query()
assert "ANTHROPIC_API_KEY" in os.environ
Useful when the agent runs inside a server that has no wrapper — a queue worker or a web handler. Resolve once at boot, not per request.
3. Never hold the key
The Agent SDK is built on the same foundation as Claude Code and loads the same settings, so it takes the same gateway variables and the proxy needs no code change:
export ANTHROPIC_BASE_URL=http://127.0.0.1:8080/anthropic
export ANTHROPIC_AUTH_TOKEN='{{seekrit:ANTHROPIC_API_KEY}}'
# seekrit-proxy.toml
listen = "127.0.0.1:8080"
[[route]]
prefix = "/anthropic"
upstream = "https://api.anthropic.com"
allow = ["ANTHROPIC_API_KEY"]
methods = ["POST"]
paths = ["/v1/messages"]
ANTHROPIC_AUTH_TOKEN sends the value as Authorization: Bearer …;
ANTHROPIC_API_KEY sends it as x-api-key. The proxy substitutes placeholders
in either header, so pick whichever your setup already uses — but use exactly
one, since a credential in the variable your gateway doesn't read fails with a
401 that looks like a seekrit problem and isn't.
Why there is no in-process option here
The other pages in this section offer a
fetch/httpx shim as a middle rung
between an environment variable and the proxy. It does not apply here: the
Claude Agent SDK drives a subprocess rather than calling the API from your
process, so there is no client of yours to hand a transport to. Environment or
proxy are the two options.
Gotchas
- The agent can reach whatever the process can. With
Bashallowed, an agent that wants a credential can go looking for one. That is the argument for keeping plaintext out of the filesystem entirely rather than for tighteningallowed_tools— though tightening them is also worth doing. - Multi-tenant needs more than a credential boundary. Anthropic's own docs
warn against relying on default
query()options for tenant isolation: managed policy settings,~/.claude.json, and per-directory auto memory are read regardless ofsettingSources. For a multi-tenant agent, give each tenant its own filesystem, passsettingSources: []andCLAUDE_CODE_DISABLE_AUTO_MEMORY=1, and resolve that tenant's secrets per run rather than injecting every tenant's into one process. - A wrapped process is not a sandboxed one.
seekrit runkeeps values off disk; it does not stop the agent reading the environment it was given. If the agent must not be able to read the key at all, that is shape 3. - Authenticate with
seekrit login, not an exportedSEEKRIT_TOKEN.seekrit runinjects the resolved values on top of the environment it inherits, so a token exported in your shell is visible to the child too — and a token is a decryption credential, so an agent holding it can resolve everything that token grants, not just what you injected.seekrit loginstores it in~/.config/seekrit/config.jsoninstead, leaving nothing in the environment for the agent to find.