grpc/grpc-go · error

header key contains value with non-printable ASCII…

Error message

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

What it means

For non-binary metadata values (key does not end with "-bin"), every value must consist solely of printable ASCII characters in the range %x20-%x7E. ValidatePair at metadata.go:138-141 calls hasNotPrintable and rejects values containing bytes below 0x20 (control chars, newline, tab) or above 0x7E. Binary payloads must instead use a "-bin" suffixed key so they are base64-encoded.

Solutions

  1. If the value is binary or may contain non-printable bytes, use a key suffixed with "-bin" and attach a base64-encoded value via md.Append("my-key-bin", encodedValue).
  2. If the value is text, strip or escape control characters (newlines, tabs, CR) before insertion.
  3. Validate values with a printable-ASCII check in a helper before adding to metadata.

Example fix

// before:
//   raw := someBinaryBlob // contains bytes > 0x7E
//   md := metadata.Pairs("payload", string(raw))

// after:
//   enc := base64.StdEncoding.EncodeToString(raw)
//   md := metadata.Pairs("payload-bin", enc)
Defensive patterns

Strategy: validation

Validate before calling

package main

import (
	"encoding/base64"
	"fmt"
)

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

func addValue(md map[string][]string, key, val string) error {
	if !printableASCII(val) {
		// force the -bin channel by base64-encoding.
		enc := base64.StdEncoding.EncodeToString([]byte(val))
		md[key+"-bin"] = append(md[key+"-bin"], enc)
		return nil
	}
	if !printableASCII(key) {
		return fmt.Errorf("key not printable")
	}
	md[key] = append(md[key], val)
	return nil
}

// func main() { _ = addValue }

Try / catch

// Validate values before attaching; if a value may carry non-printable bytes,
// switch to a '-bin' key.
//
//   if err := addValue(md, key, val); err != nil { return err }

Prevention

When it happens

Trigger: Triggered when metadata.ValidatePair is called on a non-binary key whose value contains a newline (\n), tab, NUL, or any byte outside 0x20-0x7E. Common when a developer puts raw bytes (a serialized proto, a UUID with a non-ASCII separator, a value containing a CR/LF) under a normal (non -bin) header key.

Common situations: Copying a binary blob (trace context, serialized struct, raw bytes) into a plain metadata value; a value containing a newline or control character from a log/format string; forgetting to base64-encode before attaching as metadata.

Related errors


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

Appendix: 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 0c51461d27)