{"record":{"id":"35e0f17cb9c4f06b","repo":"grpc/grpc-go","slug":"header-key-q-is-not-lowercase","errorCode":null,"errorMessage":"header key %q is not lowercase","messagePattern":"header key %q is not lowercase","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extconfig.go","lineNumber":232,"sourceCode":"\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}\n\treturn true\n}","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extconfig.go#L214-L250","documentation":"Returned by validateHeaderKey when the key is not all-lowercase. HTTP/2 mandates lowercase header names; gRPC carries this requirement through to metadata. validateHeaderKey checks key != strings.ToLower(key) at extconfig.go:231 and rejects mixed/uppercase names like \"Content-Type\" or \"X-Request-Id\".","triggerScenarios":"The ext_proc server returns a mutation whose key contains uppercase letters (e.g. \"Content-Type\", \"X-Custom\"). validateHeaderKey fails the lowercase check before grammar validation.","commonSituations":"Server reused from HTTP/1 code where header case was free; server copies headers from a case-preserving map without normalizing; integration with a system that canonicalizes header names to Title-Case.","solutions":["Lowercase the key on the ext_proc server before emitting the mutation.","Normalize at the boundary where external data enters the server (strings.ToLower).","Add a lint check / unit test asserting all emitted keys are lowercase.","Audit shared header constants for accidental uppercase."],"exampleFix":"// before\nemit(\"X-Request-Id\", id)\n// after\nemit(strings.ToLower(\"X-Request-Id\"), id) // -> \"x-request-id\"","handlingStrategy":"validation","validationCode":"// server-side: lowercase keys at the boundary\nkey = strings.ToLower(key)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Lowercase all header keys before emitting mutations.","Normalize at the data-ingress boundary of the server.","Lint constants for accidental uppercase.","Test against a known Title-Case input."],"tags":["grpc","xds","extproc","header-mutation","http2"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}