{"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/0c51461d27177d997e14c642fe18c11668fc09a3/internal/metadata/metadata.go#L89-L125","documentation":"gRPC metadata keys must be non-empty. metadata.ValidateKey at metadata.go:106-107 rejects an empty key string with this error. Pseudo-headers (starting with ':') are allowed and short-circuit before this check, so the empty key specifically means a genuinely zero-length key name in an outbound metadata.MD.","triggerScenarios":"Triggered when metadata.Validate or metadata.ValidatePair iterates a metadata.MD whose map contains the key \"\" (empty string), or when a caller appends metadata.Pairs(\"\") / metadata.Pairs(\"\", \"value\"). The validation runs before serializing metadata onto the wire.","commonSituations":"Dynamic header construction sets a key from a variable that resolved to empty (e.g. unset env var or empty trace/context-propagation key), metadata.Pairs(\"\", token) was written by mistake, or a header normalization function stripped/emptied a key.","solutions":["Audit the metadata.Pairs / metadata.Pairs / metadata.NewOutgoingContext call site that produced the MD and find the empty key.","Ensure any variable used as a key is non-empty before insertion (guard with an explicit check).","If the key is optional, skip adding it when the value is empty rather than inserting an empty key.","Add a unit test that calls metadata.Validate on your constructed MD."],"exampleFix":"// before:\n//   md := metadata.Pairs(authHeader, token, \"\", \"oops\")\n//   ctx = metadata.NewOutgoingContext(ctx, md)\n\n// after:\n//   md := metadata.Pairs(authHeader, token)\n//   if extraKey != \"\" {\n//       md.Append(extraKey, extraVal)\n//   }\n//   ctx = metadata.NewOutgoingContext(ctx, md)","handlingStrategy":"validation","validationCode":"package main\n\nimport (\n\t\"fmt\"\n\n\t\"google.golang.org/grpc/metadata\"\n)\n\nfunc buildOutgoingMD(pairs []string) (metadata.MD, error) {\n\tmd := metadata.MD{}\n\tfor i := 0; i+1 < len(pairs); i += 2 {\n\t\tk := pairs[i]\n\t\tif k == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"refusing to add metadata with empty key\")\n\t\t}\n\t\tmd.Append(k, pairs[i+1])\n\t}\n\treturn md, nil\n}\n\n// func main() { _, _ = buildOutgoingMD(nil) }","typeGuard":null,"tryCatchPattern":"// gRPC rejects invalid metadata before sending; the RPC fails with an error.\n// Validate before attaching so the call never reaches that path.\n//\n//   md, err := buildOutgoingMD(pairs)\n//   if err != nil { return err }\n//   ctx = metadata.NewOutgoingContext(ctx, md)","preventionTips":["Never derive a metadata key from a variable that may be empty without checking.","Wrap all metadata construction in a helper that rejects empty keys.","Call metadata.Validate(md) in unit tests on your produced MD."],"tags":["metadata","headers","validation","grpc"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}