larksuite/cli · error

set_header: header name must not contain ':', CR, or LF

Error message

set_header: header name must not contain ':', CR, or LF

What it means

applyOp validates that a 'set_header' op's header name contains none of ':', CR, or LF. A colon would truncate the header name on the wire and newlines enable header injection, so the name is rejected.

Source

Thrown at shortcuts/mail/draft/patch.go:94

		return removeRecipient(snapshot, op.Field, op.Address)
	case "set_reply_to":
		upsertHeader(&snapshot.Headers, "Reply-To", formatAddressList(op.Addresses))
	case "clear_reply_to":
		removeHeader(&snapshot.Headers, "Reply-To")
	case "set_body":
		return setBody(snapshot, op.Value, options)
	case "set_reply_body":
		return setReplyBody(snapshot, op.Value, options)
	case "replace_body":
		return replaceBody(snapshot, op.BodyKind, op.Value, options)
	case "append_body":
		return appendBody(snapshot, op.BodyKind, op.Value, options)
	case "set_header":
		if err := ensureHeaderEditable(op.Name, options); err != nil {
			return err
		}
		if strings.ContainsAny(op.Name, ":\r\n") {
			return fmt.Errorf("set_header: header name must not contain ':', CR, or LF")
		}
		if strings.ContainsAny(op.Value, "\r\n") {
			return fmt.Errorf("set_header: header value must not contain CR or LF")
		}
		upsertHeader(&snapshot.Headers, op.Name, op.Value)
	case "remove_header":
		if err := ensureHeaderEditable(op.Name, options); err != nil {
			return err
		}
		removeHeader(&snapshot.Headers, op.Name)
	case "add_attachment":
		return addAttachment(dctx, snapshot, op.Path)
	case "remove_attachment":
		// Priority: part_id > cid > token. When only token is set, route to
		// the large attachment path (updates header + HTML card, no MIME
		// part to remove). Otherwise, resolve to a concrete part_id.
		tgt := op.Target
		if strings.TrimSpace(tgt.PartID) == "" && strings.TrimSpace(tgt.CID) == "" {

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Pass only the header name without the colon: op.Name = strings.TrimSpace(parts[0]) after splitting on the first ':'.
  2. Strip CR/LF from the name before constructing the op.
  3. Validate header names against the RFC 7230 token charset in your caller before patching.

Example fix

// before
op := PatchOp{Op: "set_header", Name: "X-Custom: value", Value: "v"}
// after
parts := strings.SplitN("X-Custom: value", ":", 2)
op := PatchOp{Op: "set_header", Name: strings.TrimSpace(parts[0]), Value: "v"}
Defensive patterns

Strategy: validation

Validate before calling

func validHeaderName(name string) bool {
    name = strings.TrimSpace(name)
    if name == "" || strings.ContainsAny(name, ":\r\n") { return false }
    for _, r := range name {
        if r <= 32 || r >= 127 { return false }
    }
    return true
}

Type guard

func splitHeaderLine(line string) (name, value string, ok bool) {
    name, value, found := strings.Cut(line, ":")
    if !found { return "", "", false }
    return strings.TrimSpace(name), strings.TrimSpace(value), true
}

Try / catch

if err := Apply(ctx, dctx, ops, opts); err != nil {
    if strings.Contains(err.Error(), "header name must not") {
        return fmt.Errorf("strip ':' / CR / LF from header name: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Apply with PatchOp{Op:"set_header"} where Name includes ':' (e.g. passing the full line 'X-Custom: value' as the name) or any '\r'/'\n'.

Common situations: Parsing 'Name: value' pairs from text and passing the whole pair as the name; splitting header lines incorrectly on spaces instead of ':'; unsanitized user-supplied header names.

Related errors


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