Skip to content

[🐛 Bug]: NO_PROXY/no_proxy entries are parsed without trimming whitespace #17460

Description

@skatkov

Description

Selenium Ruby appears to split NO_PROXY / no_proxy on commas without trimming surrounding whitespace. This affects local WebDriver traffic when a proxy is configured, because hosts like 127.0.0.1 or localhost may fail to match the bypass list if the entry contains a leading space.

Steps to reproduce

  1. Configure a proxy and a NO_PROXY value with spaces after commas:

    export HTTP_PROXY=http://proxy.example:8080
    export NO_PROXY=localhost, 127.0.0.1
  2. Run Selenium Ruby code that connects to a local WebDriver endpoint hosted on 127.0.0.1 or localhost.

  3. Observe how the Ruby client decides whether to use the proxy for the WebDriver connection.

Actual result

NO_PROXY is split on , without trimming entries, so the second value becomes " 127.0.0.1" instead of "127.0.0.1". That value does not match the target host, so Selenium does not bypass the proxy as expected.

Expected result

NO_PROXY entries should be normalized before comparison, for example by trimming surrounding whitespace.

Reproducible Code

require 'socket'
require 'json'
require 'selenium-webdriver'

server = TCPServer.new('127.0.0.1', 0)
port = server.addr[1]
url = "http://127.0.0.1:#{port}"
stop = false

thread = Thread.new do
  until stop
    begin
      client = server.accept_nonblock
    rescue IO::WaitReadable, Errno::EINTR
      IO.select([server], nil, nil, 0.1)
      next
    rescue IOError, Errno::EBADF
      break
    end

    request_line = client.gets
    if request_line.nil?
      client.close
      next
    end

    method, path, = request_line.split(' ')
    headers = {}

    while (line = client.gets)
      line = line.chomp
      break if line.empty?
      key, value = line.split(':', 2)
      headers[key] = value.to_s.strip
    end

    content_length = headers['Content-Length'].to_i
    client.read(content_length) if content_length.positive?

    payload = case [method, path]
    when ['GET', '/status']
      { value: { ready: true, message: 'ok' } }
    when ['POST', '/session']
      { value: { sessionId: 'test-session', capabilities: { 'browserName' => 'fake' } } }
    when ['DELETE', '/session/test-session']
      { value: nil }
    else
      { value: { error: 'unknown command', message: path } }
    end

    json = JSON.dump(payload)
    client.write "HTTP/1.1 200 OK\r\n"
    client.write "Content-Type: application/json; charset=utf-8\r\n"
    client.write "Content-Length: #{json.bytesize}\r\n"
    client.write "Connection: close\r\n"
    client.write "\r\n"
    client.write json
    client.close
  end
end

begin
  cases = {
    'trimmed' => 'localhost,127.0.0.1',
    'space-after-comma' => 'localhost, 127.0.0.1',
    'leading-space-only' => ' 127.0.0.1'
  }

  cases.each do |label, no_proxy|
    ENV['HTTP_PROXY'] = 'http://127.0.0.1:9'
    ENV['NO_PROXY'] = no_proxy
    ENV.delete('http_proxy')
    ENV.delete('no_proxy')

    begin
      driver = Selenium::WebDriver.for(:remote, url: url, capabilities: :firefox)
      puts "#{label}: success"
      driver.quit rescue nil
    rescue => e
      puts "#{label}: #{e.class}: #{e.message.lines.first.strip}"
    end
  end
ensure
  stop = true
  server.close
  thread.join
end

Run it with:

ruby repro.rb

Actual Result

Output is:

trimmed: success
space-after-comma: Errno::ECONNREFUSED: Connection refused - using proxy: http://127.0.0.1:9
leading-space-only: Errno::ECONNREFUSED: Connection refused - using proxy: http://127.0.0.1:9

This shows that:

  • NO_PROXY=localhost,127.0.0.1 works
  • NO_PROXY=localhost, 127.0.0.1 fails because Selenium does not trim the second entry
  • NO_PROXY= 127.0.0.1 also fails for the same reason

### Debugging Logs

```logs
- `lib/selenium/webdriver/remote/http/default.rb:152-153`
  `proxy.no_proxy.split(',').any? do |host|`
  This splits on commas but does not trim, so `" localhost"` stays `" localhost"` and will not match `server_url.host == "localhost"`.

- `lib/selenium/webdriver/common/proxy.rb:147`
  `'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(', ') : no_proxy`
  This uses a different assumption: split on `", "` instead of just `","`. That inconsistency also suggests proxy bypass parsing is not normalized correctly upstream.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-needs-triagingA Selenium member will evaluate this soon!C-rbRuby BindingsD-firefoxI-defectSomething is not working as intendedOS-linux

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions