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

Thrown by PatchOp.Validate() in shortcuts/mail/draft/model.go:305 when a set_header op's Name contains a colon, carriage return, or line feed. Colons would break the "Name: Value" header framing and CRLF enables header injection, so names containing them are rejected before any API call.

Source

Thrown at shortcuts/mail/draft/model.go:305

	case "set_reply_to":
		if len(op.Addresses) == 0 {
			return fmt.Errorf("set_reply_to requires addresses")
		}
	case "clear_reply_to":
	case "set_body", "set_reply_body":
	case "replace_body", "append_body":
		if !isBodyKind(op.BodyKind) {
			return fmt.Errorf("body_kind must be text/plain or text/html")
		}
		if op.Selector != "" && op.Selector != "primary" {
			return fmt.Errorf("selector must be primary")
		}
	case "set_header":
		if strings.TrimSpace(op.Name) == "" {
			return fmt.Errorf("set_header requires name")
		}
		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")
		}
	case "remove_header":
		if strings.TrimSpace(op.Name) == "" {
			return fmt.Errorf("remove_header requires name")
		}
	case "add_attachment":
		if strings.TrimSpace(op.Path) == "" {
			return fmt.Errorf("add_attachment requires path")
		}
	case "remove_attachment":
		if !op.Target.hasAnyKey() {
			return fmt.Errorf("remove_attachment requires target with at least one of part_id, cid, or token")
		}
	case "add_inline":
		if strings.TrimSpace(op.Path) == "" {

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Pass only the header name without the colon; put the value in op.Value.
  2. Sanitize untrusted input: reject or strip any ':', '\r', '\n' from the name before building the op.
  3. If the value itself contains newlines, fold it per RFC 5322 or split into multiple headers.

Example fix

// before
PatchOp{Op: "set_header", Name: "X-Trace-Id: " + traceID}

// after
PatchOp{Op: "set_header", Name: "X-Trace-Id", Value: traceID}
Defensive patterns

Strategy: validation

Validate before calling

if strings.ContainsAny(name, ":\r\n") {
    return fmt.Errorf("invalid header name %q", name)
}

Type guard

func safeHeaderName(name string) bool {
    if name == "" { return false }
    return !strings.ContainsAny(name, ":\r\n")
}

Prevention

When it happens

Trigger: name:"X-Custom: v" (colon included by mistake), name built by concatenating "Name:" + value, or a name containing "\r\n" from untrusted/user-supplied input attempting header injection.

Common situations: Copying a full header line "X-Foo: bar" from raw email source and using it as the name; interpolating user input into the name field — a classic header-injection vector; parsing headers where the split on ':' didn't happen.

Related errors


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