seekrit
Docs/Daytona

Secrets in Daytona sandboxes

Daytona takes environment variables at creation and — unusually — lets you change them on a running sandbox, which makes it the one provider here where a rotated secret can reach a live sandbox without recreating it.

Inject at creation

import { Daytona, type CreateSandboxFromSnapshotParams } from '@daytonaio/sdk';
import { Seekrit } from '@seekrit/sdk';

const secrets = await new Seekrit({ token: process.env.SEEKRIT_TOKEN }).resolve();

const daytona = new Daytona();
const sandbox = await daytona.create({
  envVars: {
    OPENAI_API_KEY: secrets.OPENAI_API_KEY,
    TAVILY_API_KEY: secrets.TAVILY_API_KEY,
  },
} satisfies CreateSandboxFromSnapshotParams);
import os
import seekrit
from daytona import Daytona, CreateSandboxFromSnapshotParams

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

daytona = Daytona()
sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        env_vars={
            "OPENAI_API_KEY": secrets["OPENAI_API_KEY"],
            "TAVILY_API_KEY": secrets["TAVILY_API_KEY"],
        },
    )
)

Note the casing difference between the SDKs: envVars in TypeScript, env_vars in Python. Named keys, not the whole dict — see Pick names, not the whole environment.

Update a running sandbox

updateEnv replaces what later processes see, which is how a rotation lands without recreating the sandbox:

// After rotating OPENAI_API_KEY in seekrit.
const fresh = await new Seekrit({ token: process.env.SEEKRIT_TOKEN }).resolve();

await sandbox.updateEnv(
  { OPENAI_API_KEY: fresh.OPENAI_API_KEY },
  { unset: ['TAVILY_API_KEY'] },
);
caution

Only newly started processes see the change. Daytona is explicit: "Newly spawned processes, sessions and PTYs inherit the change; already-running processes keep their environment." A long-lived agent process started before the update keeps the old key until it restarts — so an updateEnv after a rotation is not, on its own, a revocation. Revoke at the provider too.

Keep the credential outside the sandbox

Daytona sandboxes are commonly handed to coding agents, which is exactly the case for not injecting anything. Run seekrit-proxy on the host and give the sandbox a placeholder and a URL:

# seekrit-proxy.toml — on the host
listen = "0.0.0.0:8080"

[[route]]
prefix = "/anthropic"
upstream = "https://api.anthropic.com"
allow = ["ANTHROPIC_API_KEY"]
methods = ["POST"]
paths = ["/v1/messages"]
const sandbox = await daytona.create({
  envVars: {
    // A placeholder, not a key.
    ANTHROPIC_API_KEY: '{{seekrit:ANTHROPIC_API_KEY}}',
    ANTHROPIC_BASE_URL: 'http://<host>:8080/anthropic',
  },
});

The value in the sandbox means nothing anywhere except through the proxy, and the paths list bounds what the real key can be spent on. This also removes the rotation problem above entirely: the proxy re-resolves on its own ([secrets] refresh_interval), so a rotated key reaches even a long-running agent process without touching the sandbox.

See also