Skip to content

Fix headless JS loading with -tlsi and addheader/setheader#7325

Open
dogancanbakir wants to merge 1 commit intodevfrom
fix/headless-tlsi-addheader
Open

Fix headless JS loading with -tlsi and addheader/setheader#7325
dogancanbakir wants to merge 1 commit intodevfrom
fix/headless-tlsi-addheader

Conversation

@dogancanbakir
Copy link
Copy Markdown
Member

@dogancanbakir dogancanbakir commented Apr 2, 2026

Proposed changes

Closes #6360

When -tlsi is used with headless templates containing addheader/setheader, all requests get hijacked through Go's HTTP client with utls-based TLS impersonation. utls negotiates h2 via ALPN but returns *utls.UConn instead of *tls.Conn, so Go's http.Transport can't detect the negotiated protocol and fails with "malformed HTTP response" when reading h2 frames.

Fix: use http2.Transport directly with utls dialing when TLS impersonation is enabled, properly handling h2 connections. Also log LoadResponse errors instead of silently discarding them.

Proof

before:

go run ./cmd/nuclei -t /tmp/headless-addheader-test.yaml -u https://example.com -headless -timeout 15 -tlsi

                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.7.1

                projectdiscovery.io

[INF] Current nuclei version: v3.7.1 (latest)
[INF] Current nuclei-templates version: v10.4.1 (latest)
[INF] New templates added in latest release: 76
[INF] Templates loaded for current scan: 1
[WRN] Loading 1 unsigned templates for scan. Use with caution.
[INF] Targets loaded for current scan: 1
[INF] Scan completed in 20.1685435s. No results found.

after:

go run ./cmd/nuclei -t /tmp/headless-addheader-test.yaml -u https://example.com -headless -timeout 15 -tlsi

                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.7.1

                projectdiscovery.io

[INF] Current nuclei version: v3.7.1 (latest)
[INF] Current nuclei-templates version: v10.4.1 (latest)
[INF] New templates added in latest release: 76
[INF] Templates loaded for current scan: 1
[WRN] Loading 1 unsigned templates for scan. Use with caution.
[INF] Targets loaded for current scan: 1
[headless-addheader-test] [headless] [info] https://example.com/
[INF] Scan completed in 7.376011375s. 1 matches found.

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)

@auto-assign auto-assign bot requested a review from Mzack9999 April 2, 2026 12:28
@neo-by-projectdiscovery-dev
Copy link
Copy Markdown

neo-by-projectdiscovery-dev bot commented Apr 2, 2026

Neo - PR Security Review

No security issues found

Hardening Notes
  • The switch to http2.Transport when TLS impersonation is enabled creates a new transport instance that does not inherit the proxy configuration set on the original http.Transport (lines 66, 81). This may cause proxy settings to be bypassed when using -tlsi flag, which could violate organizational policies requiring all traffic through a proxy. However, this is a configuration issue rather than an exploitable vulnerability.
  • Pre-existing InsecureSkipVerify: true (line 31) is appropriate for a security testing tool like Nuclei that needs to scan hosts with invalid certificates

Comment @pdneo help for available commands. · Open in Neo

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

Walkthrough

Two files modified to enhance headless browsing with TLS impersonation support and improve error handling during HTTP response loading. The primary change introduces conditional HTTP/2 transport setup with FastDialer when TLS impersonation is enabled, while the secondary change adds error capture and logging for response loading failures.

Changes

Cohort / File(s) Summary
TLS Impersonation Transport Configuration
pkg/protocols/headless/engine/http_client.go
Introduced HTTP/2 transport conditional logic when options.TlsImpersonate is enabled, using http2.Transport with DialTLSContext wired to dialers.Fastdialer.DialTLSWithConfigImpersonate() and custom TLS configuration. Fixed error propagation for options.AliveSocksProxy parsing to return actual parse errors instead of scoped variable.
Error Handling in Response Loading
pkg/protocols/headless/engine/rules.go
Enhanced routingRuleHandler to capture errors from ctx.LoadResponse() and log verbose diagnostics including request URL and error details on failure.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hopping down the headless trail,
TLS tricks that cannot fail,
HTTP/2 dances swift and bright,
Errors logged, headers set right,
FastDialer hops with impersonate flair!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references the primary fix: enabling TLS impersonation (-tlsi) with header actions (addheader/setheader) in headless templates, which aligns with the changeset's modifications to http_client.go and rules.go.
Linked Issues check ✅ Passed The code changes directly address #6360 by fixing HTTP/2 protocol handling when TLS impersonation is enabled, and improving error logging in the headless routing logic to prevent silent failures.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the TLS impersonation and headless JS loading issue: http_client.go adds http2.Transport support for impersonation, and rules.go improves error handling in the response loading pipeline.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/headless-tlsi-addheader

Comment @coderabbitai help to get the list of available commands and usage tips.

@dogancanbakir dogancanbakir self-assigned this Apr 2, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/protocols/headless/engine/rules.go (1)

52-54: Consider returning early on error to avoid processing incomplete response data.

After logging the error, execution continues with cookie handling, response rule processing, and history storage. If LoadResponse fails, the response object may be in an undefined state, potentially causing misleading behavior or additional errors downstream.

♻️ Proposed fix to return early on error
 		// perform the request
 		if err := ctx.LoadResponse(httpClient, true); err != nil {
 			gologger.Verbose().Msgf("headless: failed to load response for %s: %s", ctx.Request.URL(), err)
+			return
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/headless/engine/rules.go` around lines 52 - 54, The code logs
an error from ctx.LoadResponse(httpClient, true) but continues processing;
change the logic in rules.go to return early when ctx.LoadResponse(...) returns
an error—i.e., after the gologger.Verbose() call, exit the current function or
loop (depending on surrounding context) to prevent running cookie handling,
response rule processing, and history storage on an incomplete/invalid response;
reference ctx.LoadResponse and the subsequent cookie/response rule/history
handling blocks when making the early-return change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/protocols/headless/engine/http_client.go`:
- Around line 84-100: The new http2.Transport assigned to roundTripper when
options.TlsImpersonate is true ignores any proxy settings
(AliveHttpProxy/AliveSocksProxy) set earlier; update the DialTLSContext used in
that http2.Transport to use the same proxy-aware dialing logic as the original
transport (i.e., route through the configured HTTP/SOCKS proxy) instead of
calling dialers.Fastdialer.DialTLSWithConfigImpersonate directly. Concretely,
modify the DialTLSContext implementation in the http2.Transport block to detect
and apply the configured proxy (AliveHttpProxy/AliveSocksProxy) and perform TLS
impersonated dialing through that proxy (leveraging the existing proxy dialer
code path used by the original transport), ensuring TLSClientConfig (tlsConfig)
and impersonate.Random are preserved.

---

Nitpick comments:
In `@pkg/protocols/headless/engine/rules.go`:
- Around line 52-54: The code logs an error from ctx.LoadResponse(httpClient,
true) but continues processing; change the logic in rules.go to return early
when ctx.LoadResponse(...) returns an error—i.e., after the gologger.Verbose()
call, exit the current function or loop (depending on surrounding context) to
prevent running cookie handling, response rule processing, and history storage
on an incomplete/invalid response; reference ctx.LoadResponse and the subsequent
cookie/response rule/history handling blocks when making the early-return
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 47c3ab3c-2d17-4064-8b89-1991debf96d5

📥 Commits

Reviewing files that changed from the base of the PR and between c6fb1ae and 10cccb7.

📒 Files selected for processing (2)
  • pkg/protocols/headless/engine/http_client.go
  • pkg/protocols/headless/engine/rules.go

Comment on lines +84 to +100
var roundTripper http.RoundTripper = transport

// TLS impersonation via utls advertises h2 in ALPN to mimic a real browser.
// Since utls returns *utls.UConn (not *tls.Conn), Go's http.Transport can't
// detect the negotiated protocol and tries HTTP/1.x on an h2 connection,
// causing "malformed HTTP response" errors. We use http2.Transport directly
// with utls dialing to properly handle h2 connections (see #6360).
if options.TlsImpersonate {
roundTripper = &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
return dialers.Fastdialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil)
},
TLSClientConfig: tlsConfig,
AllowHTTP: false,
DisableCompression: false,
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's existing proxy handling with http2.Transport elsewhere in the codebase
echo "=== Searching for http2.Transport proxy handling patterns ==="
rg -n -C3 'http2\.Transport' --type=go

echo ""
echo "=== Check how fastdialer handles proxy with TLS impersonation ==="
ast-grep --pattern $'DialTLSWithConfigImpersonate($$$)'

Repository: projectdiscovery/nuclei

Length of output: 1340


🏁 Script executed:

#!/bin/bash
# Read the full context around lines 64-100 in http_client.go
echo "=== Full context of http_client.go lines 50-105 ==="
sed -n '50,105p' pkg/protocols/headless/engine/http_client.go | cat -n

echo ""
echo "=== Search for proxy configuration setup ==="
rg -n "Proxy|proxy|AliveHttpProxy|AliveSocksProxy" pkg/protocols/headless/engine/http_client.go -B2 -A2

echo ""
echo "=== Locate Fastdialer and DialTLSWithConfigImpersonate ==="
fd -name "*.go" -type f | xargs grep -l "DialTLSWithConfigImpersonate"

Repository: projectdiscovery/nuclei

Length of output: 3519


🏁 Script executed:

#!/bin/bash
# Find Fastdialer implementation
echo "=== Locating Fastdialer DialTLSWithConfigImpersonate ==="
fd "fastdialer" -type d

echo ""
echo "=== Search in common vendor/import paths ==="
fd -name "*.go" -type f 2>/dev/null | head -20 | xargs grep -l "DialTLSWithConfigImpersonate" 2>/dev/null | head -5

echo ""
echo "=== Alternative: check imports in http_client.go ==="
head -30 pkg/protocols/headless/engine/http_client.go | grep -E "import|dialers"

echo ""
echo "=== Check if fastdialer is external dependency ==="
if [ -f go.mod ]; then
  grep -i fastdialer go.mod | head -5
fi

Repository: projectdiscovery/nuclei

Length of output: 684


🏁 Script executed:

#!/bin/bash
# Verify http2.Transport capabilities and proxy handling
echo "=== Check golang.org/x/net/http2 import and usage ==="
grep -n "http2" pkg/protocols/headless/engine/http_client.go | head -10

echo ""
echo "=== Verify that http2.Transport is indeed without Proxy field ==="
# Check if anyone in codebase tries to set Proxy on http2.Transport
rg "http2\.Transport\s*\{" -A 15 pkg/

echo ""
echo "=== Check how this file uses roundTripper ==="
grep -n "roundTripper" pkg/protocols/headless/engine/http_client.go

Repository: projectdiscovery/nuclei

Length of output: 1748


Proxy configuration is lost when TLS impersonation is enabled.

When TlsImpersonate is true, the roundTripper is replaced with a new http2.Transport that doesn't incorporate any proxy settings configured on lines 64-82. If a user enables both -tlsi and a proxy (AliveHttpProxy or AliveSocksProxy), the proxy will be silently ignored.

The http2.Transport doesn't have a direct Proxy field. You need to integrate the proxy dialer into the DialTLSContext function for http2.Transport to preserve proxy support when TLS impersonation is enabled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/headless/engine/http_client.go` around lines 84 - 100, The new
http2.Transport assigned to roundTripper when options.TlsImpersonate is true
ignores any proxy settings (AliveHttpProxy/AliveSocksProxy) set earlier; update
the DialTLSContext used in that http2.Transport to use the same proxy-aware
dialing logic as the original transport (i.e., route through the configured
HTTP/SOCKS proxy) instead of calling
dialers.Fastdialer.DialTLSWithConfigImpersonate directly. Concretely, modify the
DialTLSContext implementation in the http2.Transport block to detect and apply
the configured proxy (AliveHttpProxy/AliveSocksProxy) and perform TLS
impersonated dialing through that proxy (leveraging the existing proxy dialer
code path used by the original transport), ensuring TLSClientConfig (tlsConfig)
and impersonate.Random are preserved.

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.

[BUG] [-tlsi] Headless doesn't load js when using with addheader / setheader

1 participant