Proposed Changes

Three bugs caused tlsx to hang indefinitely for certain hosts (those that accept TCP but have problematic TLS configurations):

Bug 1: Broken select in ztls tlsHandshakeWithTimeout()

// BEFORE (broken): Handshake() blocks synchronously, ctx.Done() never checked
select {
case <-ctx.Done():
return error
case errChan <- tlsConn.Handshake(): // ← blocks here forever
}
// AFTER: Handshake runs in goroutine, select properly races both channels
go func() { errChan <- tlsConn.Handshake() }()
select {
case <-ctx.Done():
_ = tlsConn.Close() // unblock the goroutine
return error
case err := <-errChan:
return err
}

Bug 2: ztls cipher enumeration used context.TODO() (no timeout)

Each cipher handshake attempt now uses context.WithTimeout based on the configured timeout.

Bug 3: Standard tls cipher enumeration used bare conn.Handshake() (no timeout)

Changed to conn.HandshakeContext(ctx) with a proper timeout context.

Proof

Regression test (TestHandshakeTimeout): Starts a local TCP listener that accepts connections but never speaks TLS (simulating the problematic hosts). Verifies tlsx returns within the configured timeout instead of hanging:

=== RUN TestHandshakeTimeout
--- PASS: TestHandshakeTimeout (3.00s)
PASS

Without this fix, the test would hang for 10+ seconds and fail with “tlsx hung indefinitely”.

Checklist

  • PR created against the dev branch
  • All checks passed
  • Tests added that prove the fix is effective
  • Minimal, focused diff — only touches timeout/handshake paths

/claim #819

Claim

Total prize pool $1,324
Total paid $0
Status Pending
Submitted February 16, 2026
Last updated February 16, 2026

Contributors

JP

jpirstin

@jpirstin

100%

Sponsors

YO

youssefosama3820009-commits

@youssefosama3820009-commits

$1,224
PR

ProjectDiscovery

@projectdiscovery

$100