Skip to content

fix(ext/http): reject Response-like return from serve handler#34416

Merged
littledivy merged 3 commits into
mainfrom
orch/divybot-244
May 29, 2026
Merged

fix(ext/http): reject Response-like return from serve handler#34416
littledivy merged 3 commits into
mainfrom
orch/divybot-244

Conversation

@divybot

@divybot divybot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

When the Deno.serve handler returns an object that has Response.prototype
in its chain but doesn't carry Deno's internal slot (e.g. a subclass that
skipped super(), or a polyfilled/foreign-realm Response), mapped in
ext/http/00_serve.ts reads inner.status with inner === undefined
and the whole serve loop tears down with:

Terminating Deno.serve loop due to unexpected error TypeError:
Cannot read properties of undefined (reading 'status')
    at mapped (ext:deno_http/00_serve.ts:...)

This bug is observable with the nitro + vite Deno template
(denoland/deno#33893, denoland/deno#34327) — the env-runner worker
spawned by Nitro's dev server returns such an object to Deno.serve
and the loop crashes on the very first request, surfacing as
[vite] Internal server error: socket hang up upstream.

The validation that catches this used to exist (added in #29086 and
removed when the surrounding fallback was deleted in #29105). Re-add a
focused check against the internal slot so this case is rejected by
the existing onError path instead of crashing the loop.

Test plan

  • New handleServeCallbackReturnsResponseLike + handleServeErrorCallbackReturnsResponseLike
    in tests/unit/serve_test.ts build a Response-prototyped object with
    no internal slot, return it from the handler / onError, and assert that
    the request still completes (recovered response / Internal Server Error)
    instead of the serve loop dying.
  • Existing handleServeCallbackReturn / handleServeErrorCallbackReturn
    tests cover the case where the handler returns a non-Response — message
    for that path is unchanged.

Closes denoland/orchid#244

divybot and others added 2 commits May 27, 2026 07:46
Subclasses of `Response` that skip `super()` (or Response polyfills
returning a `Response.prototype`-chained object) pass the existing
prototype check but don't carry Deno's internal slot. The `mapped`
function then reads `inner.status` with `inner === undefined` and the
whole `Deno.serve` loop tears down with:

  Terminating Deno.serve loop due to unexpected error TypeError:
  Cannot read properties of undefined (reading 'status')
      at mapped (ext:deno_http/00_serve.ts:...)

Reject those during validation so they flow through `onError` (or the
default 500) like any other invalid return value, instead of crashing
the loop.

Reproduces with `deno run -A npm:create-nitro-app workspace -t vite -p deno`
followed by `deno task dev` and hitting `/`, where the worker srvx
adapter returns such an object.

Closes denoland/orchid#244

Co-Authored-By: Divy Srivastava <[email protected]>
Comment thread ext/http/00_serve.ts Outdated
Comment on lines +577 to +583
// The Response prototype check above passes for Response-like objects
// (e.g. a subclass that skipped super(), or a Response from a different
// realm/polyfill). Those don't carry the internal slot we read from
// below, so reject them with a clear error instead of crashing the
// serve loop with "Cannot read properties of undefined (reading
// 'status')".
if (toInnerResponse(response) === undefined) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This would regress perf. It's now called twice in this function

Compute the inner response once and reuse it for both the
Response-like validation guard and the downstream status/header
reads, instead of calling toInnerResponse twice per request.

Co-Authored-By: Divy Srivastava <[email protected]>
@littledivy
littledivy merged commit b5c0dbb into main May 29, 2026
136 checks passed
@littledivy
littledivy deleted the orch/divybot-244 branch May 29, 2026 14:49
littledivy added a commit that referenced this pull request May 31, 2026
## Summary

When `respondWith` for the legacy `Deno.serveHttp` is called with an
object whose prototype chain matches `Response` but which doesn't carry
Deno's internal slot — e.g. `Object.create(Response.prototype)`, a
subclass that skipped `super()`, or a polyfilled/foreign-realm Response
—
the call crashes with:

```
TypeError: Cannot read properties of undefined (reading 'body')
    at respondWith (ext:deno_http/01_http.js:...)
```

`toInnerResponse(resp)` returns `undefined` in that case and the next
line reads `innerResp.body`. This mirrors the bug pattern that
`Deno.serve` had upstream (#34327), fixed for the
`00_serve.ts` path in #34416. The legacy `01_http.js` path was missed by
that change but is still callable: `Deno.serveHttp` is "soft-removed"
in Deno 2, not removed, and exposed via `Deno[Deno.internal]`.

Re-add the same focused guard against the internal slot here so
the case is rejected with a clear `TypeError` instead of crashing with
the cryptic `'body'` access.

## Test plan

New `httpServerRespondWithResponseLike` in `tests/unit/http_test.ts`
builds a `Response`-prototyped object with no internal slot (matching
the helper used by `handleServeCallbackReturnsResponseLike` in
`serve_test.ts`), passes it to `respondWith` from a `Deno.serveHttp`
request event, and asserts that the call rejects with a `TypeError`
whose message points the user at the realm/constructor mismatch —
instead of crashing with `Cannot read properties of undefined
(reading 'body')`.

Verified locally:

```
$ cargo test --test unit -- http_test
test unit::http_test ... ok (19594ms)
1 tests passed
```

Closes denoland/orchid#340

Co-authored-by: divybot <[email protected]>
Co-authored-by: Divy Srivastava <[email protected]>
littledivy added a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
…nd#34416)

## Summary

When the `Deno.serve` handler returns an object that has
`Response.prototype`
in its chain but doesn't carry Deno's internal slot (e.g. a subclass
that
skipped `super()`, or a polyfilled/foreign-realm Response), `mapped` in
`ext/http/00_serve.ts` reads `inner.status` with `inner === undefined`
and the whole serve loop tears down with:

```
Terminating Deno.serve loop due to unexpected error TypeError:
Cannot read properties of undefined (reading 'status')
    at mapped (ext:deno_http/00_serve.ts:...)
```

This bug is observable with the `nitro` + `vite` Deno template
(`denoland#33893`, `denoland#34327`) — the env-runner worker
spawned by Nitro's dev server returns such an object to `Deno.serve`
and the loop crashes on the very first request, surfacing as
`[vite] Internal server error: socket hang up` upstream.

The validation that catches this used to exist (added in denoland#29086 and
removed when the surrounding fallback was deleted in denoland#29105). Re-add a
focused check against the internal slot so this case is rejected by
the existing `onError` path instead of crashing the loop.

## Test plan

- New `handleServeCallbackReturnsResponseLike` +
`handleServeErrorCallbackReturnsResponseLike`
in `tests/unit/serve_test.ts` build a `Response`-prototyped object with
no internal slot, return it from the handler / `onError`, and assert
that
the request still completes (recovered response / `Internal Server
Error`)
  instead of the serve loop dying.
- Existing `handleServeCallbackReturn` /
`handleServeErrorCallbackReturn`
tests cover the case where the handler returns a non-Response — message
  for that path is unchanged.

Closes denoland/orchid#244

---------

Co-authored-by: divybot <[email protected]>
Co-authored-by: Divy Srivastava <[email protected]>
littledivy added a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
…#34589)

## Summary

When `respondWith` for the legacy `Deno.serveHttp` is called with an
object whose prototype chain matches `Response` but which doesn't carry
Deno's internal slot — e.g. `Object.create(Response.prototype)`, a
subclass that skipped `super()`, or a polyfilled/foreign-realm Response
—
the call crashes with:

```
TypeError: Cannot read properties of undefined (reading 'body')
    at respondWith (ext:deno_http/01_http.js:...)
```

`toInnerResponse(resp)` returns `undefined` in that case and the next
line reads `innerResp.body`. This mirrors the bug pattern that
`Deno.serve` had upstream (denoland#34327), fixed for the
`00_serve.ts` path in denoland#34416. The legacy `01_http.js` path was missed by
that change but is still callable: `Deno.serveHttp` is "soft-removed"
in Deno 2, not removed, and exposed via `Deno[Deno.internal]`.

Re-add the same focused guard against the internal slot here so
the case is rejected with a clear `TypeError` instead of crashing with
the cryptic `'body'` access.

## Test plan

New `httpServerRespondWithResponseLike` in `tests/unit/http_test.ts`
builds a `Response`-prototyped object with no internal slot (matching
the helper used by `handleServeCallbackReturnsResponseLike` in
`serve_test.ts`), passes it to `respondWith` from a `Deno.serveHttp`
request event, and asserts that the call rejects with a `TypeError`
whose message points the user at the realm/constructor mismatch —
instead of crashing with `Cannot read properties of undefined
(reading 'body')`.

Verified locally:

```
$ cargo test --test unit -- http_test
test unit::http_test ... ok (19594ms)
1 tests passed
```

Closes denoland/orchid#340

Co-authored-by: divybot <[email protected]>
Co-authored-by: Divy Srivastava <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants