HarperFast / HarperFast/harper

Response.redirect() throws TypeError: immutable — response pipeline mutates headers after handler returns

Open
#1,698 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
89
Forks
10
Avg merge
2d 6h
Merged PRs (30d)
200

Description

## Summary

`Response.redirect(url, status)` constructs a `Response` whose `Headers` object has its guard set to `"immutable"` per the Fetch spec. Harper's own HTTP response pipeline mutates the handler's returned `Response`'s headers *after* the handler returns (e.g. to add CORS/instrumentation headers) — against an immutable-guarded `Headers` this throws `TypeError: immutable`, turning the request into a 500.

This fires on **any** resource `post()`/`get()` handler that returns `Response.redirect(...)` — not an edge case, the standard-library redirect constructor itself is unusable.

## Observed

Found while writing the first real (non-mocked, live-Harper) integration test for an OAuth authorize/deny flow. A minimal resource:

```js
import { Resource } from "@harperfast/harper";

export class Redirect extends Resource {
async post() {
return Response.redirect("https://example.com/callback?code=abc", 302);
}
}
```

`curl -X POST http://localhost:9926/Redirect` against an unmodified Harper 5.1.15 origin/build returns a 500 with `TypeError: immutable` in the logs, instead of a 302.

## Workaround (currently carried, would like to retire)

Construct the `Response` manually instead of via the spec's redirect helper — same wire bytes, but the default (mutable) `"response"` Headers guard, so Harper's later header mutation succeeds:

```js
function redirectTo(url, status = 302) {
return new Response(null, { status, headers: { Location: url } });
}
```

Identical behavior on the wire; it just avoids the immutable guard `Response.redirect()` sets. Every consumer of the Fetch-standard constructor has to know to avoid it, which isn't discoverable until it 500s in production.

## Expected

Harper's response pipeline should not mutate a `Response`'s `Headers` when its guard is `immutable` — either clone the `Headers` before mutating (spec-compliant `Response.redirect()` stays usable), or honor the guard and skip the mutation for that response. A `Response.redirect()` return from any resource handler should behave identically to a hand-built 3xx Response.

## Environment

- `@harperfast/harper` 5.1.15 (npm), reproduced via native Node (not Docker)
- macOS arm64, Node v25.9.0

## Related

Relates to the #1660 epic (HTTP response security hardening / headers). Also touches REST HTTP-semantics more broadly (#1682).

Contributor guide

Open the contributing guide

Research direction

Start with the live Harper integration reproduction: run the POST request against a resource handler returning Response.redirect(), then trace the response pipeline's post-handler header mutation. Done means the redirect returns HTTP 302 rather than a 500, and the pipeline handles the immutable Headers guard without breaking the expected response headers.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.