Skip to content

ai-sandbox-daytona: workspace secrets go into each command string and into the readable sandbox record #1084

Description

@mu-hashmi

Versions: @tanstack/ai-sandbox-daytona 0.2.1, @daytona/sdk v0.191.0.

Summary

The sandbox documentation says that secrets never go into snapshots or the event log. On Daytona, secrets stay in two locations that the Daytona API can read: the sandbox env record, and the command string of each session command.

Mechanism

Secrets go into the sandbox on two paths. Both paths keep the values.

Path 1 — create-time env. ensure() puts all workspace secrets into SandboxCreateInput.env (packages/ai-sandbox/src/sandbox.ts). The adapter sends them as envVars to daytona.create (packages/ai-sandbox-daytona/src/provider.ts). Daytona keeps create-time env vars in the sandbox record. GET /sandbox/:id returns them in plain text. Each organization member with sandbox read access sees them in the dashboard, for the life of the sandbox.

Path 2 — per-command exports. bootstrapWorkspace also sends the same secrets to handle.env.set(). DaytonaHandle.withEnv() (packages/ai-sandbox-daytona/src/handle.ts) adds export KEY='value'; before each exec and each spawn command. The spawn path starts the harness CLI, which holds the agent API key (for example, ANTHROPIC_API_KEY).

Daytona keeps the full command string of a session command. The get-session-command endpoint returns it until the session is deleted. The adapter deletes the session only after the process ends. The daemon also writes the command to a cmd.sh file in the sandbox.

The git path shows the defect most clearly. The header of packages/ai-sandbox/src/git-exec.ts says that auth tokens must never go into argv. The code sends GIT_ASKPASS_TOKEN through opts.env. The Daytona handle writes opts.env into the command string. Thus the repo token goes into the clone command line.

Scope

Members of a different organization cannot see the values. The two surfaces are: API and dashboard readers in the same organization, and processes in the sandbox. The sandbox processes get the secrets by design. The problem is that the values stay in readable records after their use.

Fix direction

  1. Use Daytona Secrets (SDK v0.192.0) as the primary path. Create an organization Secret once (daytona.secret.create({ name, value, hosts })). At sandbox create, map the env var to the Secret name with the secrets parameter. The sandbox record and the sandbox processes then hold an opaque placeholder (dtn_secret_<id>), not the value. Daytona puts the real value only into outbound requests to the Secret's allowed hosts. This fits the createSecrets() workspace contract directly. It requires a pin bump from ^0.191.0 to ^0.192.0.
  2. For values that must be literal in the process, send per-call env through the SDK parameter: executeCommand(command, cwd, env, timeout). The daemon applies this env to the process and keeps no copy.
  3. For spawn, write an env file once with fs.write, then source it in the command. The command record then contains a source line, not the values. (The session-execute request has no env field.)
  4. Keep only non-sensitive variables in create-time envVars.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-reproNeeds a minimal reproduction before it can be worked onwaiting-on: maintainerThe ball is in the maintainers’ court

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions