Skip to content

fix(js-sdk): detect Request inputs by shape, not instanceof, in the Node fetch bridge#1610

Open
himself65 wants to merge 1 commit into
e2b-dev:mainfrom
himself65:fix/realm-safe-request-detection
Open

fix(js-sdk): detect Request inputs by shape, not instanceof, in the Node fetch bridge#1610
himself65 wants to merge 1 commit into
e2b-dev:mainfrom
himself65:fix/realm-safe-request-detection

Conversation

@himself65

Copy link
Copy Markdown

Symptom

In an app embedding the JS SDK, every control-plane call (Sandbox.create, Sandbox.list, …) crashed with:

TypeError: Failed to parse URL from [object Request]
    at fetch (…/undici/index.js:155:10)
    at wrapped (…/e2b/src/api/http2.ts:90)
    at …/e2b/src/api/inflight.ts:73

Root cause

toUndiciRequestInput (and the in-flight limiter's signal extraction) decide Request-ness with input instanceof Request. That brand check misses any Request minted by a different Request class — and replaced/duplicated Request classes are a real thing in Node processes: runtimes and tools swap globalThis.Request exactly like they swap globalThis.fetch (test environments, instrumentation, server shims). src/undici.ts already late-binds the global fetch for this very reason ("msw, instrumentation may replace globalThis.fetch") — this PR extends the same reasoning to Request.

The concrete case we debugged: the host app served HTTP with @hono/node-server, which installs a lightweight class Request extends GlobalRequest shim onto globalThis at import — and the dependency tree contained multiple copies of that shim (different versions via different packages), each with its own class. A Request built by one copy's class fails instanceof against the other copy's, so the SDK passed it verbatim into the npm-undici fetch, whose own brand check also can't recognize it → it coerces the object to a URL string → new URL('[object Request]') throws. Sandbox creation and listing died on every call; which copy "wins" the global is import-order-dependent, so the failure appears and disappears with unrelated dependency changes.

Fix

Treat anything carrying url + method as a Request (isRequestLike in src/utils.ts) and destructure it into the plain (url, init) pair undici accepts — the existing destructuring already produces exactly that. Two call sites:

  • toUndiciRequestInput (src/undici.ts) — the crash above;
  • limitConcurrency (src/api/inflight.ts) — a foreign-class Request's abort signal was silently ignored while the request waited for a slot.

No behavior change for native Requests (the instanceof fast path still short-circuits), strings, or URL objects (no method, so the shape check can't match them).

Tests

Both new tests reproduce the real-world event faithfully with sibling subclasses of the native Request (instances of one are fully functional Requests but fail instanceof against the other) plus vi.stubGlobal('Request', …):

  • tests/undici.test.ts — a foreign-class Request must reach the (injected fake) undici fetch destructured as (url, init), not verbatim;
  • tests/api/inflight.test.ts — an already-aborted foreign-class Request must reject with AbortError without consuming a slot.

Red/green verified: with src/ reverted, exactly these two tests fail; with the fix, vitest run tests/undici.test.ts tests/api/inflight.test.ts = 11/11, and pnpm build (tsc + tsdown) + pnpm lint are clean. Changeset included (e2b: patch).

🤖 Generated with Claude Code

…ode fetch bridge

A Request minted by a different Request class than the current
globalThis.Request fails the brand checks in toUndiciRequestInput and
limitConcurrency. Runtimes and tools replace globalThis.Request the same
way they replace globalThis.fetch (test environments, instrumentation,
server shims such as @hono/node-server) — and two copies of one shim can
coexist in a process, each installing its own class, so a Request built
by one class is checked against the other and disowned.

The miss is fatal on the undici path: the foreign Request is passed to
undici's fetch verbatim, fails undici's own brand check too, gets
coerced to a URL string, and every API call crashes with
'Failed to parse URL from [object Request]' (observed in production-like
apps embedding the SDK next to @hono/node-server: sandbox create/list
died on every call).

Detect Requests by shape instead (url + method both present) and
destructure them into the plain (url, init) pair undici accepts; the
in-flight limiter now also honors the abort signal of a foreign-class
Request while it waits for a slot.

Both new tests reproduce the real-world event with sibling subclasses of
the native Request plus a stubbed global, and fail without the fix.
@himself65
himself65 requested a review from mishushakov as a code owner July 25, 2026 08:23
@cla-bot

cla-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have @himself65 on file. You can sign our CLA at https://e2b.dev/docs/cla . Once you've signed, post a comment here that says '@cla-bot check'

@changeset-bot

changeset-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c58a1e8

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

This PR includes changesets to release 1 package
Name Type
e2b 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

@himself65

Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label Jul 25, 2026
@cla-bot

cla-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

The cla-bot has been summoned, and re-checked this pull request!

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