A Chrome/Brave/Edge (Manifest V3) extension that transparently protects internal
services behind a header-based authentication barrier. It injects a configurable
secret header (a GUID, default X-BAUERGROUP-Auth) into outgoing requests to the
domains you configure; a reverse proxy / WAF in front of the service validates it.
Greenfield successor to the original
BAUERGROUP.Extension.Chrome.HeaderAuth. Built on a modern stack and a flexible rule model. The legacy repository is archived.
- Rule/profile model – any number of rules, each with its own domains, header name and secret. Different service groups can use different GUIDs.
- Professional UI – a compact status popup and a full options page (CRUD table, validated add/edit dialog, masked secret with reveal/generate/copy, domain chips, JSON import/export). German + English, switchable in-app.
- Cross-device sync – configuration syncs via the browser account
(
chrome.storage.sync); secrets can optionally be kept device-local per rule. - Enterprise rollout – central provisioning via
chrome.storage.managed(GPO/MDM). Managed rules are read-only with an organization badge. - Least privilege – only the canonical
*.app.bauer-group.comis granted by default; user-added domains request host access at runtime.
Extension ID (pinned): jncjhkagdjiiohjfmbpmlemdchbkjaib
WXT (Vite-based) · React 19 · Tailwind 4 · shadcn-pattern components (Radix) · Zod 4 · TypeScript (strict+) · ESLint flat + Prettier · Vitest · commitlint + simple-git-hooks.
npm install # also runs `wxt prepare` (generates .wxt types)
npm run dev # Chrome dev with HMR
npm run dev:firefox # Firefox dev with HMR
npm run typecheck
npm run lint
npm testnpm run build # unpacked build -> .output/chrome-mv3/ (for "Load unpacked")
npm run zip # distributable zip -> .output/*-chrome.zip
npm run zip:firefox # .output/*-firefox.zip
npm run sign:crx # signed .crx + updates.xml (needs .keys/key.pem locally)Three first-class artifacts are produced:
| Artifact | Command | Purpose |
|---|---|---|
Unpacked (dist) |
wxt build → .output/chrome-mv3/ |
Developer install via "Load unpacked" |
| Distributable ZIP | wxt zip → .output/*.zip |
Web Store upload / manual sharing |
Signed .crx + updates.xml |
npm run sign:crx |
Self-hosted force-install + auto-update |
npm install && npm run build- Open
chrome://extensions(orbrave://,edge://), enable Developer mode. - Load unpacked → select
.output/chrome-mv3.
The extension reaches managed workstations via enterprise force-install policy, through two parallel channels (same extension ID, one channel per machine):
- Self-hosted – signed
.crx+updates.xmlpublished as GitHub Release assets;update_urlpoints at…/releases/latest/download/updates.xml. - Chrome Web Store (unlisted) –
update_urlis the Web Store update service.
See docs/deployment/ for ready-to-use GPO .reg, macOS
configuration profile, and managed-config examples.
The signing key pair gives the extension its stable ID. The private key is
never committed (.keys/key.pem, gitignored). In CI it is provided as the
EXTENSION_PRIVATE_KEY secret. The matching public key is pinned as key in
wxt.config.ts. Losing the private key changes the ID and breaks force-install
policies — store it in the team password manager.
Provision it with npm run secrets:sync, which reads .keys/key.pem directly
and refuses to push a key that would derive a different extension ID. See
docs/secrets.md for the full set of repository secrets and
which ones are inherited from the organization instead.
- Least privilege by construction. The only API permissions are
storageanddeclarativeNetRequestWithHostAccess— the host-scoped variant, which carries no install warning at all. PlaindeclarativeNetRequestwould warn "Block content on any page" anddeclarativeNetRequestFeedback"Read your browsing history"; neither buys anything here. There is no content script and no blockingwebRequestlistener, so no extension code runs on any page or in any request path. - Any site can trigger a header-carrying request to a protected origin. DNR matches
the request URL, not who initiated it, so a third-party page embedding
<img src="https://app.bauer-group.com/…">gets the header attached. The secret itself stays unreadable to that page — the browser adds it below JavaScript's reach, and cross-origin reads still need CORS — but the request does pass the perimeter gate, leaving the app's own authentication as the control that matters. This has been true since v1. Constraining it means splitting the rule and addinginitiatorDomainsto the subresource half, which also cuts off legitimate cross-originfetchfrom other company origins; that trade-off has not been taken. - Shipped code has 0 known vulnerabilities. The dev-only audit findings trace to
wxt → web-ext-run(the Firefox dev runner) and never ship. - Secrets in sync are stored unencrypted in the browser account cloud (the cross-device-sync trade-off). For governed secrets, push them via managed policy, or disable "Sync secret across devices" on a rule to keep it device-local.
- WSS is best-effort and opt-in.
wssis not one of Chrome's four documented match-pattern schemes: it works at runtime but Web Store review rejects it inhost_permissions, so it sits inoptional_host_permissionsand is asked for at runtime — in the same prompt as thehttpsorigin, so one click covers both. Without that grant, HTTP requests still carry the header and only WebSocket upgrades go bare; the rules table shows a click-to-grant chip when that is the case. Header modification on upgrades initiated from a service worker is unreliable on top of that (Chromium limitation); page-initiated WebSockets work. - The rule status reflects the engine, not the config. A rule reads as active only
once the background has confirmed it is installed in the declarativeNetRequest
engine. A failed
updateDynamicRulesshows its error on the settings page instead of disappearing into an unhandled rejection.
MIT