grpc/grpc-go · error

header key %q contains value with non-printable ASCII charac

Error message

header key %q contains value with non-printable ASCII characters

What it means

Returned by metadata.ValidatePair when, for a non-binary key, any value contains a byte outside the printable ASCII range %x20-%x7E. Binary headers (suffix -bin) bypass this check; ASCII headers must be printable so they can be carried as HTTP/2 header values.

Source

Thrown at internal/metadata/metadata.go:140

// 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
	}
	// check value
	for _, val := range vals {
		if hasNotPrintable(val) {
			return fmt.Errorf("header key %q contains value with non-printable ASCII characters", key)
		}
	}
	return nil
}

View on GitHub (pinned to 03255a9237)

Solutions

  1. Suffix the key with -bin to mark it as a binary header (then values are base64-encoded automatically).
  2. Base64-encode or hex-encode binary payloads before placing them in an ASCII header.
  3. Sanitize values to the printable ASCII range, or reject non-printable input at the boundary.

Example fix

// before
md := metadata.Pairs("trace-id", string(rawBytes)) // non-printable
// after
md := metadata.Pairs("trace-id-bin", string(rawBytes)) // -bin bypasses the check
Defensive patterns

Strategy: validation

Validate before calling

// Choose -bin for binary payloads, validate ASCII otherwise.
func addHeader(md metadata.MD, key, val string) {
    if strings.HasSuffix(key, "-bin") {
        md.Append(key, val); return
    }
    if !isPrintable(val) {
        key = strings.TrimSuffix(key, "") + "-bin" // promote to binary
    }
    md.Append(key, val)
}

Type guard

func isPrintable(s string) bool {
    for i := 0; i < len(s); i++ { if s[i] < 0x20 || s[i] > 0x7E { return false } }
    return true
}

Try / catch

if err := metadata.Validate(md); err != nil {
    if strings.Contains(err.Error(), "non-printable") {
        md = encodeBinaries(md) // base64-encode offenders or rename to -bin
    }
}

Prevention

When it happens

Trigger: Putting raw bytes, UTF-8 multi-byte sequences, or control characters into an ASCII metadata value. Encoding binary data (e.g. a serialized proto, a UUID in raw bytes) into a non-bin header.

Common situations: Storing a trace context or auth blob as raw bytes in a regular header; concatenating strings that include newline/tab; copying values from a binary protocol field.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/2bfe0854a6ea56f0. Report an issue: GitHub.