grpc/grpc-go · error

header mutation disallowed by headerMutationRules for header

Error message

header mutation disallowed by headerMutationRules for header key %q

What it means

Returned by ApplyAdditions when a header the ext_proc server wants to add/modify is rejected by the configured HeaderMutationRules (AllowExpr/DisallowExpr) AND DisallowIsError is true. Without DisallowIsError the mutation is silently skipped (continue); with it, the data-plane RPC fails with gRPC status Unknown. The allow() function returns false if the key matches DisallowExpr or does not match a configured AllowExpr.

Source

Thrown at internal/xds/httpfilter/extconfig.go:158

		}

		value := header.GetValue()
		if strings.HasSuffix(key, "-bin") {
			value = string(header.GetRawValue())
		}
		if len(value) > maxHeaderSize {
			return fmt.Errorf("invalid header mutation: value for header key %q exceeds the maximum length of %d bytes", key, maxHeaderSize)
		}
		// ValidatePair rejects values carrying bytes outside %x20-%x7E. It
		// skips the value check for "-bin" keys, whose values the transport
		// base64 encodes.
		if err := imetadata.ValidatePair(key, value); err != nil {
			return fmt.Errorf("invalid header mutation: %v", err)
		}

		if !hmr.allow(key) {
			if hmr.DisallowIsError {
				return fmt.Errorf("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 0c51461d27)

Solutions

  1. Update the ext_proc server so it only mutates headers permitted by the configured allow/disallow regexes.
  2. If the mutation is intended, broaden allow_expression (or narrow disallow_expression) in the xDS HeaderMutationRules.
  3. If failing the RPC is too aggressive, set disallow_is_error=false to revert to silent skip behavior.
  4. Verify the regex with the same RE2 engine the client uses (Go regexp) to avoid dialect differences.

Example fix

// xDS EnvoyFilter/config - before
mutation_rules:
  allow_expression: { regex: "^x-trace-.*$" }
  disallow_is_error: true
// server sends "x-auth-token" -> RPC fails
// after: allow both, or downgrade to skip
mutation_rules:
  allow_expression: { regex: "^x-(trace|auth)-.*$" }
  disallow_is_error: true
Defensive patterns

Strategy: validation

Validate before calling

// server-side: apply the same allow/disallow regexes before sending
func allowed(key string, allow, disallow *regexp.Regexp) bool {
  if disallow != nil && disallow.MatchString(key) { return false }
  if allow != nil { return allow.MatchString(key) }
  return true
}

Prevention

When it happens

Trigger: xDS config sets mutation_rules with disallow_is_error=true and either an allow_expression that does not match the key, or a disallow_expression that does match it. The ext_proc server then attempts to mutate that key, hitting extconfig.go:158.

Common situations: A security policy disallows mutating x-auth-* but the ext_proc server tries to set it; an allow list was tightened and the server was not updated; the regex was written for Envoy semantics and behaves differently under Go's RE2.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/05323316d0ce81f0. Report an issue: GitHub.