{"record":{"id":"2af63daccadadd18","repo":"grpc/grpc-go","slug":"header-key-q-is-in-the-reserved-grpc-space","errorCode":null,"errorMessage":"header key %q is in the reserved 'grpc-' space","messagePattern":"header key %q is in the reserved 'grpc-' space","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extconfig.go","lineNumber":230,"sourceCode":"\t\t}\n\t\tinput.Delete(header)\n\t}\n\treturn nil\n}\n\n// validateHeaderKey returns a non-nil error if key may not be mutated by an\n// external processing server, either because the key is reserved or because it\n// is not a valid gRPC header name.\nfunc validateHeaderKey(key string) error {\n\tswitch {\n\tcase len(key) == 0:\n\t\treturn fmt.Errorf(\"header key is empty\")\n\tcase key[0] == ':':\n\t\treturn fmt.Errorf(\"header key %q is a pseudo-header\", key)\n\tcase key == \"host\":\n\t\treturn fmt.Errorf(\"header key %q is reserved\", key)\n\tcase strings.HasPrefix(key, \"grpc-\"):\n\t\treturn fmt.Errorf(\"header key %q is in the reserved 'grpc-' space\", key)\n\tcase key != strings.ToLower(key):\n\t\treturn fmt.Errorf(\"header key %q is not lowercase\", key)\n\tcase len(key) > maxHeaderSize:\n\t\treturn fmt.Errorf(\"header key exceeds the maximum length of %d bytes\", maxHeaderSize)\n\t}\n\treturn imetadata.ValidateKey(key)\n}\n\nfunc (hmr *HeaderMutationRules) allow(key string) bool {\n\tif hmr.DisallowExpr != nil && hmr.DisallowExpr.MatchString(key) {\n\t\treturn false\n\t}\n\tif hmr.AllowExpr != nil && hmr.AllowExpr.MatchString(key) {\n\t\treturn true\n\t}\n\tif hmr.AllowExpr != nil {\n\t\treturn false\n\t}","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extconfig.go#L212-L248","documentation":"Returned by validateHeaderKey when the key has the \"grpc-\" prefix. The grpc-* namespace is reserved for gRPC-internal headers (grpc-trace-bin, grpc-status, grpc-message, grpc-encoding, etc.); mutating them from an external processor could corrupt the protocol. Matched by strings.HasPrefix(key, \"grpc-\") at extconfig.go:229.","triggerScenarios":"The ext_proc server sends a mutation whose key starts with \"grpc-\" (e.g. \"grpc-trace-bin\", \"grpc-status\", or a custom \"grpc-foo\"). validateHeaderKey rejects it.","commonSituations":"Server tries to inject or rewrite tracing metadata via grpc-trace-bin; server copies all inbound headers into outbound mutations including the grpc-* transport headers; a custom server uses a grpc--prefixed name not realizing it is reserved.","solutions":["Pick a non-reserved prefix for custom headers (e.g. x-* following the extension convention).","Never mutate grpc-trace-bin, grpc-status, grpc-message, grpc-encoding from ext_proc; use the stats handler / OpenTelemetry APIs for tracing.","Filter out keys starting with \"grpc-\" when building mutations.","If you need to propagate trace context, use the binary metadata convention under a non-reserved name."],"exampleFix":"// before\nemit(\"grpc-trace-bin\", traceBin)\n// after: use OpenTelemetry stats handler for trace propagation, not header mutation","handlingStrategy":"validation","validationCode":"// server-side: keep out of the reserved grpc- namespace\nif strings.HasPrefix(key, \"grpc-\") { return /* skip or rename to x- */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use x-* names for custom extensions.","Propagate trace context via OpenTelemetry stats handler, not grpc-trace-bin mutation.","Filter grpc--prefixed keys in copy loops.","Document the grpc- reservation in server guides."],"tags":["grpc","xds","extproc","header-mutation","reserved-namespace"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}