security(hermes): strip inbound X-Hux-* at the router boundary

Defense in depth: the tenant router deletes every browser-supplied
X-Hux-* header before asserting its own HUX identity headers, so no
client can forge subject, trust class, or relay key. Regression covers
X-Hux-Subject, X-Hux-Trust and X-Hux-Relay-Key forgeries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
This commit is contained in:
jenkins 2026-08-24 04:12:03 -03:00
parent b66c762f5d
commit bd63b568e1
2 changed files with 14 additions and 0 deletions

View File

@ -395,6 +395,14 @@ func (router *tenantRouter) ServeHTTP(writer http.ResponseWriter, request *http.
} {
outbound.Header.Del(header)
}
// HUX identity is asserted here, at the authenticated tenant boundary.
// Delete every browser-supplied HUX header before adding the one opaque
// subject that the same-pod WebUI BFF may trust.
for header := range outbound.Header {
if strings.HasPrefix(strings.ToLower(header), "x-hux-") {
outbound.Header.Del(header)
}
}
// Each Keycloak subject is already mapped to exactly one isolated pod.
// Give that pod a non-sensitive, router-asserted identity while retaining
// only its own WebUI cookies; OAuth credentials never reach the backend.

View File

@ -106,6 +106,9 @@ func TestRouterProxiesWebUIAndAddsTelegramShortcut(t *testing.T) {
if request.Header.Get(trustedTenantHeader) != "slot-0" {
t.Fatalf("trusted tenant identity was not asserted: %q", request.Header.Get(trustedTenantHeader))
}
if request.Header.Get("X-Hux-Subject") != "" || request.Header.Get("X-Hux-Trust") != "" || request.Header.Get("X-Hux-Relay-Key") != "" {
t.Fatal("browser-supplied HUX trust headers reached the tenant backend")
}
cookies := request.Cookies()
if len(cookies) != 2 || cookies[0].Name != tenantSessionCookie || cookies[1].Name != tenantProfileCookie {
t.Fatalf("unexpected backend cookies: %#v", cookies)
@ -121,6 +124,9 @@ func TestRouterProxiesWebUIAndAddsTelegramShortcut(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "/", nil)
request.Header.Set("X-Forwarded-User", "subject")
request.Header.Set("X-Auth-Request-User", "subject")
request.Header.Set("X-Hux-Subject", "usr_0123456789abcdef")
request.Header.Set("X-Hux-Trust", "worker")
request.Header.Set("X-Hux-Relay-Key", "browser-forgery")
request.Header.Set("Cookie", "_oauth2_proxy=secret; "+tenantSessionCookie+"=session; "+tenantProfileCookie+"=default")
response := httptest.NewRecorder()
router.ServeHTTP(response, request)