mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Document the proxy auth header trust boundary
The proxy auth docs never told operators that the identity and role headers must be replaced rather than appended, nor that Pulse has to be unreachable except through the proxy. Both are prerequisites for the scheme being safe at all, and neither is enforceable from inside Pulse. The append case is the sharp edge: Pulse reads the first value of a repeated header, so a client-supplied role header that arrives ahead of the proxy's value decides the admin verdict. The client does not need the shared secret to do it, because the proxy attaches the secret itself. Verified against a scratch instance: sending "X-Proxy-Roles: user" then "admin" yields 403 while "admin" then "user" yields 200 on /api/system/settings. Documented rather than fixed in code on purpose. Matching any value instead of the first would make injection strictly easier, and rejecting repeated headers outright would break identity providers that legitimately emit one header per group. The trust boundary is the proxy's to hold.
This commit is contained in:
@@ -27,6 +27,15 @@ Setting `PROXY_AUTH_ROLE_HEADER` turns on role gating. From then on admin access
|
||||
|
||||
If you intentionally want every proxy-authenticated user to be an admin, leave `PROXY_AUTH_ROLE_HEADER` unset and protect Pulse entirely at the proxy/IdP layer.
|
||||
|
||||
## ⚠️ Header Trust Boundary
|
||||
|
||||
Pulse trusts these headers completely — they *are* the identity and the privilege decision. Two deployment requirements make that safe, and both are yours to enforce:
|
||||
|
||||
1. **Your proxy must _replace_ these headers, never append to them.** On every request the proxy has to discard any client-supplied copy of `X-Proxy-Secret`, your user header, and your role header, then set its own. Pulse reads the **first** value of a repeated header, so a client-supplied `X-Proxy-Roles: admin` that arrives ahead of your proxy's value wins — and because the proxy supplies the shared secret itself, the client never needs to know it. The examples below use nginx `proxy_set_header` and Traefik `customRequestHeaders`, which replace; be careful with anything that adds a header rather than setting it.
|
||||
2. **Pulse must not be reachable except through the proxy.** Anyone who can connect directly and knows `PROXY_AUTH_SECRET` can assert any username and any role. Bind Pulse to the proxy's network or to localhost.
|
||||
|
||||
Neither of these can be enforced from inside Pulse: a forged header and a genuine one are indistinguishable once they arrive.
|
||||
|
||||
## 📦 Examples
|
||||
|
||||
### Authentik (with Traefik)
|
||||
|
||||
Reference in New Issue
Block a user