ruby/ruby · error · ArgumentError
field content type cannot include CR/LF
Error message
field content type cannot include CR/LF
What it means
In multipart form encoding, a part's per-field content type (h[:content_type], defaulting to application/octet-stream) is written verbatim into a `Content-Type:` MIME header line. If it contains CR or LF, headers could be injected, so /[\r\n]/ triggers ArgumentError('field content type cannot include CR/LF').
Source
Thrown at lib/net/http/generic_request.rb:357
if /[\r\n]/.match?(boundary.to_s)
raise ArgumentError, "multipart boundary cannot include CR/LF"
end
chunked_p = chunked?
buf = +''
params.each do |key, value, h={}|
key = quote_string(key, charset)
filename =
h.key?(:filename) ? h[:filename] :
value.respond_to?(:to_path) ? File.basename(value.to_path) :
nil
buf << "--#{boundary}\r\n"
if filename
filename = quote_string(filename, charset)
type = (h[:content_type] || 'application/octet-stream').to_s
if /[\r\n]/.match?(type)
raise ArgumentError, "field content type cannot include CR/LF"
end
buf << "Content-Disposition: form-data; " \
"name=\"#{key}\"; filename=\"#{filename}\"\r\n" \
"Content-Type: #{type}\r\n\r\n"
if !out.respond_to?(:write) || !value.respond_to?(:read)
# if +out+ is not an IO or +value+ is not an IO
buf << (value.respond_to?(:read) ? value.read : value)
elsif value.respond_to?(:size) && chunked_p
# if +out+ is an IO and +value+ is a File, use IO.copy_stream
flush_buffer(out, buf, chunked_p)
out << "%x\r\n" % value.size if chunked_p
IO.copy_stream(value, out)
out << "\r\n" if chunked_p
else
# +out+ is an IO, and +value+ is not a File but an IO
flush_buffer(out, buf, chunked_p)
1 while flush_buffer(out, value.read(4096), chunked_p)
endView on GitHub (pinned to 0e5b888e1c)
Solutions
- Sanitize before use: `type = type.to_s.gsub(/[\r\n]+/, ' ').strip`.
- Validate against a MIME allowlist (`type =~ /\A[\w.+-]+\/[\w.+-]+\z/`) and fall back to 'application/octet-stream'.
- Never forward raw client headers into multipart metadata.
Example fix
# before
req.set_multipart_form_data([['f', io, { content_type: untrusted_ct }]])
# after
ct = untrusted_ct.to_s
ct = 'application/octet-stream' unless ct.match?(%r{\A[\w.+\-/]+\/[\w.+\-/]+\z})
req.set_multipart_form_data([['f', io, { content_type: ct }]]) Defensive patterns
Strategy: validation
Validate before calling
type = (h[:content_type] || 'application/octet-stream').to_s
type = 'application/octet-stream' unless type.match?(%r{\A[\w.+\-/;= ]+\z})
opt = opt.merge(content_type: type) Type guard
def safe_content_type?(t) !t.to_s.match?(/[\r\n]/) end
Prevention
- Validate content types against a MIME token regex before passing them into multipart APIs.
- Never echo raw client Content-Type headers into outgoing multipart metadata.
When it happens
Trigger: `req.set_multipart_form_data([['file', file, { content_type: "text/plain\r\nX-Evil: 1" }]])`. Passing content_type strings built from untrusted metadata (uploaded file content-type headers, EXIF, or filename-derived guesses) that embed newlines. Helpers that forward a content_type option verbatim.
Common situations: Server code echoing a client-supplied Content-Type back into an outgoing multipart upload (proxying uploads). Fuzz/security tests deliberately probing CRLF injection surfaces. MIME maps or config containing embedded newlines.
Related errors
- multipart boundary cannot include CR/LF
- multipart field name or filename cannot include CR/LF
- A Request-Line must not contain CR or LF
- header #{key} has field value #{value.inspect}, this cannot
- header field name cannot include control characters or colon
AI-assisted analysis of ruby/ruby@0e5b888e1c (2026-08-21).
Data as JSON: /api/errors/dedc87582844067d.
Report an issue: GitHub.