grpc/grpc-go · error

header key %q is reserved

Error message

header key %q is reserved

What it means

Returned by validateHeaderKey when the key is exactly "host". The host/authority is reserved (it maps to the :authority pseudo-header in HTTP/2), so the transport owns it and an external processor may not mutate it directly. This is the third case in the validation switch.

Source

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

			}
			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":
		return fmt.Errorf("header key %q is reserved", key)
	case strings.HasPrefix(key, "grpc-"):
		return fmt.Errorf("header key %q is in the reserved 'grpc-' space", key)
	case key != strings.ToLower(key):
		return fmt.Errorf("header key %q is not lowercase", key)
	case len(key) > maxHeaderSize:
		return fmt.Errorf("header key exceeds the maximum length of %d bytes", maxHeaderSize)
	}
	return imetadata.ValidateKey(key)
}

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 {

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Do not mutate the "host" header from an ext_proc server.
  2. To influence authority, configure route host-rewrite in the xDS RouteConfiguration instead.
  3. Skip "host" explicitly when building mutations from an inbound header set.
  4. Document the reserved set (host, :*, grpc-*) in the server's authoring guide.

Example fix

// before
emit("host", newAuthority)
// after: use route rewrite, not header mutation
// (configure virtual host rewrite: request_headers_to_add is NOT for host)
Defensive patterns

Strategy: validation

Validate before calling

// server-side: never mutate host
if key == "host" { return /* skip */ }

Prevention

When it happens

Trigger: The ext_proc server returns an add/remove/modify mutation for the literal header name "host" (lowercase). validateHeaderKey matches key == "host" at extconfig.go:227.

Common situations: Server ported from HTTP/1 attempts to rewrite Host; a generic 'copy all headers' loop includes host; a security filter tries to strip host to hide the upstream.

Related errors


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