From 784db981643cdef8d02f3bd18d00177d4350d845 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 6 Aug 2026 14:16:29 +0000 Subject: [PATCH] Fix CRLF injection in Connection.connect via target_host parameter Validate target_host and target_port for CR, LF, and NUL characters before interpolating them into the raw CONNECT request line. This prevents HTTP request smuggling through the proxy when a consuming application passes user-derived input as the connection target. The validation uses the same INVALID_HEADER_VALUE_RE regex already used by validate_header! for proxy header values, ensuring consistency. Validation runs before any network I/O (fail fast). Adds specs covering CR, LF, NUL in target_host and CR in target_port. Co-authored-by: ProxyMesh AI --- lib/ruby_proxy_headers/connection.rb | 13 +++++++++++++ spec/connection_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/lib/ruby_proxy_headers/connection.rb b/lib/ruby_proxy_headers/connection.rb index 04601c5..83abee2 100644 --- a/lib/ruby_proxy_headers/connection.rb +++ b/lib/ruby_proxy_headers/connection.rb @@ -31,6 +31,8 @@ def initialize(proxy, options = {}) # @param target_port [Integer] Target port (default: 443) # @return [OpenSSL::SSL::SSLSocket] TLS-wrapped socket to target def connect(target_host, target_port = 443) + validate_connect_target!(target_host, target_port) + # Connect to proxy @socket = TCPSocket.new(@proxy[:host], @proxy[:port]) @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) @@ -132,6 +134,17 @@ def upgrade_to_tls(target_host) @socket = ssl_socket end + def validate_connect_target!(host, port) + if RubyProxyHeaders::INVALID_HEADER_VALUE_RE.match?(host.to_s) + raise ArgumentError, + "CONNECT target host contains invalid characters (CR, LF, or NUL): #{host.inspect}" + end + if RubyProxyHeaders::INVALID_HEADER_VALUE_RE.match?(port.to_s) + raise ArgumentError, + "CONNECT target port contains invalid characters (CR, LF, or NUL): #{port.inspect}" + end + end + def raise_connect_error case @proxy_response_status when 407 diff --git a/spec/connection_spec.rb b/spec/connection_spec.rb index 8a4b3c6..71baa90 100644 --- a/spec/connection_spec.rb +++ b/spec/connection_spec.rb @@ -68,4 +68,30 @@ expect(decoded).to eq('user:pass') end end + + describe '#connect target validation' do + let(:connection) do + described_class.new({ host: 'proxy.example.com', port: 8080 }) + end + + it 'rejects target_host containing CR' do + expect { connection.connect("evil.com\r\nInjected: header") } + .to raise_error(ArgumentError, /target host.*invalid/i) + end + + it 'rejects target_host containing LF' do + expect { connection.connect("evil.com\nInjected: header") } + .to raise_error(ArgumentError, /target host.*invalid/i) + end + + it 'rejects target_host containing NUL' do + expect { connection.connect("evil.com\0hidden") } + .to raise_error(ArgumentError, /target host.*invalid/i) + end + + it 'rejects target_port containing CR' do + expect { connection.connect('example.com', "443\r\nInjected: header") } + .to raise_error(ArgumentError, /target port.*invalid/i) + end + end end