@@ -5,6 +5,7 @@ import { EventEmitter } from 'node:events';
55import http from 'node:http' ;
66import https from 'node:https' ;
77import type net from 'node:net' ;
8+ import type tls from 'node:tls' ;
89import { URL } from 'node:url' ;
910import util from 'node:util' ;
1011
@@ -19,7 +20,7 @@ import type { HandlerOpts as ForwardOpts } from './forward';
1920import { forward } from './forward' ;
2021import { forwardSocks } from './forward_socks' ;
2122import { RequestError } from './request_error' ;
22- import type { Socket } from './socket' ;
23+ import type { Socket , TLSSocket } from './socket' ;
2324import { badGatewayStatusCodes } from './statuses' ;
2425import { getTargetStats } from './utils/count_target_bytes' ;
2526import { nodeify } from './utils/nodeify' ;
@@ -115,6 +116,7 @@ export type ServerOptions = HttpServerOptions | HttpsServerOptions;
115116 * Represents the proxy server.
116117 * It emits the 'requestFailed' event on unexpected request errors, with the following parameter `{ error, request }`.
117118 * It emits the 'connectionClosed' event when connection to proxy server is closed, with parameter `{ connectionId, stats }`.
119+ * It emits the 'tlsError' event on TLS handshake failures (HTTPS servers only), with parameter `{ error, socket }`.
118120 */
119121export class Server extends EventEmitter {
120122 port : number ;
@@ -193,23 +195,49 @@ export class Server extends EventEmitter {
193195 if ( ! options . httpsOptions ) {
194196 throw new Error ( 'httpsOptions is required when serverType is "https"' ) ;
195197 }
196- this . server = https . createServer ( options . httpsOptions ) ;
198+
199+ // Apply secure TLS defaults (user options can override)
200+ // This prevents users from accidentally configuring insecure TLS settings
201+ const secureDefaults : https . ServerOptions = {
202+ minVersion : 'TLSv1.2' , // Disable TLS 1.0 and 1.1 (deprecated, insecure)
203+ maxVersion : 'TLSv1.3' , // Enable modern TLS 1.3
204+ // Strong cipher suites (TLS 1.3 and TLS 1.2)
205+ ciphers : [
206+ // TLS 1.3 ciphers (always enabled with TLS 1.3)
207+ 'TLS_AES_128_GCM_SHA256' ,
208+ 'TLS_AES_256_GCM_SHA384' ,
209+ 'TLS_CHACHA20_POLY1305_SHA256' ,
210+ // TLS 1.2 ciphers (strong only)
211+ 'ECDHE-RSA-AES128-GCM-SHA256' ,
212+ 'ECDHE-RSA-AES256-GCM-SHA384' ,
213+ ] . join ( ':' ) ,
214+ honorCipherOrder : true , // Server chooses cipher (prevents downgrade attacks)
215+ ...options . httpsOptions , // User options override defaults
216+ } ;
217+
218+ this . server = https . createServer ( secureDefaults ) ;
197219 this . serverType = 'https' ;
198220 } else {
199221 this . server = http . createServer ( ) ;
200222 this . serverType = 'http' ;
201223 }
202224
203- // Attach event handlers (same for both HTTP and HTTPS)
225+ // Attach common event handlers (same for both HTTP and HTTPS)
204226 this . server . on ( 'clientError' , this . onClientError . bind ( this ) ) ;
205227 this . server . on ( 'request' , this . onRequest . bind ( this ) ) ;
206228 this . server . on ( 'connect' , this . onConnect . bind ( this ) ) ;
207- this . server . on ( 'connection' , this . onConnection . bind ( this ) ) ;
208229
209- // For HTTPS servers, also listen to secureConnection for proper TLS socket handling
210- // This ensures connection tracking works correctly with TLS sockets
230+ // Attach connection tracking based on server type
231+ // CRITICAL: Only listen to ONE connection event to avoid double registration
211232 if ( this . serverType === 'https' ) {
233+ // For HTTPS: Track only post-TLS-handshake sockets (secureConnection)
234+ // This ensures we track the TLS-wrapped socket with correct bytesRead/bytesWritten
212235 this . server . on ( 'secureConnection' , this . onConnection . bind ( this ) ) ;
236+ // Handle TLS handshake errors to prevent server crashes
237+ this . server . on ( 'tlsClientError' , this . onTLSClientError . bind ( this ) ) ;
238+ } else {
239+ // For HTTP: Track raw TCP sockets (connection)
240+ this . server . on ( 'connection' , this . onConnection . bind ( this ) ) ;
213241 }
214242
215243 this . lastHandlerId = 0 ;
@@ -232,14 +260,38 @@ export class Server extends EventEmitter {
232260 onClientError ( err : NodeJS . ErrnoException , socket : Socket ) : void {
233261 this . log ( socket . proxyChainId , `onClientError: ${ err } ` ) ;
234262
263+ // HTTP protocol error occurred after TLS handshake succeeded (in case HTTPS server is used)
235264 // https://nodejs.org/api/http.html#http_event_clienterror
236265 if ( err . code === 'ECONNRESET' || ! socket . writable ) {
237266 return ;
238267 }
239268
269+ // Can send HTTP response because HTTP protocol layer is active
240270 this . sendSocketResponse ( socket , 400 , { } , 'Invalid request' ) ;
241271 }
242272
273+ /**
274+ * Handles TLS handshake errors for HTTPS servers.
275+ * Without this handler, unhandled TLS errors can crash the server.
276+ * Common errors: ECONNRESET, ERR_SSL_SSLV3_ALERT_CERTIFICATE_UNKNOWN,
277+ * ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION, ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE
278+ */
279+ onTLSClientError ( err : NodeJS . ErrnoException , tlsSocket : tls . TLSSocket ) : void {
280+ const connectionId = ( tlsSocket as TLSSocket ) . proxyChainId ;
281+ this . log ( connectionId , `TLS handshake failed: ${ err . message } ` ) ;
282+
283+ // If connection already reset or socket not writable, nothing to do
284+ if ( err . code === 'ECONNRESET' || ! tlsSocket . writable ) {
285+ return ;
286+ }
287+
288+ // TLS handshake failed before HTTP, cannot send HTTP response
289+ tlsSocket . destroy ( err ) ;
290+
291+ // Emit event for user monitoring/metrics
292+ this . emit ( 'tlsError' , { error : err , socket : tlsSocket } ) ;
293+ }
294+
243295 /**
244296 * Assigns a unique ID to the socket and keeps the register up to date.
245297 * Needed for abrupt close of the server.
@@ -280,8 +332,12 @@ export class Server extends EventEmitter {
280332 // We need to consume socket errors, because the handlers are attached asynchronously.
281333 // See https://github.com/apify/proxy-chain/issues/53
282334 socket . on ( 'error' , ( err ) => {
283- // Handle errors only if there's no other handler
284- if ( this . listenerCount ( 'error' ) === 1 ) {
335+ // Prevent duplicate error handling for the same socket
336+ if ( socket . proxyChainErrorHandled ) return ;
337+ socket . proxyChainErrorHandled = true ;
338+
339+ // Log errors only if there are no user-provided error handlers
340+ if ( this . listenerCount ( 'error' ) === 0 ) {
285341 this . log ( socket . proxyChainId , `Source socket emitted error: ${ err . stack || err } ` ) ;
286342 }
287343 } ) ;
@@ -654,6 +710,7 @@ export class Server extends EventEmitter {
654710
655711 // For TLS sockets, bytesRead/bytesWritten might not be immediately available
656712 // Use nullish coalescing to ensure we always have valid numeric values
713+ // Note: Even destroyed sockets retain their byte count properties
657714 const result = {
658715 srcTxBytes : socket . bytesWritten ?? 0 ,
659716 srcRxBytes : socket . bytesRead ?? 0 ,
0 commit comments