{"record":{"id":"2e48e30191765bb1","repo":"grpc/grpc-go","slug":"there-is-an-empty-key-in-the-header","errorCode":null,"errorMessage":"there is an empty key in the header","messagePattern":"there is an empty key in the header","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/metadata/metadata.go","lineNumber":107,"sourceCode":"// hasNotPrintable return true if msg contains any characters which are not in %x20-%x7E\nfunc hasNotPrintable(msg string) bool {\n\t// for i that saving a conversion if not using for range\n\tfor i := 0; i < len(msg); i++ {\n\t\tif msg[i] < 0x20 || msg[i] > 0x7E {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\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.","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/metadata/metadata.go#L89-L125","documentation":"Returned by metadata.ValidateKey when the supplied header key is the empty string. gRPC metadata keys must be non-empty and lowercase; an empty key is treated as malformed per the internal address metadata validator. Pseudo-headers (leading ':') and binary headers (-bin) are exempt.","triggerScenarios":"Calling metadata.Set(addr, md) where md contains a \"\" key, or metadata.Validate / ValidatePair with an empty key. Constructing a metadata.MD from user input without filtering blanks.","commonSituations":"Building metadata from a map with a missing/blank key; splitting a raw header line on ':' and using an empty left side; deserializing config where a header entry has no name.","solutions":["Filter out empty keys before constructing the metadata.MD.","Validate keys with metadata.ValidateKey (or this internal.ValidatePair) before passing to Set/Validate.","Log the offending key at the boundary where user input enters your code."],"exampleFix":"// before\nmd := metadata.MD{\"\": []string{\"v\"}, \"auth\": []string{\"t\"}}\nmetadata.Set(addr, md) // empty key error\n// after\nmd := metadata.MD{\"auth\": []string{\"t\"}}\nfor k, v := range userInput {\n    if k == \"\" { continue }\n    md[k] = v\n}","handlingStrategy":"validation","validationCode":"// Strip empty keys before building metadata.\nfunc sanitizeMD(md metadata.MD) metadata.MD {\n    out := metadata.MD{}\n    for k, v := range md {\n        if k == \"\" { continue }\n        out[k] = v\n    }\n    return out\n}","typeGuard":"func hasNoEmptyKeys(md metadata.MD) bool {\n    for k := range md { if k == \"\" { return false } }\n    return true\n}","tryCatchPattern":"if err := metadata.Validate(md); err != nil {\n    md = sanitizeMD(md) // drop bad entries and retry\n}","preventionTips":["Validate metadata once at construction with metadata.Validate.","Never derive keys from untrusted input without filtering empties.","Unit-test metadata builders with blank-key inputs."],"tags":["grpc","metadata","validation","headers"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}