seekrit
Docs/Modal

Secrets in Modal

Modal's unit of environment is a modal.Secret, and the interesting one here is Secret.from_dict() — it is built where your code runs, not stored in Modal's dashboard. That makes Modal a particularly clean fit: seekrit stays the single source of truth, and Modal holds the values only for as long as the function or sandbox that received them.

Inject into a Function at deploy time

import os
import modal
import seekrit

secrets = seekrit.Client(token=os.environ["SEEKRIT_TOKEN"]).resolve()

app = modal.App("storefront-agent")

@app.function(
    secrets=[
        modal.Secret.from_dict(
            {
                "OPENAI_API_KEY": secrets["OPENAI_API_KEY"],
                "TAVILY_API_KEY": secrets["TAVILY_API_KEY"],
            }
        )
    ],
)
def run_agent(prompt: str) -> str:
    import os

    # The keys are ordinary environment variables in here.
    ...

Secret.from_dict() runs on your machine (or in CI) when you modal deploy, so the values travel with that deployment. Named keys, not the whole secrets dict — see Pick names, not the whole environment.

note

This binds values at deploy time. A rotated secret does not reach a deployed Modal function until you deploy again. That is the honest trade for keeping Modal out of the loop; if you need rotation to land without a deploy, read the proxy section below, where the function holds no key at all.

Inject into a Sandbox

Sandboxes take the same secrets= list, and this is the more interesting case because a sandbox is usually running something you trust less than your own function body:

sb_app = modal.App.lookup("agent-sandboxes", create_if_missing=True)

sandbox = modal.Sandbox.create(
    app=sb_app,
    secrets=[modal.Secret.from_dict({"OPENAI_API_KEY": secrets["OPENAI_API_KEY"]})],
)

process = sandbox.exec("python", "-c", "import os; print(bool(os.environ['OPENAI_API_KEY']))")
print(process.stdout.read())

Why there is no Modal sync connector

Every other agent-hosting page under third-party sync exists because the platform has a secrets API seekrit's servers can call. Modal does not. Its control plane at api.modal.com speaks gRPC and nothing else — secrets are managed through the dashboard, the modal secret CLI, or the Python/JS/Go SDKs, all of which talk that same gRPC surface. There is no REST endpoint to push to.

That turns out to be the right answer anyway. Sync means seekrit decrypting your environment on its own servers, which seekrit's zero-knowledge rule permits only where no client exists at the moment values are needed. With Modal a client always exists: your modal deploy, or your code calling Sandbox.create. Secret.from_dict() keeps decryption on your side, which is strictly better than a connector would have been.

Keep the credential outside the sandbox

For untrusted code, do not inject at all. Run seekrit-proxy where the sandbox cannot reach into it — the machine that created the sandbox — and give the sandbox a placeholder and a URL:

# On the host, beside the code that calls Sandbox.create.
docker run --rm -e SEEKRIT_TOKEN=skt_… \
  -v "$PWD/seekrit-proxy.toml:/seekrit-proxy.toml" \
  -p 8080:8080 seekritdev/proxy --listen 0.0.0.0:8080
# seekrit-proxy.toml
[[route]]
prefix = "/openai"
upstream = "https://api.openai.com"
allow = ["OPENAI_API_KEY"]
methods = ["POST"]
paths = ["/v1/chat/completions", "/v1/embeddings"]
sandbox = modal.Sandbox.create(
    app=sb_app,
    secrets=[
        modal.Secret.from_dict(
            {
                # A placeholder, not a key. Worthless if it leaks.
                "OPENAI_API_KEY": "{{seekrit:OPENAI_API_KEY}}",
                "OPENAI_BASE_URL": "https://proxy.internal.example/openai",
            }
        )
    ],
)

The agent's SDK sends Authorization: Bearer {{seekrit:OPENAI_API_KEY}}; the proxy swaps in the real value and forwards it. The sandbox holds no seekrit token, so there is nothing in it to steal — and the paths list means the key cannot be spent on anything but chat completions and embeddings even by code that finds the endpoint.

caution

Do not run the proxy inside the same sandbox as untrusted code. The proxy needs SEEKRIT_TOKEN, and a Modal sandbox is one process space — an agent running as the same user can read it out of the proxy's environment, which hands over the whole environment rather than the two keys you meant to share. The boundary only holds when the token is somewhere the agent cannot read.

See also

  • Agent sandboxes — the two shapes and when each is right
  • Agent proxy — the full proxy configuration
  • Notebooksseekrit.load(), if you are driving Modal from a notebook