Skip to content

fix: always set TLS servername for HTTPS proxy connections#86

Open
chiefbrain wants to merge 1 commit into
gajus:mainfrom
chiefbrain:fix_missing_servername
Open

fix: always set TLS servername for HTTPS proxy connections#86
chiefbrain wants to merge 1 commit into
gajus:mainfrom
chiefbrain:fix_missing_servername

Conversation

@chiefbrain

@chiefbrain chiefbrain commented Apr 29, 2026

Copy link
Copy Markdown

Related issues: #83, #62

When using HttpsProxyAgent via a CONNECT proxy, TLS options (including servername for SNI) are only set when configuration.secureEndpoint is truthy. However, many popular HTTP clients (notably got@11, which uses Node's built-in https.request) do not set secureEndpoint in the options passed to the agent's addRequest method.

This causes tls.connect() to be called without servername, meaning no SNI extension is sent in the TLS ClientHello. Servers behind CDNs (e.g. CloudFront, Cloudflare) that require SNI to route to the correct certificate reject the handshake with:

Error: write EPROTO ... ssl/tls alert handshake failure ... SSL alert number 40

Root cause

In Agent.ts, the TLS configuration block is gated by:

if (configuration.secureEndpoint) {   // ← NOT set by got@11, axios, etc.
    connectionConfiguration.tls = {
        servername,                    // ← never reaches here
        ...
    };
}

The secureEndpoint property is a convention from agent-base / pac-proxy-agent, but standard Node.js https.request() and libraries like got@11 never set it.

Fix

Change the condition from configuration.secureEndpoint to this.protocol === 'https:'. Since HttpsProxyAgent always sets this.protocol = 'https:', TLS options will always be configured when the agent is used for HTTPS connections, regardless of whether the caller sets secureEndpoint.

Reproduction

# Requires an HTTP CONNECT proxy at $PROXY_HOST:$PROXY_PORT
node -e "
process.env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE = '';
process.env.GLOBAL_AGENT_FORCE_GLOBAL_AGENT = 'false';
process.env.HTTPS_PROXY = 'http://\$PROXY_HOST:\$PROXY_PORT';
require('global-agent').bootstrap();

const got = require('got');
(async () => {
  try {
    // Any HTTPS host behind a CDN requiring SNI
    const r = await got('https://api.sensible.so/v0', {throwHttpErrors:false, timeout:{request:10000}});
    console.log('OK:', r.statusCode);
  } catch(e) {
    console.log('FAIL:', e.code, e.message);
    // Without fix: EPROTO ssl/tls alert handshake failure SSL alert number 40
    // With fix:    OK: 403
  }
})();
"

Verified fix

Tested with:

  • global-agent@4.1.3 + got@11.8.6 through HTTP CONNECT proxy
  • Node v25.9.0 (OpenSSL 3.6.2)
  • Node v22.22.2 (OpenSSL 3.5.5)
  • Targets: api.sensible.so, s3.amazonaws.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant