larksuite/cli · error

emlbuilder: display name contains CR or LF: %q

Error message

emlbuilder: display name contains CR or LF: %q

What it means

validateDisplayName rejects display names containing CR or LF, because mail.Address.String() encodes names in a quoted-string that CRLF would escape, allowing header injection into the generated EML. It runs for every address-setting setter (From, To, CC, BCC, ReplyTo, DispositionNotificationTo). Wrapped into a typed ValidationError by the mail command layer.

Source

Thrown at shortcuts/mail/emlbuilder/builder.go:186

// validateHeaderName rejects any string that contains ':', CR (\r), LF (\n),
// or non-printable ASCII characters, as required by RFC 5322 field-name syntax.
func validateHeaderName(n string) error {
	if strings.ContainsAny(n, ":\r\n") {
		return fmt.Errorf("emlbuilder: header name contains ':', CR, or LF: %q", n) //nolint:forbidigo // intermediate EML builder error; mail command layer wraps into typed ValidationError.
	}
	for _, r := range n {
		if r < 0x21 || r > 0x7e {
			return fmt.Errorf("emlbuilder: header name contains non-printable character: %q", n) //nolint:forbidigo // intermediate EML builder error; mail command layer wraps into typed ValidationError.
		}
	}
	return nil
}

// validateDisplayName rejects display names containing CR or LF, which could
// escape the quoted-string encoding used by mail.Address.String() and inject headers.
func validateDisplayName(name string) error {
	if strings.ContainsAny(name, "\r\n") {
		return fmt.Errorf("emlbuilder: display name contains CR or LF: %q", name) //nolint:forbidigo // intermediate EML builder error; mail command layer wraps into typed ValidationError.
	}
	return nil
}

// validateCID rejects content IDs containing ASCII control characters (0x00–0x1F, 0x7F).
// RFC 2045 Content-ID has the same syntax as Message-ID; control characters are never valid.
func validateCID(cid string) error {
	for _, r := range cid {
		if r < 0x20 || r == 0x7f {
			return fmt.Errorf("emlbuilder: content ID contains control character: %q", cid) //nolint:forbidigo // intermediate EML builder error; mail command layer wraps into typed ValidationError.
		}
	}
	return nil
}

// From sets the From header. name may be empty.
func (b Builder) From(name, addr string) Builder {
	if b.err != nil {

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Strip CR/LF from the display name before passing it (strings.ReplaceAll(name, "\n", "") and same for "\r").
  2. Validate address book input at ingestion time to reject multiline display names.
  3. If a line break is intended, remove it — RFC 5322 display names cannot contain raw newlines.

Example fix

// before
b.To("Alice\nBcc: evil@x.com" <alice@x.com>)
// after
name := strings.ReplaceAll(strings.ReplaceAll(raw, "\r", ""), "\n", "")
b.To(name + " <alice@x.com>")
Defensive patterns

Strategy: validation

Validate before calling

func safeDisplayName(name string) string {
    return strings.Map(func(r rune) rune {
        if r == '\r' || r == '\n' { return -1 }
        return r
    }, name)
}
// use: b.To(safeDisplayName(name) + " <" + addr + ">")

Try / catch

if err := b.To(display + " <" + addr + ">"); err != nil {
    var verr *ValidationError
    if errors.As(err, &verr) { return fmt.Errorf("invalid display name %q: %w", display, verr) }
    return err
}

Prevention

When it happens

Trigger: Passing a display name to From/To/CC/BCC/ReplyTo/DispositionNotificationTo that contains '\r' or '\n', e.g. To("Alice\r\nBcc: victim@x.com" <a@x.com>).

Common situations: Display names assembled from user profile fields or form input containing embedded newlines; data imported from CSV/Excel with multiline cells; log-formatted strings reused as names.

Related errors


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