diff --git a/pkg/vmcp/config/validator.go b/pkg/vmcp/config/validator.go index e6766cf284..9ce1e54665 100644 --- a/pkg/vmcp/config/validator.go +++ b/pkg/vmcp/config/validator.go @@ -591,6 +591,16 @@ func (*DefaultValidator) validateCompositeToolRefs(refs []CompositeToolRef) erro return nil } +// The standalone header-forward middleware deliberately allows Authorization +// (an operator may legitimately forward it), but for vMCP passthrough the +// documented contract is stricter: Authorization and Cookie are rejected at +// startup because forwarding caller-supplied credentials verbatim to every +// backend is a credential-leak footgun, not a pass-through use case. +var vmcpRestrictedHeaders = map[string]bool{ + "Authorization": true, + "Cookie": true, +} + func (*DefaultValidator) validatePassthroughHeaders(cfg *Config) error { for i, name := range cfg.PassthroughHeaders { if name == "" { @@ -599,7 +609,7 @@ func (*DefaultValidator) validatePassthroughHeaders(cfg *Config) error { canonical := http.CanonicalHeaderKey(name) - if middleware.RestrictedHeaders[canonical] { + if middleware.RestrictedHeaders[canonical] || vmcpRestrictedHeaders[canonical] { return fmt.Errorf("passthroughHeaders[%d]: %q is a restricted header and cannot be forwarded", i, canonical) } diff --git a/pkg/vmcp/config/validator_test.go b/pkg/vmcp/config/validator_test.go index 62ee183d87..3efbbd64c0 100644 --- a/pkg/vmcp/config/validator_test.go +++ b/pkg/vmcp/config/validator_test.go @@ -1495,6 +1495,21 @@ func TestValidator_ValidatePassthroughHeaders(t *testing.T) { wantErr: true, errMsg: "X-Forwarded-For", }, + { + // Documented contract (virtualmcpserver-api.md): Authorization is + // rejected at startup. Forwarding caller-supplied credentials + // verbatim to every backend is a credential-leak footgun. + name: "Authorization is restricted", + headers: []string{"authorization"}, + wantErr: true, + errMsg: "Authorization", + }, + { + name: "Cookie is restricted", + headers: []string{"Cookie"}, + wantErr: true, + errMsg: "Cookie", + }, { name: "empty string header name is rejected", headers: []string{""},