seekrit — Python SDK
Read-path SDK for seekrit. Authenticate with a service token, resolve your environment, and get decrypted secrets — the API only ever returns ciphertext; decryption happens in your process.
This repo is a read-only mirror published from seekrit's monorepo so the code that holds your token and decrypts plaintext is auditable. Don't commit here — it's overwritten on each sync. Issues and PRs welcome.
Install
pip install seekrit
Requires Python 3.9+. The only dependency is cryptography.
Usage
import seekrit
client = seekrit.Client() # token from $SEEKRIT_TOKEN
secrets = client.resolve() # {"DATABASE_URL": "postgres://…", …}
db_url = client.get("DATABASE_URL")
api_key = client.get("API_KEY", default="")
Load everything into the process environment:
import os, seekrit
seekrit.Client().into_env() # existing os.environ vars win by default
print(os.environ["DATABASE_URL"])
Configuration
| Argument | Env var | Default |
|---|---|---|
token |
SEEKRIT_TOKEN |
— (required) |
api_url |
SEEKRIT_API_URL |
https://api.seekrit.dev |
overrides |
— | {} |
timeout |
— | 30.0 (seconds) |
A service token binds to a single app environment (plus its composed group
slices). To pull a different environment slice of a composed group, pass
overrides (the ?with= override):
seekrit.Client(overrides={"shared": "dev"}).resolve()
Errors
SeekritApiError— non-2xx from the API; has.statusand.code("unauthorized","forbidden","not_found", …).SeekritCryptoError— a token or ciphertext could not be parsed/decrypted.SeekritError— base class (also covers network failures).
The client is fail-closed: any resolve or decrypt failure raises rather than returning partial results.
Notebooks
seekrit.load() is the one-call form: resolve, load os.environ, done. Put it
at the top of a notebook or script.
import seekrit
seekrit.load()
It's built around the two ways a notebook leaks a credential:
- No token in a cell.
load()takes the token from$SEEKRIT_TOKEN, and when there isn't one it asks through a password prompt (ipykernel routesgetpassto the notebook frontend) — so the token stays in kernel memory instead of being saved into the.ipynb. Passprompt=Falseto never ask, or setSEEKRIT_TOKENfor headless runs likepapermill. - No values in cell outputs.
load()returns the names it loaded and the scope they came from — never the values — so displaying it in a cell writes a summary into the notebook file and nothing more.
loaded = seekrit.load()
loaded # <seekrit: 7 secrets loaded from acme/analytics/staging: API_KEY, …>
len(loaded) # 7
"DATABASE_URL" in loaded # True
os.environ["DATABASE_URL"] # the value lives here, not on the result
Re-running the cell refreshes: load() defaults to override=True, unlike
into_env(), so a rotated secret takes effect on a re-run rather than being
skipped as already-set. Pass override=False to keep what the environment
already has (those names are then listed in loaded.skipped).
This guards the summary, not your own cells — print(os.environ["API_KEY"])
still writes a secret into the notebook. Strip outputs before committing.
Hold a placeholder instead of a key
seekrit.transport substitutes {{seekrit:NAME}} placeholders into outbound
requests, so a provider key is never in your source, your .env, or
os.environ:
pip install 'seekrit[httpx]'
import httpx
from openai import OpenAI
from seekrit.transport import SeekritTransport
client = OpenAI(
api_key="{{seekrit:OPENAI_API_KEY}}",
http_client=httpx.Client(
transport=SeekritTransport(allow={"api.openai.com": ["OPENAI_API_KEY"]}),
),
)
One transport covers every Python agent toolkit, because they all reach the
network through the same http_client=: LangChain's ChatOpenAI, Pydantic AI's
OpenAIProvider, the OpenAI Agents SDK's set_default_openai_client,
LlamaIndex's OpenAI. Use AsyncSeekritTransport for the async client.
For a client built on httpx2 — a separate
distribution, whose BaseTransport is unrelated to httpx's — import the same
classes from seekrit.transport_httpx2 instead, after
pip install 'seekrit[httpx2]'. Same arguments, same behaviour; the TypeSafe SDK
is one such client.
The allowlist is the boundary, and it is default-deny: a name that is not permitted toward that host, method, and path is refused, and so is a name that did not resolve. Neither sends the request.
A refusal answers with the same 403 the proxy answers with, carrying
x-seekrit-refusal and the secret's name but never its value. That is on
purpose: a provider SDK wraps anything its HTTP layer raises into an opaque
connection error and retries it, so raising would turn a denied placeholder
into "Connection error" after six attempts. Pass refusal="raise" to get the typed error
instead.
LangChain middleware
pip install 'seekrit[langchain]' adds agent middleware that scopes credentials
to a single tool call:
from langchain.agents import create_agent
from seekrit.langchain import SeekritCredentials
agent = create_agent(
model=model,
tools=[refund, search],
context_schema=Context,
middleware=[
SeekritCredentials(
scope=lambda ctx: {"tenants": ctx.tenant},
tools={"refund": ["STRIPE_SECRET_KEY"]},
),
],
)
refund may substitute the Stripe key; search may substitute nothing. scope
also picks which tenant's secrets to resolve, per request, without rebuilding
the model. Pair it with require_scope=True on the transport so a lost context
fails closed. Details:
https://seekrit.dev/docs/guides/agent-proxy/in-process.
Pydantic AI
pip install 'seekrit[pydantic-ai]' adds a WrapperToolset that scopes each
tool call:
from pydantic_ai import Agent
from pydantic_ai.toolsets import FunctionToolset
from seekrit.pydantic_ai import SeekritToolset, Scope, use_scope
agent = Agent(
"openai:gpt-5.6-terra",
deps_type=Deps,
toolsets=[
SeekritToolset(
FunctionToolset([refund, search]),
scope=lambda deps: {"tenants": deps.tenant},
tools={"refund": ["STRIPE_SECRET_KEY"]},
)
],
)
# A toolset only wraps tools; wrap the run to cover the model call too.
with use_scope(Scope(overrides={"tenants": tenant})):
result = await agent.run(prompt, deps=Deps(tenant=tenant))
CrewAI
pip install 'seekrit[crewai]' adds CrewAI's own interception contract, plus
hooks that scope credentials per agent as well as per tool — the bound that
matters when several agents share one process:
from crewai import LLM
from seekrit.crewai import SeekritCredentials, SeekritInterceptor, ensure_intercepted
llm = ensure_intercepted(
LLM(
model="openai/gpt-5.6-terra",
api_key="{{seekrit:OPENAI_API_KEY}}",
interceptor=SeekritInterceptor(
allow={
"api.openai.com": ["OPENAI_API_KEY"],
"api.stripe.com": ["STRIPE_SECRET_KEY"],
},
require_scope=True,
),
)
)
with SeekritCredentials(
agents={"Researcher": ["OPENAI_API_KEY"]},
tools={"refund": ["OPENAI_API_KEY", "STRIPE_SECRET_KEY"]},
):
crew.kickoff()
Both lists are exhaustive and are intersected, so the Researcher cannot reach
Stripe even by calling refund, and an agent added later starts with nothing.
ensure_intercepted is worth the line: a model string that routes to CrewAI's
LiteLLM fallback accepts an interceptor and never calls it.
Because it runs in your process, this is a weaker boundary than the egress proxy. What it does buy: the value exists only inside one HTTP call, so it never reaches model context, a tool result, or a trace exporter — nor an environment-scraping bug in a dependency.
Hermes Agent
Installing this package registers two secret sources for
Hermes Agent, so the agent's provider
credentials arrive from seekrit at startup instead of sitting in
~/.hermes/.env. Both are inert until a secrets: section enables one.
secrets:
sources: [seekrit]
seekrit:
enabled: true
seekrit is the bulk source — one environment, whole. seekrit_refs is the
mapped one, for explicit VAR: skt://NAME bindings, renames, and reading
more than one environment. Full guide:
seekrit.dev/docs/guides/ai-agents/hermes.
Secret references
A secret's value may reference another with ${OTHER_SECRET}. References are
stored literally and expanded here, after the layers are merged — so a reference
picks up whichever layer won that name, and rotating the referenced secret
updates every value that uses it. $${OTHER_SECRET} is a literal; an unknown
name is left as written; a reference cycle raises. Full rules:
seekrit.dev/docs/guides/references.
client = seekrit.Client(interpolate=False) # get the stored text instead
Zero-knowledge
GET /v1/resolve returns ciphertext plus a data-encryption key wrapped to your
token's public key. This SDK recovers the token's private key, unwraps the DEK
(ECDH P-256 → HKDF-SHA256 → AES-256-GCM), and decrypts each secret
(AES-256-GCM, AAD-bound to environmentId/NAME) — the exact scheme used by the
CLI, seekrit run, and every other seekrit client. See
seekrit.dev/docs.
License
MIT
Metadata
Release files for seekrit 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| seekrit-0.9.0.tar.gz | 65.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| seekrit-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 117.4 kB
Release files / seekrit-0.9.0.tar.gz
| Download URL | seekrit-0.9.0.tar.gz |
|---|---|
| Size | 65.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0b91d75155460b26dfb49c61600fee383adccb75a85282fd4b50efb15baf8e98
|
|
BLAKE2b-256 checksum How to use checksums |
265658efab17c762ec4e8fd0c812268d6e0eb0b811daf551d1118696ccb2ffc0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency logRelease files / seekrit-0.9.0-py3-none-any.whl
| Download URL | seekrit-0.9.0-py3-none-any.whl |
|---|---|
| Size | 51.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
92ae899bf010490a0d33cc334308a52994c3e28c674c0265399b3c4d53ad1b1e
|
|
BLAKE2b-256 checksum How to use checksums |
ee625bc46c391acf47dc4715aa54a0d009218b44b013fd0b20b734983b6e30c4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency log