grpc/grpc-go · error
header mutation disallowed by headerMutationRules for header
Error message
header mutation disallowed by headerMutationRules for header %q
What it means
Returned by ApplyRemovals when a header the ext_proc server wants to remove is blocked by HeaderMutationRules and DisallowIsError is true. Same policy mechanism as error 442, but on the removal path (extconfig.go:209). When DisallowIsError is false the removal is silently skipped.
Source
Thrown at internal/xds/httpfilter/extconfig.go:209
// The input metadata must not be nil.
func (hmr *HeaderMutationRules) ApplyRemovals(headersToRemove []string, input metadata.MD) error {
if hmr == nil {
hmr = &HeaderMutationRules{}
}
if input == nil {
return fmt.Errorf("input metadata is nil")
}
if hmr.DisallowAll {
return nil
}
for _, header := range headersToRemove {
if err := validateHeaderKey(header); err != nil {
return fmt.Errorf("invalid header mutation: %v", err)
}
if !hmr.allow(header) {
if hmr.DisallowIsError {
return fmt.Errorf("header mutation disallowed by headerMutationRules for header %q", header)
}
continue
}
input.Delete(header)
}
return nil
}
// validateHeaderKey returns a non-nil error if key may not be mutated by an
// external processing server, either because the key is reserved or because it
// is not a valid gRPC header name.
func validateHeaderKey(key string) error {
switch {
case len(key) == 0:
return fmt.Errorf("header key is empty")
case key[0] == ':':
return fmt.Errorf("header key %q is a pseudo-header", key)
case key == "host":View on GitHub (pinned to 0c51461d27)
Solutions
- Stop requesting removal of headers the policy protects; only remove explicitly allowed names.
- Adjust allow_expression / disallow_expression to permit the intended removal.
- Set disallow_is_error=false if silent skip is acceptable for your threat model.
- Cross-check the regex semantics against Go's RE2 (no backreferences, anchored differently).
Example fix
// before: server blanket-removes everything it did not set
for k := range incoming { resp.Remove(k) }
// after: respect an allowlist
for k := range incoming {
if allowedRemoval(k) { resp.Remove(k) }
} Defensive patterns
Strategy: validation
Validate before calling
// server-side: only request removal of policy-allowed headers
for _, k := range toRemove {
if !policy.Allowed(k) { continue }
resp.Remove(k)
} Prevention
- Share the allow/disallow regex config with the server.
- Consider disallow_is_error=false for resilience.
- Avoid blanket 'remove all' loops on the server.
- Re-test policy changes against the server's emitted set.
When it happens
Trigger: xDS config has mutation_rules.disallow_is_error=true and the ext_proc server returns a request to delete a header that fails allow() (matches disallow_expression, or allow_expression is set and does not match).
Common situations: Policy forbids deleting x-trace-* but the server tries to strip it; a generic 'remove all headers' implementation in the server runs afoul of a strict allow list; regex change deployed without updating the server.
Related errors
- header mutation disallowed by headerMutationRules for header
- invalid header mutation: value for header key %q exceeds the
- header key is empty
- header key %q is a pseudo-header
- header key %q is reserved
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/33f917aed82118f3.
Report an issue: GitHub.