fix(ext/http): reject Response-like return from serve handler#34416
Merged
Conversation
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]>
littledivy
requested changes
May 29, 2026
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) { |
Member
There was a problem hiding this comment.
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
approved these changes
May 29, 2026
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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When the
Deno.servehandler returns an object that hasResponse.prototypein its chain but doesn't carry Deno's internal slot (e.g. a subclass that
skipped
super(), or a polyfilled/foreign-realm Response),mappedinext/http/00_serve.tsreadsinner.statuswithinner === undefinedand the whole serve loop tears down with:
This bug is observable with the
nitro+viteDeno template(
denoland/deno#33893,denoland/deno#34327) — the env-runner workerspawned by Nitro's dev server returns such an object to
Deno.serveand the loop crashes on the very first request, surfacing as
[vite] Internal server error: socket hang upupstream.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
onErrorpath instead of crashing the loop.Test plan
handleServeCallbackReturnsResponseLike+handleServeErrorCallbackReturnsResponseLikein
tests/unit/serve_test.tsbuild aResponse-prototyped object withno internal slot, return it from the handler /
onError, and assert thatthe request still completes (recovered response /
Internal Server Error)instead of the serve loop dying.
handleServeCallbackReturn/handleServeErrorCallbackReturntests cover the case where the handler returns a non-Response — message
for that path is unchanged.
Closes denoland/orchid#244