Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.opencomputer.dev/llms.txt

Use this file to discover all available pages before exploring further.

Secrets let you pass API keys, tokens, and credentials into sandboxes without the real values ever entering the VM. They are encrypted at rest, sealed into opaque tokens at boot, and only revealed by a host-side proxy on outbound HTTPS requests.

How it works

The real secret value only exists in the host-side proxy’s memory — it is never written to disk, never sent to the VM, and never visible via env or /proc inside the sandbox.

Quick start

Create a secret store, add a secret, and launch a sandbox that uses it:
import { Sandbox, SecretStore } from '@opencomputer/sdk';

// 1. Create a secret store with egress restrictions
const store = await SecretStore.create({
  name: 'my-agent-secrets',
  egressAllowlist: ['api.anthropic.com'],
});

// 2. Add an encrypted secret
await SecretStore.setSecret(store.id, 'ANTHROPIC_API_KEY', 'sk-ant-...');

// 3. Create a sandbox — secrets are injected as sealed tokens
const sandbox = await Sandbox.create({
  secretStore: 'my-agent-secrets',
  timeout: 600,
});

// Inside the VM, the env var is sealed — not the real key
const result = await sandbox.exec.run('echo $ANTHROPIC_API_KEY');
console.log(result.stdout); // "osb_sealed_7f3a9c..."

// But HTTPS requests to allowed hosts get the real value via the proxy
const apiResult = await sandbox.exec.run(`
  curl -s https://api.anthropic.com/v1/messages \\
    -H "x-api-key: $ANTHROPIC_API_KEY" \\
    -H "anthropic-version: 2023-06-01" \\
    -H "content-type: application/json" \\
    -d '{"model":"claude-haiku-4-5-20251001","max_tokens":10,"messages":[{"role":"user","content":"hi"}]}'
`);
console.log(apiResult.stdout); // 200 OK — real key was substituted by the proxy

Per-secret host restrictions

You can restrict individual secrets so they are only substituted in requests to specific hosts. This prevents a compromised dependency from exfiltrating secrets to an attacker-controlled server.
// This secret will only be substituted in requests to api.anthropic.com
await SecretStore.setSecret(store.id, 'ANTHROPIC_API_KEY', 'sk-ant-...', {
  allowedHosts: ['api.anthropic.com'],
});

// This secret works on any allowed egress host
await SecretStore.setSecret(store.id, 'GENERIC_TOKEN', 'tok-...');

Egress allowlists

Secret stores can restrict which hosts the sandbox can make HTTPS requests to. Requests to hosts not on the list are blocked by the proxy.
const store = await SecretStore.create({
  name: 'restricted-store',
  egressAllowlist: ['api.anthropic.com', '*.openai.com'],
});
Supports exact matches (api.anthropic.com) and wildcards (*.openai.com). An empty allowlist means all hosts are allowed.

Managing secrets

// List all secret stores
const stores = await SecretStore.list();

// List secrets in a store (metadata only — values are never returned)
const entries = await SecretStore.listSecrets(store.id);

// Delete a secret
await SecretStore.deleteSecret(store.id, 'OLD_KEY');

// Delete a store and all its secrets
await SecretStore.delete(store.id);

Secrets with snapshots and checkpoints

You can attach a secret store when creating a sandbox from a snapshot or checkpoint, even if the original didn’t have one. This is useful for baking a base environment (e.g., installed dependencies) and then giving each fork its own scoped credentials.

Snapshot template with secrets

Pre-build a snapshot once, then create sandboxes from it with different credentials:
import { Sandbox, Snapshots, Image } from '@opencomputer/sdk/node';

// Pre-build a reusable snapshot
const snapshots = new Snapshots();
await snapshots.create({
  name: 'data-pipeline',
  image: Image.base().aptInstall(['python3-pip']).pipInstall(['pandas']),
});

// Create sandboxes from the snapshot, each with their own credentials
const worker1 = await Sandbox.create({
  snapshot: 'data-pipeline',
  secretStore: 'worker-1-keys',
});
const worker2 = await Sandbox.create({
  snapshot: 'data-pipeline',
  secretStore: 'worker-2-keys',
});

Checkpoint fork with secrets

Layer a new secret store on top of an existing checkpoint’s store:
// Bake a base snapshot with git credentials
const base = await Sandbox.create({ secretStore: 'git-creds' });
await base.exec.run('git clone https://github.com/org/repo /app');
const cp = await base.createCheckpoint('repo-cloned');

// Fork with sandbox-specific API credentials (layered on top of git-creds)
const worker1 = await Sandbox.createFromCheckpoint(cp.id, {
  secretStore: 'worker-1-keys',
});
const worker2 = await Sandbox.createFromCheckpoint(cp.id, {
  secretStore: 'worker-2-keys',
});

Layering rules

When a checkpoint already has a secret store and you attach another at fork time, the stores are merged:
  • Secrets: Both stores’ secrets are available. On name collision, the fork’s store wins.
  • Egress allowlists: Aggregated (union of both stores’ lists).
  • Per-secret host restrictions: Follow the winning secret’s store.
This means a base snapshot can provide broad credentials (e.g., git access) while each fork adds its own scoped credentials (e.g., limited API keys). On a fork-of-fork, the checkpoint’s merged result becomes the new base — there’s no unbounded chain of stores to resolve.

Security properties

PropertyDetail
Encryption at restAES-256-GCM in Postgres, key via OPENSANDBOX_SECRET_ENCRYPTION_KEY
Never in VM memoryEnv vars contain opaque osb_sealed_* tokens
Host-side onlyReal values exist only in the MITM proxy process on the worker host
Egress controlAllowlists restrict which domains receive secrets
Per-secret scopingIndividual secrets can be locked to specific hosts
Values never returnedThe API only returns secret names and metadata, never values

Next steps