A small, dependency-free Chrome extension for setting, appending, and removing
HTTP request headers, built on Manifest V3's declarativeNetRequest API only.
No webRequest permission, ever. HeaderWright receives no request or response
events: it hands declarative rules to Chrome, and Chrome applies them
internally. That's verifiable by reading the manifest rather than trusting a
description.
- Header profiles: set / append / remove request headers
- Per-profile domain scoping (subdomains included)
- Master on/off toggle with a badge showing that toggle's state, plus a distinct state when Chrome rejects a rule registration
- Deterministic JSON import/export — canonical key-sorted, byte-stable output, so configs can be shared and versioned in git
- No response header modification — a later version
- No account, no sync, no backend — profiles live in local extension storage
- No telemetry
Install from the Chrome Web Store
To run from source instead, see Development.
HeaderWright does not request access to any site at install time. When you add a profile scoped to a domain, the browser prompts for permission on that domain only — one extra click per new domain, not once for everything.
Why:
-
Bounded blast radius. If the extension is ever compromised — a bad update, a hijacked account, a supply-chain slip — the damage is limited to domains you've actually granted, not every site you visit.
-
Revocable per site. Deleting a profile, editing its domains, or importing a config that drops a domain all release that domain's permission — unless another profile still references it. Verified against
chrome.permissions.getAll(): the grant is gone immediately, not deferred. -
What you see is what's true. HeaderWright's own popup is the honest display: a green dot means the grant is currently held, a gray dot means it isn't and the headers will not apply. Both are read from Chrome at render time rather than remembered.
One caveat, found while verifying this: the site list under
chrome://extensions→ Details is not a reliable view of what is currently granted. It has been observed listing a domain thatchrome.permissions.getAll()reports as not granted. Trust the popup, or the API, over that panel. -
Lighter store review. Broad host permissions draw more scrutiny from the Chrome Web Store; asking for nothing until it's needed avoids that by construction, not by explanation.
This costs one extra permission prompt the first time you scope a profile to a new domain. That's the tradeoff, made deliberately.
A note on the prompt's wording: Chrome phrases every host grant as "read and
change your data" on the site. That describes the permission class, not this
extension — with declarativeNetRequest only, the browser applies the rules
itself and no extension code observes any request. The manifest is the proof.
A note on prompts that don't appear: re-adding a domain you previously removed
may produce no dialog at all. The grant is still acquired — Chrome appears
to suppress the prompt for a host you have already consented to for this
extension. Nothing is hidden from you when this happens: the popup's dot,
chrome.permissions.contains() and chrome.permissions.getAll() all agree
that the grant is held.
How far that suppression reaches is only partly established. Uninstalling the extension does clear it — reinstalling and granting the same host prompts again, observed twice. Whether it is scoped to the browsing session or persists indefinitely for an install that stays in place is not yet verified; settling it needs a fresh browser profile, and it is recorded here as open rather than guessed at. The practical consequence either way is that the absence of a prompt is not evidence that a grant was skipped — read the dot.
No build step — the extension/ directory is the extension. Load it
unpacked from chrome://extensions with Developer mode on.
Run the selftests with node test/selftest.mjs from the repo root (no
dependencies). The suite covers rule construction and the canonical export
format, and ends with a check-count tripwire: adding or removing checks
requires updating EXPECTED_CHECKS in the same commit.
The suite verifies construction, not application — a rule can be built correctly and still no-op on a domain without a permission grant, so release verification also includes a manual smoke test against a live endpoint. Those steps are written down in test/SMOKE.md rather than left to memory.
Each minor version adds exactly one feature; patch versions are fixes only, never features.
MIT — see LICENSE.