grpc/grpc-go · error

header key contains illegal characters not in [0-9a-z-_.]

Error message

header key %q contains illegal characters not in [0-9a-z-_.]

What it means

Per gRPC metadata rules, non-pseudo-header keys may only contain lowercase letters [a-z], digits [0-9], dot (.), dash (-), and underscore (_). ValidateKey at metadata.go:114-117 scans each byte and rejects any character outside that set. This enforces lowercase HTTP/2 header-name semantics and prevents illegal bytes from reaching the wire.

Solutions

  1. Lowercase the key and replace disallowed characters before inserting: e.g. strings.ToLower and translate spaces/special chars to dash or underscore.
  2. Rename offending keys to the gRPC canonical lowercase form (e.g. "authorization", "x-request-id", "grpc-status").
  3. If you must carry binary/structured data, suffix the key with "-bin" so values are base64 and use a compliant key name.
  4. Run metadata.Validate on your constructed MD in tests.

Example fix

// before:
//   md := metadata.Pairs("Authorization", "Bearer "+token)
//   ctx = metadata.NewOutgoingContext(ctx, md)
//   // error: header key "Authorization" contains illegal characters

// after:
//   md := metadata.Pairs("authorization", "Bearer "+token)
//   ctx = metadata.NewOutgoingContext(ctx, md)
Defensive patterns

Strategy: validation

Validate before calling

package main

import (
	"fmt"
	"strings"
)

func sanitizeKey(k string) (string, error) {
	k = strings.ToLower(k)
	for i := 0; i < len(k); i++ {
		c := k[i]
		ok := (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
			c == '.' || c == '-' || c == '_'
		if !ok {
			return "", fmt.Errorf("illegal char %q in key %q", c, k)
		}
	}
	return k, nil
}

// func main() { _, _ = sanitizeKey("Authorization") }

Try / catch

// Validate keys before adding to metadata; gRPC will reject the RPC otherwise.
//
//   k, err := sanitizeKey(rawKey)
//   if err != nil { return err }
//   md := metadata.Pairs(k, value)

Prevention

When it happens

Trigger: Triggered when a metadata.MD key contains an uppercase letter, a space, a colon (except the leading pseudo-header colon), or any special character (e.g. ';', '/', '*', '(', etc.), and that MD is passed through metadata.Validate/ValidatePair/ValidateKey.

Common situations: A developer uses a mixed-case or CamelCase header name (e.g. "Authorization", "X-Request-Id"), copies an HTTP/1 header verbatim, or constructs a key from user input that contains uppercase or punctuation.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/f15b95386a47c8e8. Report an issue: GitHub.

Appendix: source

Thrown at internal/metadata/metadata.go:117

// ValidateKey validates a key with the following rules (pseudo-headers are
// skipped):
// - the key must contain one or more characters.
// - the characters in the key must be in [0-9 a-z _ - .].
func ValidateKey(key string) error {
	// key should not be empty
	if key == "" {
		return fmt.Errorf("there is an empty key in the header")
	}
	// pseudo-header will be ignored
	if key[0] == ':' {
		return nil
	}
	// check key, for i that saving a conversion if not using for range
	for i := 0; i < len(key); i++ {
		r := key[i]
		if !(r >= 'a' && r <= 'z') && !(r >= '0' && r <= '9') && r != '.' && r != '-' && r != '_' {
			return fmt.Errorf("header key %q contains illegal characters not in [0-9a-z-_.]", key)
		}
	}
	return nil
}

// ValidatePair validates a key-value pair with the following rules
// (pseudo-header are skipped):
//   - the key must contain one or more characters.
//   - the characters in the key must be in [0-9 a-z _ - .].
//   - if the key ends with a "-bin" suffix, no validation of the corresponding
//     value is performed.
//   - the characters in every value must be printable (in [%x20-%x7E]).
func ValidatePair(key string, vals ...string) error {
	if err := ValidateKey(key); err != nil {
		return err
	}
	if strings.HasSuffix(key, "-bin") {
		return nil

View on GitHub (pinned to 0c51461d27)