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

  1. Stop requesting removal of headers the policy protects; only remove explicitly allowed names.
  2. Adjust allow_expression / disallow_expression to permit the intended removal.
  3. Set disallow_is_error=false if silent skip is acceptable for your threat model.
  4. 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

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


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