f89f8c3f96
## 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>
49 lines
1.8 KiB
YAML
49 lines
1.8 KiB
YAML
# Filters the synced REST API spec (spec/openapi.yml) down to the endpoint
|
|
# tags each SDK exposes; everything else (admin, access-tokens, api-keys,
|
|
# untagged endpoints like /health) is dropped. The SDK generate pipelines run
|
|
# `redocly bundle <api> -o spec/openapi_generated.<api>.yml` before invoking
|
|
# their client generators. The js-sdk list additionally keeps `auth` because
|
|
# the CLI reaches /teams through the js-sdk's generated schema.
|
|
#
|
|
# filter-out drops any node (operation or component schema) the upstream
|
|
# specs mark `x-not-implemented: true`. It has no applyTo on purpose: the
|
|
# flag must work on schemas as well as operations. A kept operation that
|
|
# still references a dropped schema loses that media-type entry rather
|
|
# than keeping a dangling $ref.
|
|
#
|
|
# The envd api filters the synced envd spec (spec/envd/envd.yaml) the same
|
|
# way: operations the upstream spec marks `x-internal: true` belong to the
|
|
# orchestrator's control plane, not to SDKs, so they are dropped before the
|
|
# js-sdk envd schema is generated. remove-unused-components then drops the
|
|
# component schemas only those operations referenced.
|
|
apis:
|
|
js-sdk:
|
|
root: spec/openapi.yml
|
|
decorators:
|
|
filter-in:
|
|
property: tags
|
|
value: [sandboxes, snapshots, templates, tags, auth, volumes, secrets]
|
|
matchStrategy: any
|
|
applyTo: Operation
|
|
filter-out:
|
|
property: x-not-implemented
|
|
value: [true]
|
|
python-sdk:
|
|
root: spec/openapi.yml
|
|
decorators:
|
|
filter-in:
|
|
property: tags
|
|
value: [sandboxes, snapshots, templates, tags, volumes, secrets]
|
|
matchStrategy: any
|
|
applyTo: Operation
|
|
filter-out:
|
|
property: x-not-implemented
|
|
value: [true]
|
|
envd:
|
|
root: spec/envd/envd.yaml
|
|
decorators:
|
|
filter-out:
|
|
property: x-internal
|
|
value: [true]
|
|
remove-unused-components: on
|