grpc/grpc-go · error

extproc: header mutation disallowed by headerMutationRules f

Error message

extproc: header mutation disallowed by headerMutationRules for header %q

What it means

Raised by (*HeaderMutationRules).ApplyRemovals (extconfig.go:194) when the external server requests removal of a header whose key is rejected by the allow/disallow rules and DisallowIsError is true. Symmetric to error 369 but for header removals: without DisallowIsError the removal is silently skipped (extconfig.go:196), with it the mutation becomes an error and fails the ext_proc stream.

Source

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

// 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("extproc: input metadata is nil")
	}
	if hmr.DisallowAll {
		return nil
	}

	for _, header := range headersToRemove {
		if len(header) == 0 || header[0] == ':' || header == "host" || header != strings.ToLower(header) || len(header) > 16384 {
			continue
		}
		if !hmr.allow(header) {
			if hmr.DisallowIsError {
				return fmt.Errorf("extproc: header mutation disallowed by headerMutationRules for header %q", header)
			}
			continue
		}
		input.Delete(header)
	}
	return nil
}

func (hmr *HeaderMutationRules) allow(key string) bool {
	if hmr.DisallowExpr != nil && hmr.DisallowExpr.MatchString(key) {
		return false
	}
	if hmr.AllowExpr != nil && hmr.AllowExpr.MatchString(key) {
		return true
	}
	if hmr.AllowExpr != nil {
		return false
	}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Adjust the allow/disallow regex so the header name in %q is permitted for removal.
  2. Change the external processor to only remove allowed headers.
  3. If dropping that removal is acceptable, disable DisallowIsError (disallowed removals are then ignored instead of erroring).

Example fix

// before
//   mutation_rules: { allow_expression: { regex: "x-.*" }, disallow_is_error: { value: true } }
//   // ext_proc removes "authorization" -> error 371
//
// after
//   mutation_rules: { allow_expression: { regex: "(x-.*|authorization)" }, disallow_is_error: { value: true } }
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check that every header the processor removes is allowed (extconfig.go:192-195).
func sanitizeRemovals(hmr *httpfilter.HeaderMutationRules, remove []string) error {
    for _, k := range remove {
        if k == "" || k != strings.ToLower(k) { continue }
        if !headerAllowed(hmr, k) && hmr.DisallowIsError {
            return fmt.Errorf("remove header %q is disallowed", k)
        }
    }
    return nil
}

Try / catch

if err := hmr.ApplyRemovals(removeHeaders, md); err != nil {
    // DisallowIsError path: surface the offending header (ext_proc.go:1380 -> failProcStream).
    return status.Errorf(codes.Internal, "header removal rejected: %v", err)
}

Prevention

When it happens

Trigger: The ext_proc server returns remove_headers containing key K; hmr.allow(K) is false at extconfig.go:192 and DisallowIsError is true, so the error is returned through applyMutations (ext_proc.go:1380).

Common situations: allow_expression/disallow_expression forbid the header the processor tries to delete while DisallowIsError is on; e.g. the server attempts to strip 'authorization' but the rules only allow 'x-' prefixed headers.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/d12961801e100a81. Report an issue: GitHub.