Skip to content

feat(cli): authenticate with E2B_ACCESS_TOKEN - #2125

Merged
mishushakov merged 9 commits into
mainfrom
mishushakov/cli-access-token-env
Nov 13, 2026
Merged

mishushakov merged 9 commits into
mainfrom
mishushakov/cli-access-token-env

Conversation

@mishushakov

@mishushakov mishushakov commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Summary

E2B_ACCESS_TOKEN authenticates the CLI with an OAuth access token, for CI and other places where nobody can run e2b auth login. Based on main, 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.

$ E2B_ACCESS_TOKEN=<token> E2B_PROJECT_ID=prj_… e2b sandbox list    # the project for every request
$ E2B_ACCESS_TOKEN=<token> E2B_PROJECT_ID=web-app e2b template list  # by name or slug works too
$ E2B_ACCESS_TOKEN=<token> e2b project list                        # listing needs no project: find the one to set
$ E2B_ACCESS_TOKEN=<token> e2b sandbox list
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.
$ E2B_ACCESS_TOKEN=<legacy dashboard token> E2B_API_KEY=e2b_… e2b auth info
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.
Authenticating with E2B_API_KEY.

How

  • Precedence: E2B_API_KEY or E2B_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.
  • Project: E2B_PROJECT_ID is required for every request and is the project for it, like E2B_API_KEY decides the project on its own. It is resolved through /v1/me (ID, prj_ ID, slug or name) for the UUID X-Team-ID carries and the project's control plane. --project plays 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/me has no default, and a guessed one would send CI writes to the wrong project silently.
  • No refresh: there is no refresh token, so the token is used as is. JWT claims are read with jose (decodeJwt, no signature check: the API verifies the token; this replaces the hand-rolled base64url decoding the refresh logic used too). A JWT whose exp has passed is rejected before any request with E2B_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 or E2B_API_KEY is 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 with E2B_ACCESS_TOKEN was rejected.
  • Commands: requireMe() accepts the token, so workspace list, project list and project info work without a login, with E2B_PROJECT_ID's project as the current one. workspace list and project list don't need E2B_PROJECT_ID (no project is marked current), so they can be used to find it; project info without it shows the missing-E2B_PROJECT_ID error. e2b project use and e2b auth configure refuse with the token set, since the stored selection wouldn't be used and the picker would show the token user's projects, and point at E2B_PROJECT_ID. e2b auth info reports the credential in use and that a stored login is not used, warns (without failing) when the token has no E2B_PROJECT_ID, and exits with the same error when both variables are set. The signed-out box mentions the variable next to E2B_API_KEY.

Testing

  • tests/auth.test.ts (+10): auth info with the token and no E2B_PROJECT_ID reporting the token and warning, token + E2B_PROJECT_ID with the stored login and --project ignored (and /v1/me asked with the env token), the missing-E2B_PROJECT_ID error from ensureAuth pointing at e2b project list, requireMe listing projects without E2B_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 to E2B_API_KEY (the key is used), requireMe without a login, a rejected token named as such.
  • tests/commands/project.test.ts (+5): project list marks E2B_PROJECT_ID's project current with the token and marks none without it, project info without it asks for it, project use and auth configure refuse.
  • pnpm run test in packages/cli: 253 passed, 11 skipped; tests/commands/template/create.test.ts fails because its backend suite needs E2B_API_KEY. Lint and typecheck pass.
  • Checked with the built CLI: the missing-E2B_PROJECT_ID error, and the legacy-token warning next to E2B_API_KEY (output above).
  • Not run against prod: I don't have an access token outside my login's config to test with.

Part of PRD-119.

🤖 Generated with Claude Code

@cla-bot cla-bot Bot added the cla-signed label Nov 12, 2026
@linear-code

linear-code Bot commented Nov 12, 2026

Copy link
Copy Markdown

PRD-119

@mishushakov
mishushakov added this pull request to stack #2115 November 12, 2026 10:12
@changeset-bot

changeset-bot Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9332a3e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@e2b/cli Minor

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

@cursor

cursor Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

PR Summary

Medium Risk
Changes CLI authentication precedence and project resolution; misconfigured CI could hit the wrong project or fail auth, but conflicts and missing E2B_PROJECT_ID are explicit errors.

Overview
The CLI can authenticate in CI via E2B_ACCESS_TOKEN, an OAuth JWT used as Bearer auth with no refresh; E2B_PROJECT_ID is required for project-scoped commands and resolved through /v1/me for X-Team-ID and the control plane, parallel to how E2B_API_KEY picks its project. Setting E2B_API_KEY and a valid E2B_ACCESS_TOKEN together is rejected, the stored login and --project are ignored while the env token is active, expired JWTs fail before requests, and non-JWT legacy dashboard values are ignored with a warning. e2b workspace list and e2b project list work with only the token; e2b project use and e2b auth configure exit with guidance to set E2B_PROJECT_ID; e2b auth info reports which credential is in use. JWT exp parsing moves to the jose dependency and tests cover the new auth paths.

Reviewed by Cursor Bugbot for commit 9332a3e. Bugbot is set up for automated code reviews on this repo. Configure here.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-13T15:26:44.744775Z 22fc36b Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from 30ec06e. Download artifacts from this workflow run.

JS SDK (e2b@2.63.1-mishushakov-cli-access-token-env.0, with @e2b/dockerfile-utils@0.1.2-mishushakov-cli-access-token-env.0):

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.tgz

CLI (@e2b/cli@2.23.1-mishushakov-cli-access-token-env.0):

npm install ./e2b-cli-2.23.1-mishushakov-cli-access-token-env.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.9.2-mishushakov-cli-access-token-env.0):

npm install ./e2b-code-interpreter-2.9.2-mishushakov-cli-access-token-env.0.tgz

Desktop JS SDK (@e2b/desktop@2.5.1-mishushakov-cli-access-token-env.0):

npm install ./e2b-desktop-2.5.1-mishushakov-cli-access-token-env.0.tgz

Python SDK (e2b==2.63.0+mishushakov.cli.access.token.env, with e2b-dockerfile-utils==0.1.1+mishushakov.cli.access.token.env):

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.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.11.9+mishushakov.cli.access.token.env):

pip install ./e2b_code_interpreter-2.11.9+mishushakov.cli.access.token.env-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.7.2+mishushakov.cli.access.token.env):

pip install ./e2b_desktop-2.7.2+mishushakov.cli.access.token.env-py3-none-any.whl

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

claude[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-env branch from 37951ec to 95408a6 Compare November 12, 2026 12:18
@mishushakov
mishushakov force-pushed the mishushakov/cli-list-project-ids branch from 3538660 to f2b36d2 Compare November 12, 2026 12:18
@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-env branch from 95408a6 to 75e10ba Compare November 12, 2026 12:25
@mishushakov
mishushakov force-pushed the mishushakov/cli-list-project-ids branch from f2b36d2 to a631671 Compare November 12, 2026 12:25
mishushakov added a commit that referenced this pull request Nov 12, 2026
… 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>
mishushakov and others added 5 commits November 12, 2026 16:49
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>
@mishushakov
mishushakov force-pushed the mishushakov/cli-access-token-env branch from 7704e3a to 84bd20b Compare November 12, 2026 15:49
@mishushakov
mishushakov removed this pull request from stack #2115 November 12, 2026 15:49
@mishushakov
mishushakov changed the base branch from mishushakov/cli-list-project-ids to main November 12, 2026 15:49
mishushakov and others added 2 commits November 12, 2026 17:34
…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>
@mishushakov

Copy link
Copy Markdown
Member Author

@claude review
@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 22fc36b81f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

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".

claude[bot]

This comment was marked as resolved.

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>
@e2b-harness e2b-harness Bot added the review-requested Posted in #code-review-requests on Slack label Nov 13, 2026
cursor[bot]

This comment was marked as resolved.

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>
@mishushakov
mishushakov merged commit 29fe119 into main Nov 13, 2026
40 checks passed
@mishushakov
mishushakov deleted the mishushakov/cli-access-token-env branch November 13, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed review-requested Posted in #code-review-requests on Slack

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants