## Summary Implements Secrets Management in the SDK per the [Secrets Vault SDK proposal](https://app.notion.com/p/3bab8c29687380b6a8f3e2ecae3f1b50) and the backend Secrets API. Linear: [SDK-133](https://linear.app/e2b/issue/SDK-133/sdk-for-managing-secrets). Docs: [e2b-dev/docs#379](https://github.com/e2b-dev/docs/pull/379). Spec sync: bumps `spec/infra-ref` to `e19a12b8` (the commit that adds the Secrets API), adds the `secrets` tag to the `redocly.yaml` filters, and regenerates via `make codegen` (the regen also pulls in unrelated upstream spec updates, e.g. the `Error.errorCode` field). The js-sdk envd schema generation now bundles through a new `envd` redocly api that filters out operations the upstream spec marks `x-internal: true` (orchestrator control plane: `/init`, `/freeze`, `/unfreeze`, `/collapse`, `/fsfreeze`, `/fsthaw`) plus their now-unused component schemas, so they no longer appear in `src/envd/schema.gen.ts`. The existing `Secret` class (previously only the `iamToken`/`iam_token` workload-identity helper) becomes the secrets management surface, equivalent across JS, sync Python (`Secret`), and async Python (`AsyncSecret`): ```typescript Secret.create(name, value, opts?): Promise<SecretInfo> // POST /secrets Secret.update(secret, value, opts?): Promise<SecretInfo> // POST /secrets/{secretID} (rotates to a new version) Secret.getInfo(secret, opts?): Promise<SecretInfo> // GET /secrets/{secretID} Secret.list(opts?): SecretPaginator // GET /secrets (cursor-paginated) Secret.exists(secret, opts?): Promise<boolean> // 200 → true, 404 → false Secret.destroy(secret, opts?): Promise<boolean> // 204 → true, 404 → false Secret.fill(secret): string // local marker formatting, no network call ``` Design decisions per the proposal: - **Values are write-only**: `SecretInfo` carries only metadata (`secretId`, `name`, `version`, `metadata`, `createdAt`, `updatedAt`); no read surface or error message includes a value. - `update`/`getInfo` throw `SecretNotFoundError` / `SecretNotFoundException` on 404 (subclass of `NotFoundError` / `NotFoundException`, so generic not-found catches keep working; general failures throw the new `SecretError` / `SecretException`); `exists`/`destroy` map 404 to `false` instead. - `secret` selector accepts either the `sec_` ID or the canonical lowercase name (backend resolves both). - `fill` returns the `${e2b.secrets.name}` marker for use in a network rule's request transform — always the current version, purely local. The egress proxy replaces the marker with the secret's current value when it forwards a matching request; unresolvable markers fail open (the request is forwarded with the affected headers omitted). - Version-management endpoints from the proposal are marked TBD and not in the committed backend contract, so they are intentionally not implemented. Python moves `e2b/secret.py` to an `e2b/secret/` package (`base.py` shares `fill`/`iam_token`, `secret_sync.py` / `secret_async.py` mirror each other); `from e2b import Secret` is unchanged. Usage: ```typescript import { Sandbox, Secret } from 'e2b' const info = await Secret.create('stripe_api_key', 'sk_live_...', { metadata: { env: 'prod' } }) await Secret.update('stripe_api_key', 'sk_live_new...') // rotate → version 2 // Inject into matching outbound requests via a network rule's transform: const sandbox = await Sandbox.create({ network: { allowOut: ({ rules }) => [...rules.keys()], denyOut: ({ allTraffic }) => [allTraffic], rules: { 'api.stripe.com': [ { transform: { headers: { Authorization: `Bearer ${Secret.fill('stripe_api_key')}` }, }, }, ], }, }, }) await Secret.destroy('stripe_api_key') ``` ```python from e2b import AsyncSecret info = await AsyncSecret.create("stripe_api_key", "sk_live_...", metadata={"env": "prod"}) paginator = AsyncSecret.list(limit=100) while paginator.has_next: secrets = await paginator.next_items() print(AsyncSecret.fill("stripe_api_key")) # ${e2b.secrets.stripe_api_key} ``` Tests: msw-mocked JS suite (`tests/secret/secret.test.ts`) and monkeypatched sync/async Python suites covering CRUD, pagination, 404 semantics, and `fill`. `pnpm run format/lint/typecheck` pass; changeset included (minor for `e2b` and `@e2b/python-sdk`). Link to Devin session: https://app.devin.ai/sessions/175095f75cbe42df8710718a1ff2a6a3 Requested by: @mishushakov --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: mish@e2b.dev <mish@e2b.dev>
API specs
Most files in this directory are owned by other repositories and are synced
here with Copybara (config in
../copy.bara.sky) — don't edit them by hand; change them in their
source repository and re-sync:
openapi.yml,envd/envd.yaml,envd/filesystem/,envd/process/are owned by the infra repository, pinned byinfra-ref.openapi-volumecontent.ymlis owned by the private belt repository, pinned bybelt-ref.
Fetches authenticate with a GitHub token when available (GITHUB_TOKEN, or
being logged in with gh auth login); the public infra specs also fetch
anonymously, while the volume-content spec needs a token with read access
to belt. When a fetch fails, make codegen warns and falls back to the
tracked copy.
make codegen re-fetches all of them at their pinned commits before
generating the clients, and the generated-files CI check fails if the
tracked copies don't match the pins. The files are stored byte-identical to
upstream. To update the specs, point the pin at a newer commit and re-run
make codegen. To fetch without regenerating:
pnpm fetch:api-spec # openapi.yml
pnpm fetch:envd-spec # envd spec
pnpm fetch:volume-spec # openapi-volumecontent.yml
E2B_INFRA_REF=main pnpm fetch:api-spec # try the latest without moving the pin
E2B_BELT_REF=main pnpm fetch:volume-spec
The remaining files (mcp-server.json, envd/buf-*.gen.yaml) are owned by
this repository. The SDK generate pipelines filter openapi.yml down to the
tags each SDK exposes with Redocly CLI (see ../redocly.yaml) before
generating the clients.