DEV Community

Cover image for I built Header Relay: a Chrome extension that captures response headers and replays them on later requests
mktbsh
mktbsh

Posted on

I built Header Relay: a Chrome extension that captures response headers and replays them on later requests

If you develop against APIs that hand you a session token, trace ID, or gateway header in a response, and expect it back on the next request, you know the dance: copy the value from DevTools, paste it into ModHeader or curl, repeat every time it rotates.

The fix seems obvious: let the browser itself relay the header. But the API you would reach for — a blocking webRequest listener that rewrites request headers — is exactly what Manifest V3 removed. That constraint shaped most of the design below.

Header Relay - Chrome Web Store

Capture HTTP response headers and relay fixed or captured headers to matching browser requests.

favicon chromewebstore.google.com

Header Relay automates the loop anyway, entirely inside the browser.

What it does

Header Relay is a Manifest V3 Chrome extension for API development and testing:

  • Captured headers — watch responses from a target origin, keep a configured header's value (e.g. x-session-token) in session memory, and attach it to later matching requests automatically.
  • Fixed headers — always attach static headers like x-client-id: my-app to a target origin.
  • Origin-scoped profiles — each profile targets specific origins (localhost, staging, internal envs), and multiple profiles can run at once.
  • Excluded paths — glob patterns to keep relay-managed headers off assets or specific endpoints, with a built-in tester.
  • URL Probe — paste a URL and see whether it matches, which profiles apply, and exactly which headers would be attached — before saving.

Why not a proxy or a static header extension?

  • Static header extensions (ModHeader-style) can't follow dynamic values that the server rotates per session.
  • A local proxy (mitmproxy, Charles) can, but it means TLS interception on your machine — heavyweight for "I just want the browser to keep sending this token back".

Header Relay sits in that gap: no proxy, no DevTools babysitting. And if ModHeader's recent removal from the stores left you shopping for a replacement, the static-header use case is covered too.

How it works under the hood

So how do you rewrite request headers on MV3 without blocking webRequest? By splitting observation from rewriting:

  1. webRequest (non-blocking) observes response headers for enabled target origins and stores captured values in storage.session (memory only).
  2. Every enabled profile + its session state is compiled into a single intermediate representation, which is synced to one declarativeNetRequest session ruleset. Attach rules add fixed/captured headers; remove rules strip them on excluded paths.
  3. On the next matching request, Chrome's DNR engine attaches the headers natively — no request-time JavaScript involved.

One compile path

Origin matching and path exclusion are needed in three places: DNR rule generation, the URL Probe, and the runtime status view. Implement them three times and they will drift apart eventually — that's where bugs like "the Probe says the header attaches, but the real request never gets it" come from. So all three consume the output of a single compileConfig() step, and the matching logic exists in exactly one place.

Conflicts are errors

What happens when two enabled profiles set the same header on the same origin? DNR rules carry a priority field, so silent auto-resolution would be the easy answer. But in a testing tool, "which value actually got attached?" is worse than attaching nothing at all. So a conflict is a compile error: all owned rules are removed, the UI shows what conflicts with what, and disabling either profile recovers.

Privacy and permissions

This category of extension deserves suspicion, so the design is deliberately conservative:

  • Captured values live only in in-memory session storage — cleared on browser restart, extension reload, profile disable, or permission revocation. Masked by default in the UI.
  • No external transmission. Nothing is sent to me or any analytics backend. Configured headers go only to the target origins you configured.
  • Host permissions are optional and per-origin: adding a target origin triggers a permissions.request() from a user gesture. No grant → no rules, fail-closed. Only http://localhost/* is bundled for the default local workflow.
  • Response-only / transport headers (Set-Cookie, Host, Content-Length, Transfer-Encoding, …) are rejected outright; sensitive names like Authorization are allowed with an explicit warning.

Stack

WXT + TypeScript + SolidJS + Tailwind CSS. The runtime touches the browser through three ports only (storage / DNR / audit), so the whole extension runs headless in Vitest with in-memory adapters — config in, fake responses, assert on the resulting ruleset, no module mocks.

Try it

Header Relay - Chrome Web Store

Capture HTTP response headers and relay fixed or captured headers to matching browser requests.

favicon chromewebstore.google.com

It ships with a localhost profile out of the box (x-session-token capture + a fixed x-client-id), so pnpm dev on port 3000 works immediately.

The source itself isn't public, but bug reports and feature requests are tracked openly on GitHub issues. Feedback very welcome — especially edge cases in your header workflows that it doesn't cover yet.

Top comments (0)