Managing secrets in CrewAI crews
Frameworks
CrewAI reads OPENAI_API_KEY and OPENAI_BASE_URL from the environment. The
shortest fix for a crew that runs on a .env file is to inject the values at
launch and delete the file:
seekrit secrets import .env --app crew --env development && rm .env
seekrit run -- crewai run
The crew's code does not change. The credentials exist only in the child process. This post covers that setup and the two stronger ones, and explains why a crew needs the stronger ones more than a single agent does.
Why is a crew different from one agent?
A crew is several agents in one Python process. They share one environment.
Every agent in the crew can use every credential any tool in the crew needs.
allow_delegation moves work between agents but does not move it across a
security boundary. If a research agent and a payments agent run in the same
crew, the research agent's process holds the Stripe key.
CrewAI's own docs say never to commit API keys and to use a secrets manager. That handles where the key is stored. It does not handle which agent can use it. The setups below handle both.
Setup 1: wrap the process
seekrit run -- python crew.py
seekrit run -- crewai run
Every secret granted to the token arrives as an environment variable. The
crew reads OPENAI_API_KEY the way it always did. No .env file exists on
disk, so there is nothing for a
file-reading agent or a postinstall script to
pick up.
Values resolved at start hold for the whole kickoff. A crew that runs for
hours does not see a rotated key until it restarts. If that matters, use
setup 3, where the proxy re-resolves on its own interval.
Setup 2: resolve in code
import seekrit
from crewai import LLM, Agent
secrets = seekrit.Client().resolve()
llm = LLM(model="openai/gpt-5.6-terra", api_key=secrets["OPENAI_API_KEY"])
researcher = Agent(role="Research Specialist", goal="Analyse", llm=llm)
Use this when the crew runs inside a server you do not launch with a wrapper, such as a queue worker. Resolve once at boot. Do not resolve per task.
Setup 3: the process never holds the key
Point the LLM at the seekrit proxy and give it
a placeholder instead of a key:
from crewai import LLM
llm = LLM(
model="openai/gpt-5.6-terra",
base_url="http://127.0.0.1:8080/openai/v1",
api_key="{{seekrit:OPENAI_API_KEY}}",
)
The proxy swaps the placeholder for the real key on the way out. The rule list is default-deny and can bound methods and paths per route:
# seekrit-proxy.toml
listen = "127.0.0.1:8080"
[[route]]
prefix = "/openai"
upstream = "https://api.openai.com"
allow = ["OPENAI_API_KEY"]
methods = ["POST"]
paths = ["/v1/chat/completions"]
[[route]]
prefix = "/github"
upstream = "https://api.github.com"
allow = ["GITHUB_TOKEN"]
methods = ["GET", "POST"]
paths = ["/repos/*/*/issues", "/repos/*/*/issues/*"]
The second route is the reason to do this for a crew. The GITHUB_TOKEN can
read and create issues. It cannot delete a repository, push a commit, or
change a webhook, even if a prompt-injected agent asks it to. The methods
and paths fields are how you stop a research agent from performing a write
the crew's task graph never intended.
Without running the proxy
CrewAI reaches the network through LiteLLM. LiteLLM's only hook is a module global, so the in-process shim is process-wide:
import httpx, litellm
from seekrit.transport import AsyncSeekritTransport, SeekritTransport
allow = {"api.openai.com": ["OPENAI_API_KEY"]}
litellm.client_session = httpx.Client(transport=SeekritTransport(allow=allow))
litellm.aclient_session = httpx.AsyncClient(transport=AsyncSeekritTransport(allow=allow))
This works. It cannot scope per request, and it shares the crew's address space, so it is a weaker boundary than the proxy. The in-process injection page sets out the trade-off.
Which setup for which credential
| Credential | Setup |
|---|---|
| Model key, local development | 1 |
| Model key, your own deployment | 2, or 3 if the crew runs generated code |
| A tool credential (GitHub, Stripe, a database) | 3, with methods and paths set |
| Two agents that must have different reach | 3 with session tickets, or two processes |
The last row has no shortcut. Delegation inside a crew does not separate credentials. Two agents with different reach means two processes, or the proxy issuing each a session ticket that scopes what it may substitute.
Setup
Three commands put the keys in an environment, mint a token bound to it, and
export SEEKRIT_TOKEN. The
frameworks guide has
them. Values are encrypted in the browser or CLI before they are stored, so
the service holding them cannot read them. The
full CrewAI page, including the LiteLLM shim and the gotchas, is at
/docs/guides/frameworks/crewai.