{"record":{"id":"5eb1085dc8d051a8","repo":"larksuite/cli","slug":"set-header-header-name-must-not-contain-cr-5eb108","errorCode":null,"errorMessage":"set_header: header name must not contain ':', CR, or LF","messagePattern":"set_header: header name must not contain ':', CR, or LF","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shortcuts/mail/draft/patch.go","lineNumber":94,"sourceCode":"\t\treturn removeRecipient(snapshot, op.Field, op.Address)\n\tcase \"set_reply_to\":\n\t\tupsertHeader(&snapshot.Headers, \"Reply-To\", formatAddressList(op.Addresses))\n\tcase \"clear_reply_to\":\n\t\tremoveHeader(&snapshot.Headers, \"Reply-To\")\n\tcase \"set_body\":\n\t\treturn setBody(snapshot, op.Value, options)\n\tcase \"set_reply_body\":\n\t\treturn setReplyBody(snapshot, op.Value, options)\n\tcase \"replace_body\":\n\t\treturn replaceBody(snapshot, op.BodyKind, op.Value, options)\n\tcase \"append_body\":\n\t\treturn appendBody(snapshot, op.BodyKind, op.Value, options)\n\tcase \"set_header\":\n\t\tif err := ensureHeaderEditable(op.Name, options); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif strings.ContainsAny(op.Name, \":\\r\\n\") {\n\t\t\treturn fmt.Errorf(\"set_header: header name must not contain ':', CR, or LF\")\n\t\t}\n\t\tif strings.ContainsAny(op.Value, \"\\r\\n\") {\n\t\t\treturn fmt.Errorf(\"set_header: header value must not contain CR or LF\")\n\t\t}\n\t\tupsertHeader(&snapshot.Headers, op.Name, op.Value)\n\tcase \"remove_header\":\n\t\tif err := ensureHeaderEditable(op.Name, options); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tremoveHeader(&snapshot.Headers, op.Name)\n\tcase \"add_attachment\":\n\t\treturn addAttachment(dctx, snapshot, op.Path)\n\tcase \"remove_attachment\":\n\t\t// Priority: part_id > cid > token. When only token is set, route to\n\t\t// the large attachment path (updates header + HTML card, no MIME\n\t\t// part to remove). Otherwise, resolve to a concrete part_id.\n\t\ttgt := op.Target\n\t\tif strings.TrimSpace(tgt.PartID) == \"\" && strings.TrimSpace(tgt.CID) == \"\" {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/shortcuts/mail/draft/patch.go#L76-L112","documentation":"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.","triggerScenarios":"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'.","commonSituations":"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.","solutions":["Pass only the header name without the colon: op.Name = strings.TrimSpace(parts[0]) after splitting on the first ':'.","Strip CR/LF from the name before constructing the op.","Validate header names against the RFC 7230 token charset in your caller before patching."],"exampleFix":"// before\nop := PatchOp{Op: \"set_header\", Name: \"X-Custom: value\", Value: \"v\"}\n// after\nparts := strings.SplitN(\"X-Custom: value\", \":\", 2)\nop := PatchOp{Op: \"set_header\", Name: strings.TrimSpace(parts[0]), Value: \"v\"}","handlingStrategy":"validation","validationCode":"func validHeaderName(name string) bool {\n    name = strings.TrimSpace(name)\n    if name == \"\" || strings.ContainsAny(name, \":\\r\\n\") { return false }\n    for _, r := range name {\n        if r <= 32 || r >= 127 { return false }\n    }\n    return true\n}","typeGuard":"func splitHeaderLine(line string) (name, value string, ok bool) {\n    name, value, found := strings.Cut(line, \":\")\n    if !found { return \"\", \"\", false }\n    return strings.TrimSpace(name), strings.TrimSpace(value), true\n}","tryCatchPattern":"if err := Apply(ctx, dctx, ops, opts); err != nil {\n    if strings.Contains(err.Error(), \"header name must not\") {\n        return fmt.Errorf(\"strip ':' / CR / LF from header name: %w\", err)\n    }\n    return err\n}","preventionTips":["Never pass full 'Name: value' strings as the header Name; split on the first colon.","Validate header names against the RFC token charset before patching.","Normalize names (trim + lowercase for checks) before constructing ops.","Keep user input out of header names entirely; map user choices to fixed names."],"tags":["email","header-injection","validation"],"backgroundTag":"header-injection","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}