Devin
Devin works inside a Linux VM that boots from a snapshot: one frozen image per organization, with your repos cloned and your toolchain installed. Credentials do not live in that image — Devin injects its Secrets as environment variables before every build step and again at the start of every session.
That injection is the thing to think about. Every key you add to Devin's secret store becomes a long-lived credential sitting in the environment of an autonomous agent that writes and runs code, in every session your organization starts. Rotating one means editing it in Devin and hoping nothing cached it; narrowing one is not possible at all, because the store has no notion of which session may see which key.
The seekrit shape is one value in Devin's Secrets — a service token bound to one environment — and everything else resolved at the moment of use.
| Keys in Devin's Secrets | One seekrit token | |
|---|---|---|
| What the VM holds | Every key, for the whole session | A credential that resolves one environment |
| Narrowing | None — org-wide | The environment's contents, composed to taste |
| Revoke | Edit each secret, per key | seekrit token revoke, once |
| Rotate an upstream key | Devin's store, by hand | Rotation; Devin's env is unchanged |
| Record of use | None | Audit trail |
1. Scope an environment for Devin
Do not point the token at production. A service token resolves everything
its environment can see, so the environment is the blast radius. Make one
that holds what a coding agent legitimately needs — a test database, a sandbox
model key — and nothing else:
seekrit env create --app storefront --name "Devin" --slug devin
seekrit secrets set DATABASE_URL --env devin --app storefront # value on stdin
seekrit token create --name devin --app storefront --env devin
# prints skt_… once
Composing a group is the durable version of the
same narrowing: a devin environment that composes only the group holding
sandbox keys cannot leak a production credential, because it never had one.
2. Add the token to Devin's Secrets
In Devin, go to Settings → Secrets, add a raw secret at
organization scope, and name it SEEKRIT_TOKEN.
Devin mangles secret names into valid shell identifiers — anything that is not
a letter, digit, or underscore becomes an underscore, and a leading digit gets
an underscore prefix. SEEKRIT_TOKEN survives that untouched, which is why it
is the name to use.
Secrets reach only sessions created after you add them. After adding or rotating the token, start a new session rather than wondering why the current one still fails.
Personal-scope secrets are scoped to you, and secrets supplied inside a conversation reach only that session. Org scope is what you want here: one token, every session, revocable in one place.
3. Install the CLI in the blueprint
Devin's environment is declared as a YAML blueprint. Put the install in
initialize, which runs when the snapshot is built:
initialize:
- name: "Install seekrit"
run: npm install -g @seekrit/cli
No Node in the image? The seekrit-run launcher is a single
static binary with no runtime at all — it is service-token only, which is
exactly what Devin has, and the command below becomes seekrit-run --:
initialize:
- name: "Install seekrit-run"
run: curl -fsSL https://run.seekrit.dev/install.sh | sh
Never resolve secrets during a build step. The snapshot is a frozen image
that every future session boots from, so anything a build step writes is
baked into it — seekrit export > .env, or the $ENVRC pattern
(seekrit export >> $ENVRC), puts plaintext values into an image shared by
your whole organization, where rotating the underlying key changes nothing.
Devin's own secrets are safe in the blueprint precisely because they are
injected at each step rather than written down. Keep seekrit's the same way:
resolve at the moment of use, inside seekrit run.
4. Teach Devin to run commands through it
Devin reads the blueprint's knowledge entries to learn how to run your
project. That is the right place to make injection the default path, so Devin
never has a reason to go looking for a key:
knowledge:
- name: test
contents: seekrit run -- pnpm test
- name: dev
contents: seekrit run -- pnpm dev
- name: migrate
contents: seekrit run -- pnpm db:migrate
seekrit run resolves the environment the token is bound to, injects it into
the child process, and exits with the command's status. The values exist for the
life of that process — not in a file, not in ~/.bashrc, not in Devin's shell
history.
Worth adding to your repo's Devin instructions in as many words: never echo,
printenv, or commit a secret value; run the command through seekrit run
instead. Devin will otherwise print an environment variable to debug a failure,
and a printed value is a value in the session transcript.
# In a session, when something looks misconfigured:
seekrit doctor # checks the silent failures; never prints values
seekrit secrets list # names and versions, no values
5. Give Devin the MCP servers
Devin manages MCP servers under Customize → MCPs, at personal, organization, or enterprise scope, over stdio, HTTP, or SSE — which maps onto seekrit's two servers exactly.
Local crypto plane (stdio) — everything that touches a secret value:
| Field | Value |
|---|---|
| Command | npx |
| Arguments | -y @seekrit/mcp |
| Environment variables | SEEKRIT_TOKEN = the same token |
Hosted metadata plane (HTTP) — structure, audit, and keyless management, which can never decrypt:
| Field | Value |
|---|---|
| Server URL | https://mcp.seekrit.dev/mcp |
| Authentication | Auth Header — Authorization: Basic <base64(clientId:clientSecret)> |
The hosted server refuses skt_ tokens outright, so it needs
machine credentials
rather than the service token. At organization scope, Devin shares one
authenticated connection across your team — use a credential minted for that
purpose, not your own.
The reason to bother is run_command: it resolves, injects, runs, and returns
only output and an exit code, so Devin can use a secret without its value
entering the conversation. get_secret reveals a plaintext only when explicitly
asked to.
The local server runs on Devin's machine, so decryption still happens next to the credential, and the hosted server still sees only ciphertext. The zero-knowledge boundary is unchanged by any of this — what changes is who is holding the credential, and Devin's VM is a machine you should treat as one more principal with a scoped grant.
Keeping the values out of the VM entirely
A token in Devin's environment still means Devin can read everything that
environment holds. When the credential matters more than the convenience — a
payment key, anything with a spend — hand the workload a placeholder and let
seekrit-proxy swap in the real value on the way
out.
Run it as a sidecar inside Devin's machine, started by the same script that starts your app:
seekrit proxy run --preset openai --preset anthropic &
Each preset prints the environment the app needs — a placeholder where the key used to be, and a base URL pointing at the proxy:
export OPENAI_BASE_URL=http://127.0.0.1:8080/openai/v1
export OPENAI_API_KEY={{seekrit:OPENAI_API_KEY}}
Those two are safe to put in Devin's Secrets, which is the nice part: the
value in Devin's store is now a string that unlocks nothing anywhere. The real
credential exists only inside the proxy process, for the moment of the request,
and the preset's methods/paths bound what it can be spent on even if the
agent tries something else with it.
Be clear-eyed about what a sidecar buys you. Same machine, same user: this bounds what the credential can do and keeps values out of logs, commits, and the model's context — it is not a wall against an agent that goes looking on that box. A proxy on infrastructure you control is the real boundary, and it is only available if Devin's VM can already reach your network. Its data plane has no client authentication, so never expose one to the internet: put it on a private network, exactly as the proxy guide describes.
Rotate and revoke
Neither of these touches the snapshot:
seekrit token revoke skt_…
seekrit token create --name devin --app storefront --env devin
Paste the new token over the old one in Devin's Secrets; sessions started after that pick it up. The upstream keys inside the environment rotate independently — see rotation — and Devin's configuration never mentions them, so nothing there needs editing when they change.
seekrit audit shows what the org's principals did, and
seekrit audit actions lists the vocabulary to filter on.
The Devin CLI, locally
Devin also ships a CLI that runs on your own machine. There, seekrit is the
ordinary local setup — seekrit login for yourself, and the same two MCP
servers added by hand:
devin mcp add seekrit-local -- npx -y @seekrit/mcp
devin mcp add seekrit --url https://mcp.seekrit.dev/mcp
Those write .devin/mcp_config.json (project) or
~/.config/devin/mcp_config.json (user) in the familiar mcpServers shape, so
a project-scoped entry can be committed. Keep credentials out of the committed
one: mcp_config.local.json is the gitignored sibling, and the local server
reads SEEKRIT_TOKEN from the environment it inherits.
See also
- AI agents — the two MCP servers, and why they are split
- Agent sandboxes — the same problem when your code starts the VM
- Credential broker — placeholders instead of keys
- Service tokens — scoping, granting, and revoking machine credentials