seekrit
← all posts

Your agent moved to OpenAI. Your keys don't have to.

Patterns

OpenAI's Agents API runs the Codex harness: sessions, context management, tool discovery, and subagent orchestration. Your application creates a session and supplies work and tools. It still has to decide where code runs and how that code reaches other services.

That decision is also a credential decision. A key in a sandbox's environment is readable by code the agent runs. Keeping a key in your application or at a trusted proxy gives you a narrower place to use it.

Three places to run code

The session's environment.type has three shapes:

  • "none": no sandbox. Your application can answer function calls, or OpenAI can connect to remote MCP servers. Keep upstream keys in the service that handles each call.
  • "self_hosted": you start the compute and connect codex exec-server. You choose its image, network, and startup process.
  • "openai_hosted": OpenAI provisions a Linux workspace. You configure packages, setup commands, environment variables, and network access through the API.

The self-hosted case is familiar: you can start the executor with seekrit run on your machine. But the executor runs model-authored commands. Any third-party key you inject into that process is available to those commands. The application's OpenAI API key should stay in the application, while the executor gets only the restricted environment key it needs to connect.

In a hosted sandbox, env is readable

An OpenAI-hosted environment accepts an env map. OpenAI's sandbox documentation says agent-generated code can read those values, and rejects reserved names including OPENAI_API_KEY. Renaming a provider key does not make it safer: code can still print it, send it elsewhere, or write it to an output file. Putting a SEEKRIT_TOKEN there is broader still, because that token can resolve the environment it is bound to.

OpenAI has a native vault flow for hosted sandbox API calls. You store a credential in an OpenAI vault, attach the vault to the session, and the sandbox sees a placeholder. OpenAI's network proxy substitutes the real value for allowed HTTPS hosts. That solves the sandbox exposure problem, while OpenAI holds the credential in its vault.

Seekrit's OpenAI Vault sync can provision and rotate those credentials from a seekrit environment. Enabling it grants seekrit's sync engine decryption access to that environment and sends its real values to OpenAI. After a rotation, create a new Agents API session; an existing sandbox does not receive the updated credential.

If you want the real value to stay in seekrit and on infrastructure you operate, run seekrit-proxy outside the sandbox. Give the sandbox a placeholder and a base URL for your proxy:

environment = {
    "type": "openai_hosted",
    "network": {"access": "restricted", "allowed_domains": ["broker.acme.com"]},
    "env": {
        "ACME_API_BASE": "https://broker.acme.com/acme",
        "ACME_API_KEY": "{{seekrit:ACME_API_KEY}}",
        "SEEKRIT_TICKET": ticket,
    },
}

allowed_domains takes exact hostnames, without a scheme, port, path, or wildcard. Put the proxy behind HTTPS on a real hostname. The network policy limits destinations the sandbox can contact; the proxy's policy limits which secrets, upstream hosts, methods, and paths it will authorize. Neither policy stops an agent from sending data to an endpoint you allowed, so keep those routes narrow.

A public proxy needs a caller identity

The proxy in that example is reachable from the internet. Your application can mint a short-lived, scoped ticket before creating the session and pass it as SEEKRIT_TICKET. The sandbox must explicitly send it on requests to the proxy; setting an environment variable alone does not add an HTTP header:

curl "$ACME_API_BASE/orders" \
  -H "Authorization: Bearer $ACME_API_KEY" \
  -H "x-seekrit-ticket: $SEEKRIT_TICKET"

The proxy removes x-seekrit-ticket before forwarding. Its policy and the ticket's scope intersect, and an expired or unknown presented ticket is rejected. Without a ticket, seekrit-proxy can apply its default agent policy, so the public HTTPS ingress must reject requests missing x-seekrit-ticket. Use server policy and a private control listener to mint tickets; never expose SEEKRIT_PROXY_CONTROL_TOKEN or the control listener publicly. The ticket is itself a bearer credential readable inside the sandbox; keep its lifetime and scope small, and avoid logging it.

seekrit-proxy resolves values at startup by default. You can opt into its secret refresh or restart it after rotation. A proxy does not automatically pick up a rotated value on every request.

MCP has its own boundary

The Agents API can connect to an HTTP MCP server from OpenAI's service, even with environment.type: "none". seekrit's hosted server at mcp.seekrit.dev handles metadata: creating apps and environments, listing secret names, reading audit records, and listing or revoking tokens. It does not register a tool that returns a stored secret value or creates a decrypting service token. You can attach it with connection_origin: "service", using appropriate authentication and allowed_tools for the actions the agent actually needs.

The local @seekrit/mcp server does decrypt and return values. Running that server inside an OpenAI-hosted sandbox would give those values to the agent. Keep it on trusted compute when the agent's job requires those tools.

The rule is straightforward: decide which process may read a real credential. OpenAI's vault proxy keeps it out of hosted sandbox code. A seekrit proxy can also keep it on infrastructure you control, with more setup and an explicit ticket and network policy. The Agents API guide walks through those choices for each environment type.