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
- Suffix the key with -bin to mark it as a binary header (then values are base64-encoded automatically).
- Base64-encode or hex-encode binary payloads before placing them in an ASCII header.
- 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
- Use the -bin suffix for any non-ASCII or binary value.
- Base64-encode binary blobs before placing them in headers.
- Validate values at the source, not at the gRPC boundary.
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
- there is an empty key in the header
- header key %q contains illegal characters not in [0-9a-z-_.]
- grpc: invalid gzip compression level: %d
- no error details for status with code OK
- duplicated name
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/2bfe0854a6ea56f0.
Report an issue: GitHub.