Proposed changes

Fixes #819 — tlsx hangs indefinitely when processing large target lists (~25k+ hosts).

/claim #819

Root causes

Three separate bugs cause the hang:

1. ztls tlsHandshakeWithTimeout is broken (critical)

// BEFORE: Handshake() evaluates synchronously — select can never reach ctx.Done()
select {
case <-ctx.Done():
return error // never reached while Handshake blocks
case errChan <- tlsConn.Handshake(): // blocks here forever
}

Go evaluates tlsConn.Handshake() before attempting the channel send, so the select statement can never choose ctx.Done() while the handshake is blocking on an unresponsive server.

Fix: Run Handshake() in a goroutine. On timeout, close the underlying TCP connection to unblock the goroutine (closing the zcrypto TLS conn directly would deadlock on its internal mutex).

2. ztls cipher enumeration uses context.TODO() (critical)

// BEFORE: no timeout — hangs forever if server doesn't respond
c.tlsHandshakeWithTimeout(conn, context.TODO())

Even with Bug 1 fixed, context.TODO() has no deadline, so cipher probes hang forever.

Fix: Create a timeout context from c.options.Timeout (default 10s fallback).

3. Standard TLS cipher enumeration has no timeout (critical)

// BEFORE: bare Handshake() with no context
conn.Handshake()

Fix: Use conn.HandshakeContext(ctx) with a properly timed context.

Changes

File Change
pkg/tlsx/ztls/ztls.go Fix tlsHandshakeWithTimeout to run handshake in goroutine; close raw conn on timeout; add cipherHandshakeContext helper; use it in EnumerateCiphers
pkg/tlsx/tls/tls.go Add cipherHandshakeContext helper; use HandshakeContext in EnumerateCiphers
pkg/tlsx/ztls/ztls_timeout_test.go Test: handshake interrupted in ~500ms (not hanging); context timeout helpers
pkg/tlsx/tls/tls_timeout_test.go Test: context timeout helpers for ctls path

Proof

$ go test ./pkg/tlsx/ztls/ -run "TestTLSHandshakeWithTimeout|TestCipherHandshakeContext" -v
=== RUN TestTLSHandshakeWithTimeout_ContextCancellation
--- PASS: TestTLSHandshakeWithTimeout_ContextCancellation (0.50s)
=== RUN TestCipherHandshakeContext_UsesConfiguredTimeout
--- PASS: TestCipherHandshakeContext_UsesConfiguredTimeout (0.00s)
=== RUN TestCipherHandshakeContext_DefaultsWhenZero
--- PASS: TestCipherHandshakeContext_DefaultsWhenZero (0.00s)
PASS
$ go test ./pkg/tlsx/tls/ -run "TestCipherHandshakeContext" -v
=== RUN TestCipherHandshakeContext_UsesConfiguredTimeout
--- PASS: TestCipherHandshakeContext_UsesConfiguredTimeout (0.00s)
=== RUN TestCipherHandshakeContext_DefaultsWhenZero
--- PASS: TestCipherHandshakeContext_DefaultsWhenZero (0.00s)
PASS

go vet ./... and go build ./... pass clean.

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Claim

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

Contributors

NE

Nenad Ilic

@nenadilic84

100%

Sponsors

YO

youssefosama3820009-commits

@youssefosama3820009-commits

$1,224
PR

ProjectDiscovery

@projectdiscovery

$100