grpc/grpc-go · error
extproc: header mutation disallowed by headerMutationRules f
Error message
extproc: header mutation disallowed by headerMutationRules for header key %q
What it means
Raised by (*HeaderMutationRules).ApplyAdditions (extconfig.go:143) when an external server (ext_proc/ext_authz) requests a header addition/modification whose key is rejected by the allow/disallow rules AND DisallowIsError is true. Without DisallowIsError the header is silently skipped (continue at extconfig.go:145); with it set, the whole mutation is treated as an error and the data-plane RPC is failed.
Source
Thrown at internal/xds/httpfilter/extconfig.go:143
for _, hvo := range hvos {
header := hvo.GetHeader()
key := header.GetKey()
if len(key) == 0 || key[0] == ':' || key == "host" || key != strings.ToLower(key) || len(key) > 16384 {
continue
}
value := header.GetValue()
if strings.HasSuffix(key, "-bin") {
value = string(header.GetRawValue())
}
if len(value) > 16384 {
continue
}
if !hmr.allow(key) {
if hmr.DisallowIsError {
return fmt.Errorf("extproc: header mutation disallowed by headerMutationRules for header key %q", key)
}
continue
}
// Perform the mutation on output metadata using the append_action
// field from the header value option.
switch hvo.GetAppendAction() {
case v3corepb.HeaderValueOption_APPEND_IF_EXISTS_OR_ADD:
input.Append(key, value)
case v3corepb.HeaderValueOption_ADD_IF_ABSENT:
if input.Get(key) == nil {
input.Set(key, value)
}
case v3corepb.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD:
input.Set(key, value)
case v3corepb.HeaderValueOption_OVERWRITE_IF_EXISTS:
if input.Get(key) != nil {
input.Set(key, value)View on GitHub (pinned to 03255a9237)
Solutions
- Widen the allow_expression regex (or narrow disallow_expression) so the header key in the %q message is permitted.
- Fix the external processor to only mutate headers allowed by the configured rules.
- If the rejected mutation is acceptable in your deployment, either turn off DisallowIsError (mutations will be silently dropped) or add the specific header pattern to the allowlist.
Example fix
// before
// mutation_rules: {
// allow_expression: { regex: "x-safe-.*" },
// disallow_is_error: { value: true }
// }
// // ext_proc sets "x-trace-id" -> error 369
//
// after: allow the additional header
// mutation_rules: {
// allow_expression: { regex: "x-(safe|trace)-.*" },
// disallow_is_error: { value: true }
// } Defensive patterns
Strategy: validation
Validate before calling
// Verify every header the processor will set is allowed BEFORE relying on it,
// honoring the same allow() logic at extconfig.go:203-214.
func headerAllowed(rules *httpfilter.HeaderMutationRules, key string) bool {
return rules.Allow(key) // expose/replicate hmr.allow if unexported
}
// Ensure your server only emits allowed keys:
func sanitizeSetHeaders(hmr *httpfilter.HeaderMutationRules, hvos []*v3corepb.HeaderValueOption) error {
for _, h := range hvos {
k := h.GetHeader().GetKey()
if k == "" || k != strings.ToLower(k) { continue }
if !headerAllowed(hmr, k) && hmr.DisallowIsError {
return fmt.Errorf("set header %q is disallowed", k)
}
}
return nil
} Try / catch
// When applying processor mutations, treat the error as fatal only in deny mode
// (applyMutations -> failProcStream at ext_proc.go:1377).
if err := hmr.ApplyAdditions(setHeaders, md); err != nil {
// DisallowIsError path: log the offending header and fail/contract the stream.
return status.Errorf(codes.Internal, "header mutation rejected: %v", err)
} Prevention
- Keep the mutation_rules allowlist in sync with the set of headers your processor actually mutates.
- Only enable disallow_is_error once you have confirmed the allowlist covers all server mutations.
- In your external processor, reject/validate its own requested mutations against the configured rules before sending.
When it happens
Trigger: An ext_proc server returns a HeaderMutation.set_headers entry for key K; hmr.allow(K) returns false at extconfig.go:141 and hmr.DisallowIsError is true, so the error is returned. This surfaces back through applyMutations (ext_proc.go:1377) and fails the ext_proc stream (and, in deny mode, the data-plane RPC).
Common situations: The mutation_rules allow_expression is too narrow (or disallow_expression too broad) for what the external processor legitimately needs to set; DisallowIsError was enabled for safety but the rules weren't widened to cover the server's actual mutations; the server tries to add a header like 'x-forwarded-for' that falls outside the allowlist.
Related errors
- extproc: header mutation disallowed by headerMutationRules f
- extproc: input metadata is nil
- httpfilter: %v
- extproc: invalid request body mode %v: want %q or %q
- extproc: invalid response body mode %v: want %q or %q
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/3dc06db60c706ab3.
Report an issue: GitHub.