grpc/grpc-go · error
header key %q is a pseudo-header
Error message
header key %q is a pseudo-header
What it means
Returned by validateHeaderKey when the key begins with ':' (a pseudo-header). HTTP/2 pseudo-headers (:path, :method, :scheme, :authority, :status) are managed by the transport and cannot be mutated by an external processing server. This guards the integrity of the protocol layer.
Source
Thrown at internal/xds/httpfilter/extconfig.go:226
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":
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 trueView on GitHub (pinned to 0c51461d27)
Solutions
- Exclude any key starting with ':' from mutations on the ext_proc server.
- To change authority/path, use the proper Envoy/gRPC mechanism (route rewrite, not header mutation).
- Filter pseudo-headers out when converting an HTTP/2 header set into mutations.
- Add an assertion in server tests that no emitted key starts with ':'.
Example fix
// before
for k, v := range allHeaders { emit(k, v) }
// after
for k, v := range allHeaders {
if strings.HasPrefix(k, ":") { continue }
emit(k, v)
} Defensive patterns
Strategy: validation
Validate before calling
// server-side: skip pseudo-headers
if strings.HasPrefix(key, ":") { return /* skip */ } Prevention
- Treat any ':'-prefixed key as off-limits.
- Use route rewrite for authority/path changes, not header mutation.
- Filter pseudo-headers when converting HTTP/2 headers to mutations.
- Document the reserved set for server authors.
When it happens
Trigger: The ext_proc server returns a mutation to add/remove/modify a key like ":authority", ":path", or ":status". validateHeaderKey hits the key[0]==':' branch at extconfig.go:225.
Common situations: Server written for HTTP/1 rewrites passes through ':authority'; a generic header-rewrite rule copies all received headers into a mutation including pseudo-headers; server attempts to rewrite the request path via header mutation.
Related errors
- header key %q is reserved
- header key %q is not lowercase
- invalid header mutation: value for header key %q exceeds the
- header mutation disallowed by headerMutationRules for header
- header mutation disallowed by headerMutationRules for header
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/261afd90e7b35614.
Report an issue: GitHub.