Repository navigation
feat(cli): authenticate with E2B_ACCESS_TOKEN - #2125
Conversation
🦋 Changeset detectedLatest commit: 9332a3e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
PR SummaryMedium Risk Overview Reviewed by Cursor Bugbot for commit 9332a3e. Bugbot is set up for automated code reviews on this repo. Configure here. |
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Package ArtifactsBuilt from 30ec06e. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-dockerfile-utils-0.1.2-mishushakov-cli-access-token-env.0.tgz ./e2b-2.63.1-mishushakov-cli-access-token-env.0.tgzCLI ( npm install ./e2b-cli-2.23.1-mishushakov-cli-access-token-env.0.tgzCode Interpreter JS SDK ( npm install ./e2b-code-interpreter-2.9.2-mishushakov-cli-access-token-env.0.tgzDesktop JS SDK ( npm install ./e2b-desktop-2.5.1-mishushakov-cli-access-token-env.0.tgzPython SDK ( pip install ./e2b_dockerfile_utils-0.1.1+mishushakov.cli.access.token.env-py3-none-any.whl ./e2b-2.63.0+mishushakov.cli.access.token.env-py3-none-any.whlCode Interpreter Python SDK ( pip install ./e2b_code_interpreter-2.11.9+mishushakov.cli.access.token.env-py3-none-any.whlDesktop Python SDK ( pip install ./e2b_desktop-2.7.2+mishushakov.cli.access.token.env-py3-none-any.whl |
37951ec to
95408a6
Compare
3538660 to
f2b36d2
Compare
95408a6 to
75e10ba
Compare
f2b36d2 to
a631671
Compare
… key (#2103) ## Summary The CLI now authenticates every request with the login's OAuth access token instead of the project API key it used to store in `~/.e2b/config.json`. It refreshes the token automatically, including during long-running commands and across concurrent `e2b` processes. `E2B_API_KEY` still works and wins, for CI/CD. Needs #2102 (merged 2026-11-10); docs in e2b-dev/docs#636. Bottom of stack #2115: #2103 → #2104 → #2105 → #2124 → #2125; each PR merges into the one below. ## Changes **1. Login URL.** `e2b auth login` opens `https://e2b.dev/auth/cli` (`https://$E2B_DOMAIN/auth/cli` when set). `DOCS_BASE` and the `E2B_DOCS_BASE` override are removed. Both the old `e2b.dev/docs/api/cli` and the new URL already 308 to `console.e2b.dev/auth/cli`, so the browser flow itself is unchanged. **2. Automatic refresh.** `ensureAuth()` replaces `ensureAPIKey()` everywhere: ```ts const auth = await ensureAuth() // { apiKey } from E2B_API_KEY, else: // { apiHeaders: { Authorization: 'Bearer <access token>', 'X-Team-ID': <project> } } await Sandbox.list({ ...auth }) ``` - Tokens are refreshed when they expire within a minute; they live one hour. - **Refresh tokens rotate**, and the old one is rejected once used. Before, two commands refreshing at the same time could get `invalid_grant`, which deletes the config and signs the user out. Refreshes are now serialized with a lock on the config (`~/.e2b/config.json.lock`, managed by [proper-lockfile](https://github.com/moxystudio/node-proper-lockfile): kept fresh while held, released on process exit, taken over once stale after 30 s), through a generic `withFileLock(path, fn, { staleMs, timeoutMs })` in `src/utils/lock.ts` that #2104 also uses for project changes. The config is re-read under the lock, so a process that waited reuses the tokens the other one just wrote. A lock lost mid-refresh (the holder stalled past the stale window) prints a warning instead of crashing the command. - Credentials are resolved per request, not per command. `sandbox logs --follow`, `sandbox metrics --follow` and the attached `sandbox create` keepalive (now the static `Sandbox.setTimeout`, not the instance method that kept the original token) keep working past an hour. - `e2b auth configure` re-reads the config before saving the selected project. Before, it wrote back the tokens it had read before its prompt, which undid any refresh another process made while the prompt was open. - Config writes are atomic (temp file + rename), since refreshes now write in the background of any command. **3. The stored API key plays no role.** - Login and configure don't write `projectApiKey`, and nothing reads it. The schema and the v1 → v2 migration don't know the field: a key an earlier version stored is carried through writes untouched, like any field this version doesn't know about, but the CLI makes no promise about it. - The project for each request comes from `--project`, then `E2B_PROJECT_ID` / `E2B_TEAM_ID`, then the configured project. So `template list --project` now selects the project for logged-in users too. ```console $ e2b auth login # opens https://e2b.dev/auth/cli $ e2b sandbox list # Bearer <access token> + X-Team-ID: <selected project> $ E2B_PROJECT_ID=<other> e2b template list # one-off project override $ E2B_API_KEY=e2b_... e2b template create my-tpl # CI: API key, no login needed ``` ## Notes for review - **Downgrades.** Released CLIs require `projectApiKey` in a v2 config. After a fresh login with this version there is none, so an older CLI reports the deprecated-config message and asks for `e2b auth login`. The changeset calls this out. - **SDK-400 isn't fixed by this alone.** Login and configure still call `GET /teams` here, which mints a new API key for every project server-side; the CLI just stops storing it. #2104, next in the stack, replaces that call with workspace-api `/v1/me` (BKND-155's CLI side; with access tokens its planned `POST /api-keys` step is not needed). - **Known limit.** `Template.build` is a single SDK call that polls build status with the credentials it started with. A build running over an hour would need a fresh token mid-call. Handling that needs a header provider in the SDK; I left it out here. ## Testing - New `tests/auth.test.ts` (10 tests): - `E2B_API_KEY` precedence; - bearer headers and project selection (`--project` > `E2B_PROJECT_ID` > config); - the signed-out exit; - refresh storing the rotated tokens and keeping fields it doesn't know; - **5 concurrent callers → exactly one refresh**; - `invalid_grant` → signed out; - stale-lock recovery; - `tests/utils/lock.test.ts`: one holder at a time, release when the holder throws, stale-lock takeover, timeout while a live holder keeps the lock; - the `/auth/cli` URL with and without `E2B_DOMAIN`. With the lock stubbed out, the concurrency and stale-lock tests fail. - `user_config_permissions`: unknown fields survive the write, and no temp file is left behind. Migration tests are updated. The command tests mock `ensureAuth`. - `pnpm run test` in `packages/cli`: 186 passed, 11 skipped (the `template create` backend suite needs `E2B_API_KEY`). - **Against prod with my login and no `E2B_API_KEY`:** `auth info`, `sandbox list`, `template list`, then `sandbox create --detach` → `info` → `exec` → `logs` → `kill`. All pass. - **Real rotation:** I planted an expired access token in my config (keeping the real refresh token) and ran three commands in parallel. All three succeeded, the refresh happened once, the session stayed valid, and the lock file was removed. - Repo-wide lint and typecheck pass. Part of PRD-119. Related: SDK-400, BKND-155. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An OAuth access token in E2B_ACCESS_TOKEN authenticates like a login, after E2B_API_KEY and ignoring the stored login. The project comes from E2B_PROJECT_ID, else the token's user's personal project or their only active one. Workspace and project commands work with it; `project use` refuses and points at E2B_PROJECT_ID. The token is used as is and an expired one is rejected up front. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…dence - `auth configure` refuses with the token set, like `project use`: the picker would list the token user's projects and store one in the login. - `auth info` reports the credential ensureAuth actually uses when both E2B_API_KEY and E2B_ACCESS_TOKEN are set. - A token workspace-api rejects is named as such instead of surfacing the bootstrap failure or a login hint. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The variable once held the dashboard's access tokens, which some shells and CI jobs still export. A value that doesn't decode as a JWT is refused with a message to unset it, before any request, instead of outranking a working login. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The two select different projects, so there is no sensible precedence: ensureAuth, requireMe and `auth info` exit with "Both E2B_API_KEY and E2B_ACCESS_TOKEN are set. Unset one of them." Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replaces the hand-rolled base64url/JSON decoding of the access token's exp claim; malformed tokens decode to no expiry, as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7704e3a to
84bd20b
Compare
…ery request Like E2B_API_KEY decides the project on its own, the token's project is E2B_PROJECT_ID: required up front, resolved through /v1/me for its UUID and control plane, and not overridable with --project. The default- project fallback (personal, else the only active project) is gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
E2B_PROJECT_ID is still required for every request, but workspace list and project list no longer need it, so they can be used to find it. The missing E2B_PROJECT_ID error points at e2b project list, and project info asks for E2B_PROJECT_ID instead of suggesting project use, which refuses with the token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
A legacy dashboard token in E2B_ACCESS_TOKEN no longer stops the CLI: it is treated as unset, so the stored login or E2B_API_KEY is used, and a warning says to unset it. It no longer counts as setting both credentials either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It said nothing but the missing-E2B_PROJECT_ID error and exited 1, while workspace and project listing work in that environment. It now reports the token in use and warns that requests need E2B_PROJECT_ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
E2B_ACCESS_TOKENauthenticates the CLI with an OAuth access token, for CI and other places where nobody can rune2b auth login. Based onmain, which has #2103–#2105 merged (#2124 was closed, so this no longer stacks on it). Docs: to be added to e2b-dev/docs#636 before release.How
E2B_API_KEYorE2B_ACCESS_TOKEN, then the stored login. Setting both is refused before any request (Both E2B_API_KEY and E2B_ACCESS_TOKEN are set. Unset one of them.), since the key selects its own project and the token its user's, so there is no sensible precedence. While the token is set the stored login is ignored entirely, including its selected project: the token may belong to another user.E2B_PROJECT_IDis required for every request and is the project for it, likeE2B_API_KEYdecides the project on its own. It is resolved through/v1/me(ID,prj_ID, slug or name) for the UUIDX-Team-IDcarries and the project's control plane.--projectplays no part, as with the key. Without it a command that sends requests exits before any of them:E2B_ACCESS_TOKEN needs E2B_PROJECT_ID: set it to the project requests go to (its ID, prj_ ID, slug or name). Run e2b project list to see your projects.There is no fallback project:/v1/mehas no default, and a guessed one would send CI writes to the wrong project silently.decodeJwt, no signature check: the API verifies the token; this replaces the hand-rolled base64url decoding the refresh logic used too). A JWT whoseexphas passed is rejected before any request withE2B_ACCESS_TOKEN has expired. Set a current one, or unset it and run e2b auth login.A value that isn't a JWT at all is a legacy dashboard token (the variable name used to carry those, and some shells and CI jobs still export them): it is ignored as if unset, so the stored login orE2B_API_KEYis used (and it doesn't count as setting both), with a warning on stderr:E2B_ACCESS_TOKEN is not an OAuth access token and is ignored: access tokens from the dashboard are no longer supported. Unset it to remove this warning.A token workspace-api rejects fails withE2B_ACCESS_TOKEN was rejected.requireMe()accepts the token, soworkspace list,project listandproject infowork without a login, withE2B_PROJECT_ID's project as the current one.workspace listandproject listdon't needE2B_PROJECT_ID(no project is marked current), so they can be used to find it;project infowithout it shows the missing-E2B_PROJECT_IDerror.e2b project useande2b auth configurerefuse with the token set, since the stored selection wouldn't be used and the picker would show the token user's projects, and point atE2B_PROJECT_ID.e2b auth inforeports the credential in use and that a stored login is not used, warns (without failing) when the token has noE2B_PROJECT_ID, and exits with the same error when both variables are set. The signed-out box mentions the variable next toE2B_API_KEY.Testing
tests/auth.test.ts(+10):auth infowith the token and noE2B_PROJECT_IDreporting the token and warning, token +E2B_PROJECT_IDwith the stored login and--projectignored (and/v1/measked with the env token), the missing-E2B_PROJECT_IDerror fromensureAuthpointing ate2b project list,requireMelisting projects withoutE2B_PROJECT_ID, both variables together refused, an expired token rejected before any request, a non-JWT legacy value ignored with a warning (the stored login is used) and not refused next toE2B_API_KEY(the key is used),requireMewithout a login, a rejected token named as such.tests/commands/project.test.ts(+5):project listmarksE2B_PROJECT_ID's project current with the token and marks none without it,project infowithout it asks for it,project useandauth configurerefuse.pnpm run testinpackages/cli: 253 passed, 11 skipped;tests/commands/template/create.test.tsfails because its backend suite needsE2B_API_KEY. Lint and typecheck pass.E2B_PROJECT_IDerror, and the legacy-token warning next toE2B_API_KEY(output above).Part of PRD-119.
🤖 Generated with Claude Code