grpc/grpc-go · error

invalid header mutation: value for header key %q exceeds the

Error message

invalid header mutation: value for header key %q exceeds the maximum length of %d bytes

What it means

Returned by HeaderMutationRules.ApplyAdditions when an external processing server sends a header ADD/MODIFY mutation whose value (after resolving -bin raw bytes) is longer than 16384 bytes (maxHeaderSize). The client enforces this cap on mutations it receives from the ext_proc server on the data plane, because oversized header values are not valid gRPC metadata and would break framing. The key is reported in the message; the cap is a compile-time constant.

Source

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

		return fmt.Errorf("input metadata is nil")
	}
	if hmr.DisallowAll {
		return nil
	}

	for _, hvo := range hvos {
		header := hvo.GetHeader()
		key := header.GetKey()
		if err := validateHeaderKey(key); err != nil {
			return fmt.Errorf("invalid header mutation: %v", err)
		}

		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() {

View on GitHub (pinned to 0c51461d27)

Solutions

  1. In the ext_proc server, chunk or drop the large value before adding it to HeaderMutation; keep each header value under 16384 bytes.
  2. If the value is binary, send it under a key ending in -bin so it is transported as raw bytes, and verify the decoded length still fits.
  3. Move large out-of-band data to the message body or a side channel instead of headers.
  4. If you control neither server, set failure_mode_allow so the data-plane RPC is not failed by ext_proc misbehavior (this only changes failure handling, not the validation).

Example fix

// ext_proc server (Go) - before
hdr := &corev3.HeaderValueOption{
  Header: &corev3.HeaderValue{Key: "x-blob", Value: hugeString},
}
// after: cap and reject/truncate server-side
const maxHeaderSize = 16384
if len(hugeString) > maxHeaderSize {
  return status.Error(codes.InvalidArgument, "x-blob too large")
}
hdr := &corev3.HeaderValueOption{
  Header: &corev3.HeaderValue{Key: "x-blob", Value: hugeString},
}
Defensive patterns

Strategy: validation

Validate before calling

// ext_proc server-side guard before adding a mutation
const maxHeaderSize = 16384
func safeAdd(m *corev3.HeaderValueOption) error {
  v := m.GetHeader().GetValue()
  if strings.HasSuffix(m.GetHeader().GetKey(), "-bin") {
    v = string(m.GetHeader().GetRawValue())
  }
  if len(v) > maxHeaderSize {
    return fmt.Errorf("header %q value too large: %d > %d", m.GetHeader().GetKey(), len(v), maxHeaderSize)
  }
  return nil
}

Try / catch

// grpc-go client: ext_proc returns errors via RPC failure; handle in stream loop
if err := procStream.Recv(); err != nil {
  if strings.Contains(err.Error(), "exceeds the maximum length") {
    log.Warn("ext_proc sent oversized header; check server")
  }
  return err
}

Prevention

When it happens

Trigger: An ext_proc server returns a HeaderMutation with an appended/overwritten HeaderValueOption whose value field exceeds 16 KiB. Triggered during ApplyAdditions at extconfig.go:146 while the gRPC client applies the server's response to the outgoing/incoming metadata.

Common situations: The ext_proc server tries to forward a large JWT, correlation blob, or base64-encoded payload as a single header; a server bug serializes a whole object into one header; the server was written for Envoy (higher limits) and is reused against grpc-go's stricter cap.

Related errors


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