{"record":{"id":"f15b95386a47c8e8","repo":"grpc/grpc-go","slug":"header-key-q-contains-illegal-characters-not-in","errorCode":null,"errorMessage":"header key %q contains illegal characters not in [0-9a-z-_.]","messagePattern":"header key %q contains illegal characters not in \\[0-9a-z-_\\.\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/metadata/metadata.go","lineNumber":117,"sourceCode":"\n// ValidateKey validates a key with the following rules (pseudo-headers are\n// skipped):\n// - the key must contain one or more characters.\n// - the characters in the key must be in [0-9 a-z _ - .].\nfunc ValidateKey(key string) error {\n\t// key should not be empty\n\tif key == \"\" {\n\t\treturn fmt.Errorf(\"there is an empty key in the header\")\n\t}\n\t// pseudo-header will be ignored\n\tif key[0] == ':' {\n\t\treturn nil\n\t}\n\t// check key, for i that saving a conversion if not using for range\n\tfor i := 0; i < len(key); i++ {\n\t\tr := key[i]\n\t\tif !(r >= 'a' && r <= 'z') && !(r >= '0' && r <= '9') && r != '.' && r != '-' && r != '_' {\n\t\t\treturn fmt.Errorf(\"header key %q contains illegal characters not in [0-9a-z-_.]\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\n// ValidatePair validates a key-value pair with the following rules\n// (pseudo-header are skipped):\n//   - the key must contain one or more characters.\n//   - the characters in the key must be in [0-9 a-z _ - .].\n//   - if the key ends with a \"-bin\" suffix, no validation of the corresponding\n//     value is performed.\n//   - the characters in every value must be printable (in [%x20-%x7E]).\nfunc ValidatePair(key string, vals ...string) error {\n\tif err := ValidateKey(key); err != nil {\n\t\treturn err\n\t}\n\tif strings.HasSuffix(key, \"-bin\") {\n\t\treturn nil","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/metadata/metadata.go#L99-L135","documentation":"Returned by metadata.ValidateKey when a key contains any byte outside [0-9a-z-_.] (after the pseudo-header ':' check). Notably uppercase A-Z are rejected: gRPC requires lowercase keys. This protects against invalid HTTP/2 header names being sent on the wire.","triggerScenarios":"Passing a key like \"Authorization\", \"Content-Type\", \"X-Trace-Id\", or any key containing spaces, colons, or symbols. Keys derived from inbound HTTP/1 headers without lowercasing.","commonSituations":"Reusing HTTP/1 header names verbatim (capitalized); copying header keys from JSON config that preserves case; interop with proxies that inject mixed-case headers.","solutions":["Lowercase all metadata keys with strings.ToLower before adding them.","Replace any disallowed characters; keep keys to [a-z0-9-_.] only.","Run metadata.Validate(md) once at construction time to fail fast."],"exampleFix":"// before\nmd := metadata.Pairs(\"Authorization\", token) // uppercase rejected\n// after\nmd := metadata.Pairs(strings.ToLower(\"Authorization\"), token) // \"authorization\"","handlingStrategy":"validation","validationCode":"// Lowercase and constrain keys before adding to metadata.\nfunc safeKey(k string) (string, error) {\n    k = strings.ToLower(k)\n    for i := 0; i < len(k); i++ {\n        c := k[i]\n        if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.') {\n            return \"\", fmt.Errorf(\"bad header key %q\", k)\n        }\n    }\n    return k, nil\n}","typeGuard":"func isValidKey(k string) bool {\n    if k == \"\" || k[0] == ':' { return k != \"\" }\n    for i := 0; i < len(k); i++ {\n        c := k[i]\n        if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.') {\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":"if err := metadata.Validate(md); err != nil {\n    // auto-repair by lowercasing keys, then retry\n    md = lowercaseKeys(md)\n}","preventionTips":["Always lowercase header keys (HTTP/2 requires it).","Run metadata.Validate(md) at the boundary where headers enter your code.","Reject mixed-case input from config files early."],"tags":["grpc","metadata","validation","headers","http2"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}