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/RLO

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Strip or sanitize control characters from the header value before setting it
  2. Reject the input at your UI/API boundary with a clear message instead of passing it to the builder
  3. If a newline was intended, fold the header with CRLF+WSP yourself or drop the extra line
  4. 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

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


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/0c21d21ef1c6abe1. Report an issue: GitHub.