larksuite/cli · error
emlbuilder: header value contains control character: %q
Error message
emlbuilder: header value contains control character: %q
What it means
validateHeaderValue screens RFC 2822 header values before they are written into the generated EML, rejecting C0 control characters (except tab, which folded headers allow) and DEL (0x7F) that would corrupt or smuggle header boundaries. The mail command layer later converts this intermediate error into a typed ValidationError.
Source
Thrown at shortcuts/mail/emlbuilder/builder.go:140
contentType string
fileName string
contentID string // without angle brackets
isOtherPart bool // true = no Content-Disposition (AddOtherPart); false = Content-Disposition: inline
}
// New returns an empty Builder.
func New() Builder {
return Builder{}
}
// validateHeaderValue rejects strings that contain characters unsafe in MIME
// header values: C0 control chars (except \t for folded headers), DEL (0x7F),
// and dangerous Unicode (Bidi overrides, zero-width chars) that enable
// visual-spoofing attacks.
func validateHeaderValue(v string) error {
for _, r := range v {
if r != '\t' && (r < 0x20 || r == 0x7f) {
return fmt.Errorf("emlbuilder: header value contains control character: %q", v) //nolint:forbidigo // intermediate EML builder error; mail command layer wraps into typed ValidationError.
}
if isHeaderDangerousUnicode(r) {
return fmt.Errorf("emlbuilder: header value contains dangerous Unicode character: %q", v) //nolint:forbidigo // intermediate EML builder error; mail command layer wraps into typed ValidationError.
}
}
return nil
}
// isHeaderDangerousUnicode identifies Unicode code points used for visual
// spoofing: Bidi overrides that reverse display order, and zero-width characters
// that hide content. These must not appear in email header values.
func isHeaderDangerousUnicode(r rune) bool {
switch {
case r >= 0x200B && r <= 0x200D: // zero-width space/non-joiner/joiner
return true
case r == 0xFEFF: // BOM / zero-width no-break space
return true
case r >= 0x202A && r <= 0x202E: // Bidi: LRE/RLE/PDF/LRO/RLOView on GitHub (pinned to 7fd6ef3c07)
Solutions
- Strip or sanitize control characters from the header value before setting it
- Reject the input at your UI/API boundary with a clear message instead of passing it to the builder
- If a newline was intended, fold the header with CRLF+WSP yourself or drop the extra line
- Inspect the %q rendering in the error to locate the exact offending byte
Example fix
// before
b.Subject("Quarterly report\r\nBcc: attacker@evil.com") // header injection
// after
clean := strings.Map(func(r rune) rune {
if r != '\t' && (r < 0x20 || r == 0x7f) { return -1 }
return r
}, raw)
b.Subject(clean) Defensive patterns
Strategy: validation
Validate before calling
func sanitizeHeaderValue(v string) string {
return strings.Map(func(r rune) rune {
if r != '\t' && (r < 0x20 || r == 0x7f) { return -1 }
return r
}, v)
}
b.Subject(sanitizeHeaderValue(userSubject)) Type guard
func isSafeHeaderValue(v string) bool {
for _, r := range v {
if r != '\t' && (r < 0x20 || r == 0x7f) { return false }
}
return true
} Try / catch
err := b.Subject(userSubject)
if err != nil {
if strings.Contains(err.Error(), "control character") || strings.Contains(err.Error(), "dangerous Unicode") {
return fmt.Errorf("subject contains invalid characters; sanitize and retry")
}
return err
} Prevention
- Strip control chars and dangerous Unicode from user input before setting headers
- Never build headers by concatenating raw multi-line data
- Reject invalid header input at your UI/API boundary with a clear message
- Test with inputs containing \r\n and 0x00 bytes to catch injection early
When it happens
Trigger: Calling DispositionNotificationTo, Subject, MessageID, InReplyTo, LMSReplyToMessageID, or References with a value containing raw control bytes — e.g. a subject with embedded \n or \r (header injection), a pasted string with stray 0x00-0x1F bytes, or binary noise in a Message-ID.
Common situations: User input pasted from terminals or logs carrying ANSI/control characters; subject lines built by concatenating multi-line data; copying Message-IDs out of raw sources with trailing \r; injection attempts via untrusted subject/reply headers.
Related errors
- attachment %q: %w
- emlbuilder: header name contains ':', CR, or LF: %q
- %s contains invalid line break characters
- set_recipients requires non-empty addresses
- %s requires address
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/0c21d21ef1c6abe1.
Report an issue: GitHub.