Skip to content

fix: Fix incorrect gating of ACCESS_TOKEN for template commands#1315

Open
wj-e2b wants to merge 1 commit intomainfrom
wj-fix-api-key
Open

fix: Fix incorrect gating of ACCESS_TOKEN for template commands#1315
wj-e2b wants to merge 1 commit intomainfrom
wj-fix-api-key

Conversation

@wj-e2b
Copy link
Copy Markdown

@wj-e2b wj-e2b commented May 7, 2026

No description provided.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 7, 2026

🦋 Changeset detected

Latest commit: f9465f0

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 Patch

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

Package Artifacts

Built from 8d1fc52. Download artifacts from this workflow run.

JS SDK (e2b@2.19.6-wj-fix-api-key.0):

npm install ./e2b-2.19.6-wj-fix-api-key.0.tgz

CLI (@e2b/cli@2.10.2-wj-fix-api-key.0):

npm install ./e2b-cli-2.10.2-wj-fix-api-key.0.tgz

Python SDK (e2b==2.21.0+wj-fix-api-key):

pip install ./e2b-2.21.0+wj.fix.api.key-py3-none-any.whl

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f9465f0e0e

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

Comment thread packages/cli/src/api.ts
Comment on lines +99 to +100
const resolvedApiKey = resolveAPIKey()
const resolvedAccessToken = resolveAccessToken()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid mixing an explicit API key with saved tokens

When E2B_API_KEY is set on a machine that also has a saved ~/.e2b/config.json, this still calls resolveAccessToken(), loads the saved access token, and returns both credentials. Callers such as template build then prefer accessToken for Docker auth, while template create sends both headers, so a stale or wrong local token can override the explicit API key and make the new API-key-only path fail or use the wrong account. Stop resolving the fallback token once an API key has been chosen, or otherwise preserve a single credential source.

Useful? React with 👍 / 👎.

try {
child_process.execSync(
`echo "${accessToken}" | docker login docker.${connectionConfig.domain} -u _e2b_access_token --password-stdin`,
`echo "${dockerAuthToken}" | docker login docker.${connectionConfig.domain} -u _e2b_access_token --password-stdin`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Agentic Security Review
Severity: MEDIUM
The Docker login command now interpolates dockerAuthToken directly into a shell command string (echo "${dockerAuthToken}" | ...). Because this value can now be E2B_API_KEY, the secret may be exposed through process inspection or command logging on shared hosts/CI runners while the command executes.

Impact: Team-scoped API credentials can leak to other local processes/users, enabling unauthorized access to protected template and registry operations.

Comment on lines 378 to 384
if (imageUriMask == undefined) {
try {
child_process.execSync(
`echo "${accessToken}" | docker login docker.${connectionConfig.domain} -u _e2b_access_token --password-stdin`,
`echo "${dockerAuthToken}" | docker login docker.${connectionConfig.domain} -u _e2b_access_token --password-stdin`,
{
stdio: 'inherit',
cwd: root,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 After this PR, dockerAuthToken (build.ts:250) can be an API key when only E2B_API_KEY is set, but docker login (build.ts:381) and buildWithProxy.ts (the _e2b_access_token:... Basic-auth header at L42 and the ?account=_e2b_access_token query param at L127) still send the literal username _e2b_access_token. The README now explicitly markets E2B_API_KEY=e2b_... e2b template build as supported, so this is the exact path the PR is enabling. Please confirm server-side that the registry / token endpoint accepts API keys under _e2b_access_token (or make the username conditional, e.g. _e2b_api_key when sending an API key).

Extended reasoning...

What changed

Before this PR, build.ts called ensureAccessToken() so the credential passed to docker login and to buildWithProxy was always an access token. After the PR, that line is replaced with:

const credentials = ensureAPIKeyOrAccessToken()
const dockerAuthToken = credentials.accessToken ?? credentials.apiKey!

So dockerAuthToken is the access token if present, otherwise the API key. dockerAuthToken is then plumbed into three places that were originally written assuming an access token:

  1. build.ts:381echo "${dockerAuthToken}" | docker login docker.${domain} -u _e2b_access_token --password-stdin
  2. buildWithProxy.ts:42Buffer.from(\_e2b_access_token:${dockerAuthToken}`).toString('base64')used as theBasicauth header against/v2/token` and as fallback in the proxy.
  3. buildWithProxy.ts:127fetch(\https://docker.${domain}/v2/token?account=_e2b_access_token&scope=...\`, ...)– theaccount` query param is also hardcoded.

Why this is worth flagging

The whole point of the PR (per the changeset and the README diff) is to let users run E2B_API_KEY=e2b_... e2b template build. That command is the v1 build path which performs docker login against the E2B docker registry. If the registry/token endpoint only honours _e2b_access_token for actual access tokens, API-key-only users will fail at docker login (or the proxy token exchange) — silently nullifying the new flow that this PR is named after.

Step-by-step proof of the new code path

  1. User sets only E2B_API_KEY=e2b_... (no E2B_ACCESS_TOKEN, no logged-in config), as the new README example instructs.
  2. ensureAPIKeyOrAccessToken() (api.ts) returns { apiKey: 'e2b_...', accessToken: undefined }.
  3. build.ts:250 resolves dockerAuthToken = undefined ?? 'e2b_...' → the API key.
  4. build.ts:381 runs echo "e2b_..." | docker login docker.<domain> -u _e2b_access_token --password-stdin. The username sent to the registry is _e2b_access_token but the password is an API key.
  5. If docker push falls back to the proxy, buildWithProxy.ts:42 builds Basic base64(_e2b_access_token:e2b_...) and buildWithProxy.ts:127 requests /v2/token?account=_e2b_access_token&scope=... with that header.

Addressing the refutation

A refuting verifier argued that _e2b_access_token is a Docker-registry convention like AWS ECR's AWS or GCR's _json_key, where the username is opaque and the password drives auth. That is plausible and may well be true here — but it's exactly what we cannot verify from the CLI source. The bug submitters and confirming verifiers are also explicit that this hinges on server behaviour: "Worth verifying server-side". The action being requested is small and concrete: the PR author can either (a) confirm in the PR description that the registry treats the username as opaque so an API key under _e2b_access_token works, or (b) make the username conditional (e.g. _e2b_api_key when credentials.accessToken is undefined) so the username matches the credential type and is robust to future server-side changes. Even if the server currently accepts both, the hardcoded label is now semantically wrong post-PR.

Comment thread packages/cli/src/api.ts
Comment on lines +98 to 109
export function ensureAPIKeyOrAccessToken() {
const resolvedApiKey = resolveAPIKey()
const resolvedAccessToken = resolveAccessToken()

if (resolvedApiKey || resolvedAccessToken) {
return { apiKey: resolvedApiKey, accessToken: resolvedAccessToken }
}

console.error(authErrorBox('E2B_API_KEY'))
process.exit(1)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 When neither E2B_API_KEY nor E2B_ACCESS_TOKEN is set, ensureAPIKeyOrAccessToken() calls authErrorBox('E2B_API_KEY'), which only mentions the API key env var and the API-key dashboard URL. Since this function explicitly accepts either credential (and the README updated in this PR tells users they may use either), the error message is misleading for users who authenticate with an access token — consider extending authErrorBox to mention both options here.

Extended reasoning...

What the bug is

In packages/cli/src/api.ts (lines 98–109), the new ensureAPIKeyOrAccessToken() function accepts either E2B_API_KEY or E2B_ACCESS_TOKEN as valid authentication. However, when both are missing it falls through to console.error(authErrorBox('E2B_API_KEY')). The authErrorBox helper switches on keyName and only supports two single-key variants:

  • 'E2B_API_KEY' → produces "set the E2B_API_KEY environment variable. Visit https://e2b.dev/dashboard?tab=keys to get the API key."
  • 'E2B_ACCESS_TOKEN' → produces the analogous message for the access token.

So a user who normally authenticates with E2B_ACCESS_TOKEN and forgets/typos it in CI will be told to set the wrong env var and pointed to the wrong dashboard tab.

Why the existing code doesn't prevent it

authErrorBox has no combined mode — its switch only knows about the two individual key names. The new ensureAPIKeyOrAccessToken reuses the API-key branch verbatim, which conflicts with this PR's own intent of accepting either credential. The README was updated in the same PR to advertise both env vars (packages/cli/README.md: "provide E2B_API_KEY or E2B_ACCESS_TOKEN as an environment variable"), so the error path is now inconsistent with the documented behavior.

Step-by-step proof

  1. User runs e2b template list in CI with no ~/.e2b/config.json and an unset/typo'd E2B_ACCESS_TOKEN (and no E2B_API_KEY).
  2. list.ts calls ensureAPIKeyOrAccessToken().
  3. resolveAPIKey() returns undefined (no env var, no user config).
  4. resolveAccessToken() returns undefined for the same reason.
  5. The if (resolvedApiKey || resolvedAccessToken) branch is false.
  6. Code reaches console.error(authErrorBox('E2B_API_KEY')) (api.ts:106).
  7. The user sees: "…set the E2B_API_KEY environment variable. Visit https://e2b.dev/dashboard?tab=keys to get the API key." — with no mention that E2B_ACCESS_TOKEN would also have worked, and a link that is the wrong dashboard tab for their authentication method.

Impact

Purely a UX/diagnostic issue, not a functional break. The first line of the error ("Run e2b auth login") still points at a working remediation, so most interactive users recover. The degradation is concentrated on the CI/CD diagnostic path, where the env-var hint matters most. Because of that, nit severity is appropriate.

How to fix

Add a combined branch to authErrorBox (e.g. an 'E2B_API_KEY_OR_ACCESS_TOKEN' case) that lists both env vars and both dashboard URLs, and pass that key from ensureAPIKeyOrAccessToken. Alternatively, change authErrorBox to accept an array of key names and render a list. Either approach keeps the existing single-key callers (ensureAPIKey, ensureAccessToken) working unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant