From 55da24f109a85d2d4bea4a5873c7d4abd5f3b497 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 13:32:41 +0530 Subject: [PATCH 01/11] Update option.go --- common/httpx/option.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/httpx/option.go b/common/httpx/option.go index fb108729..4b905efc 100644 --- a/common/httpx/option.go +++ b/common/httpx/option.go @@ -63,6 +63,7 @@ type Options struct { CDNCheckClient *cdncheck.Client Protocol Proto Trace bool + DisableHTTPFallback bool } // DefaultOptions contains the default options From 133d9b05027d0a3f5995700683ff3896c7c6cd09 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 13:36:15 +0530 Subject: [PATCH 02/11] Update option.go --- common/httpx/option.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/httpx/option.go b/common/httpx/option.go index 4b905efc..f4e85c80 100644 --- a/common/httpx/option.go +++ b/common/httpx/option.go @@ -85,6 +85,7 @@ var DefaultOptions = Options{ VHostStripHTML: false, VHostSimilarityRatio: 85, DefaultUserAgent: "httpx - Open-source project (github.com/projectdiscovery/httpx)", + DisableHTTPFallback: false, } func (options *Options) parseCustomCookies() { From fd2571a70d5f81c2206ccb3819343158dae62bc5 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 13:36:55 +0530 Subject: [PATCH 03/11] Implement HTTP/2 fallback error handling --- common/httpx/retry.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 common/httpx/retry.go diff --git a/common/httpx/retry.go b/common/httpx/retry.go new file mode 100644 index 00000000..9058e64f --- /dev/null +++ b/common/httpx/retry.go @@ -0,0 +1,29 @@ +package httpx + +import ( + "context" + "net/http" + "strings" + + "github.com/hashicorp/go-retryablehttp" +) + +func isHTTP2FallbackError(err error) bool { + if err == nil { + return false + } + errorMsg := err.Error() + return strings.Contains(errorMsg, "malformed HTTP version \"HTTP/2\"") || + strings.Contains(errorMsg, "malformed HTTP response") +} + +func (c *Client) NewCheckRetryFunc() retryablehttp.CheckRetryFunc { + return func(ctx context.Context, resp *http.Response, err error) (bool, error) { + if c.Options.Protocol == "http11" && c.Options.DisableHTTPFallback { + if isHTTP2FallbackError(err) { + return false, err + } + } + return retryablehttp.DefaultRetryPolicy(ctx, resp, err) + } +} From 6b90cba3d35db5d5af39ca20015ad798deadf326 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 13:43:37 +0530 Subject: [PATCH 04/11] Implement custom CheckRetry for HTTP/1.1 Add custom CheckRetry function for HTTP/1.1 with fallback control. --- common/httpx/httpx.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/httpx/httpx.go b/common/httpx/httpx.go index 039f4c4c..175dcbb4 100644 --- a/common/httpx/httpx.go +++ b/common/httpx/httpx.go @@ -183,6 +183,10 @@ func New(options *Options) (*HTTPX, error) { CheckRedirect: redirectFunc, }, retryablehttpOptions) + if httpx.Options.Protocol == "http11" && httpx.Options.DisableHTTPFallback { + httpx.client.CheckRetry = getCustomCheckRetry(httpx.Options) +} + transport2 := &http2.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, @@ -468,4 +472,15 @@ func (httpx *HTTPX) Sanitize(respStr string, trimLine, normalizeSpaces bool) str respStr = httputilz.NormalizeSpaces(respStr) } return respStr + // getCustomCheckRetry returns a custom CheckRetry function that respects DisableHTTPFallback + func getCustomCheckRetry(opts *Options) retryablehttp.CheckRetryFunc { + return func(ctx context.Context, resp *http.Response, err error) (bool, error) { + if opts.Protocol == "http11" && opts.DisableHTTPFallback { + if isHTTP2FallbackError(err) { + return false, err + } + } + return retryablehttp.DefaultRetryPolicy(ctx, resp, err) + } +} } From 20c4bc962720c15fc76d5b196529655f86f9067c Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 13:55:11 +0530 Subject: [PATCH 05/11] Add option to disable HTTP fallback on protocol errors --- runner/options.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runner/options.go b/runner/options.go index 6361f5c1..9877ad10 100644 --- a/runner/options.go +++ b/runner/options.go @@ -561,6 +561,7 @@ func ParseOptions() *Options { flagSet.IntVarP(&options.StatsInterval, "stats-interval", "si", 0, "number of seconds to wait between showing a statistics update (default: 5)"), flagSet.BoolVarP(&options.NoColor, "no-color", "nc", false, "disable colors in cli output"), flagSet.BoolVarP(&options.Trace, "trace", "tr", false, "trace"), + flagSet.BoolVar(&options.DisableHTTPFallback, "dhf", false,"Disable HTTP/2 fallback on protocol errors when using HTTP/1.1 (-pr http11)") ) flagSet.CreateGroup("Optimizations", "Optimizations", From 5cd86bd945f527865edb4d82e2717fa36b370631 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 13:56:08 +0530 Subject: [PATCH 06/11] Add tests for HTTP fallback behavior --- .../httpx/httpx_disablehttpfallback_test.go | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 common/httpx/httpx_disablehttpfallback_test.go diff --git a/common/httpx/httpx_disablehttpfallback_test.go b/common/httpx/httpx_disablehttpfallback_test.go new file mode 100644 index 00000000..43ebdf63 --- /dev/null +++ b/common/httpx/httpx_disablehttpfallback_test.go @@ -0,0 +1,123 @@ +package httpx + +import ( + "context" + "errors" + "testing" + "time" +) + +func TestDisableHTTPFallbackBlocks(t *testing.T) { + opts := &Options{ + Protocol: "http11", + DisableHTTPFallback: true, + Timeout: 5 * time.Second, + RetryMax: 3, + } + + client, err := NewClient(opts) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + checkRetry := client.NewCheckRetryFunc() + http2Error := errors.New( + "net/http: HTTP/1.x transport connection broken: malformed HTTP version \"HTTP/2\"") + + shouldRetry, returnedErr := checkRetry(context.Background(), nil, http2Error) + + if shouldRetry { + t.Error("Expected shouldRetry=false when DisableHTTPFallback=true") + } + if returnedErr == nil { + t.Error("Expected error to be returned") + } +} + +func TestDisableHTTPFallbackAllows(t *testing.T) { + opts := &Options{ + Protocol: "http11", + DisableHTTPFallback: false, + Timeout: 5 * time.Second, + RetryMax: 3, + } + + client, err := NewClient(opts) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + checkRetry := client.NewCheckRetryFunc() + http2Error := errors.New( + "net/http: HTTP/1.x transport connection broken: malformed HTTP version \"HTTP/2\"") + + shouldRetry, _ := checkRetry(context.Background(), nil, http2Error) + + if !shouldRetry { + t.Error("Expected shouldRetry=true when DisableHTTPFallback=false") + } +} + +func TestNonHTTP2ErrorsRetry(t *testing.T) { + opts := &Options{ + Protocol: "http11", + DisableHTTPFallback: true, + Timeout: 5 * time.Second, + RetryMax: 3, + } + + client, err := NewClient(opts) + if err != nil { + t.Fatalf("Failed to create client: %v", err) + } + + checkRetry := client.NewCheckRetryFunc() + timeoutErr := errors.New("context deadline exceeded") + + shouldRetry, _ := checkRetry(context.Background(), nil, timeoutErr) + + if !shouldRetry { + t.Error("Expected timeout error to still trigger retry") + } +} + +func TestIsHTTP2FallbackError(t *testing.T) { + testCases := []struct { + name string + errMsg string + shouldMatch bool + }{ + { + name: "Malformed HTTP/2 version", + errMsg: "malformed HTTP version \"HTTP/2\"", + shouldMatch: true, + }, + { + name: "Malformed HTTP response", + errMsg: "malformed HTTP response", + shouldMatch: true, + }, + { + name: "Generic error", + errMsg: "connection refused", + shouldMatch: false, + }, + { + name: "Timeout error", + errMsg: "context deadline exceeded", + shouldMatch: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := errors.New(tc.errMsg) + result := isHTTP2FallbackError(err) + + if result != tc.shouldMatch { + t.Errorf("Expected %v, got %v for: %s", tc.shouldMatch, result, tc.errMsg) + } + }) + } +} + From ca0d1475c329c74b29df6255bde791b1301dc849 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 13:59:22 +0530 Subject: [PATCH 07/11] Add HTTP/2 fallback disable instructions Added instructions for disabling HTTP/2 fallback with the -dhf flag. --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7833966a..293e2a4b 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,14 @@ For details about running httpx, see https://docs.projectdiscovery.io/tools/http - As default, `httpx` probe with **HTTPS** scheme and fall-back to **HTTP** only if **HTTPS** is not reachable. - Burp Suite XML exports can be used as input with `-l burp-export.xml -im burp` - The `-no-fallback` flag can be used to probe and display both **HTTP** and **HTTPS** result. +- ### Disable HTTP/2 Fallback + +Use the `-dhf` flag to prevent automatic fallback to HTTP/2 when using HTTP/1.1 mode: +```bash +httpx -u http://target.com -pr http11 -dhf +``` + +This ensures strict HTTP/1.1 protocol compliance and prevents automatic protocol switching during retries. - Custom scheme for ports can be defined, for example `-ports http:443,http:80,https:8443` - Custom resolver supports multiple protocol (**doh|tcp|udp**) in form of `protocol:resolver:port` (e.g. `udp:127.0.0.1:53`) - Secret files can be used for domain-based authentication via `-sf secrets.yaml`. Supported auth types: `BasicAuth`, `BearerToken`, `Header`, `Cookie`, `Query`. Example: @@ -336,4 +344,4 @@ Probing feature is inspired by [@tomnomnom/httprobe](https://github.com/tomnomno Join Discord - \ No newline at end of file + From f0aafb36764caaae8662204a10e18cb531a0ead5 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 14:16:39 +0530 Subject: [PATCH 08/11] Update options.go --- runner/options.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runner/options.go b/runner/options.go index 9877ad10..f8739632 100644 --- a/runner/options.go +++ b/runner/options.go @@ -531,6 +531,7 @@ func ParseOptions() *Options { flagSet.BoolVar(&options.Unsafe, "unsafe", false, "send raw requests skipping golang normalization"), flagSet.BoolVar(&options.Resume, "resume", false, "resume scan using resume.cfg"), flagSet.BoolVarP(&options.FollowRedirects, "follow-redirects", "fr", false, "follow http redirects"), + flagSet.BoolVarP(&options.DisableHTTPFallback, "dhf", "", false, "Disable HTTP/2 fallback on protocol errors when using HTTP/1.1 (-pr http11)"), flagSet.IntVarP(&options.MaxRedirects, "max-redirects", "maxr", 10, "max number of redirects to follow per host"), flagSet.BoolVarP(&options.FollowHostRedirects, "follow-host-redirects", "fhr", false, "follow redirects on the same host"), flagSet.BoolVarP(&options.RespectHSTS, "respect-hsts", "rhsts", false, "respect HSTS response headers for redirect requests"), From 1e86afa87944e53d04275c7003cac725b068f46d Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 14:24:17 +0530 Subject: [PATCH 09/11] Update options.go --- runner/options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner/options.go b/runner/options.go index f8739632..7985d8e3 100644 --- a/runner/options.go +++ b/runner/options.go @@ -530,7 +530,7 @@ func ParseOptions() *Options { flagSet.StringVarP(&options.Proxy, "proxy", "http-proxy", "", "proxy (http|socks) to use (eg http://127.0.0.1:8080)"), flagSet.BoolVar(&options.Unsafe, "unsafe", false, "send raw requests skipping golang normalization"), flagSet.BoolVar(&options.Resume, "resume", false, "resume scan using resume.cfg"), - flagSet.BoolVarP(&options.FollowRedirects, "follow-redirects", "fr", false, "follow http redirects"), + (&options.FollowRedirects, "follow-redirects", "fr", false, "follow http redirects"), flagSet.BoolVarP(&options.DisableHTTPFallback, "dhf", "", false, "Disable HTTP/2 fallback on protocol errors when using HTTP/1.1 (-pr http11)"), flagSet.IntVarP(&options.MaxRedirects, "max-redirects", "maxr", 10, "max number of redirects to follow per host"), flagSet.BoolVarP(&options.FollowHostRedirects, "follow-host-redirects", "fhr", false, "follow redirects on the same host"), From 315cf2731adefb459e83fce3faeda9741250dbc9 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 14:28:44 +0530 Subject: [PATCH 10/11] Update options.go --- runner/options.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/runner/options.go b/runner/options.go index 7985d8e3..0dce3942 100644 --- a/runner/options.go +++ b/runner/options.go @@ -518,11 +518,11 @@ func ParseOptions() *Options { flagSet.BoolVarP(&options.ResultDatabaseOmitRaw, "result-db-omit-raw", "rdbor", false, "omit raw request/response data from database"), ) - flagSet.CreateGroup("configs", "Configurations", - flagSet.StringVar(&cfgFile, "config", "", "path to the httpx configuration file (default $HOME/.config/httpx/config.yaml)"), - flagSet.StringSliceVarP(&options.Resolvers, "resolvers", "r", nil, "list of custom resolver (file or comma separated)", goflags.NormalizedStringSliceOptions), - flagSet.Var(&options.Allow, "allow", "allowed list of IP/CIDR's to process (file or comma separated)"), - flagSet.Var(&options.Deny, "deny", "denied list of IP/CIDR's to process (file or comma separated)"), + flagSet.CreateGroup("configs", "Configurations", + flagSet.StringVar(&cfgFile, "config", "", "path to the httpx configuration file (default $HOME/.config/httpx/config.yaml)"), + flagSet.StringSliceVarP(&options.Resolvers, "resolvers", "r", nil, "list of custom resolver (file or comma separated)", goflags.NormalizedStringSliceOptions), + flagSet.Var(&options.Allow, "allow", "allowed list of IP/CIDR's to process (file or comma separated)"), + flagSet.Var(&options.Deny, "deny", "denied list of IP/CIDR's to process (file or comma separated)"), flagSet.StringVarP(&options.SniName, "sni-name", "sni", "", "custom TLS SNI name"), flagSet.BoolVar(&options.RandomAgent, "random-agent", true, "enable Random User-Agent to use"), flagSet.BoolVar(&options.AutoReferer, "auto-referer", false, "set the Referer header to the current URL"), @@ -530,8 +530,8 @@ func ParseOptions() *Options { flagSet.StringVarP(&options.Proxy, "proxy", "http-proxy", "", "proxy (http|socks) to use (eg http://127.0.0.1:8080)"), flagSet.BoolVar(&options.Unsafe, "unsafe", false, "send raw requests skipping golang normalization"), flagSet.BoolVar(&options.Resume, "resume", false, "resume scan using resume.cfg"), - (&options.FollowRedirects, "follow-redirects", "fr", false, "follow http redirects"), - flagSet.BoolVarP(&options.DisableHTTPFallback, "dhf", "", false, "Disable HTTP/2 fallback on protocol errors when using HTTP/1.1 (-pr http11)"), + flagSet.BoolVarP(&options.FollowRedirects, "follow-redirects", "fr", false, "follow http redirects"), + flagSet.BoolVarP(&options.DisableHTTPFallback, "dhf", "", false, "Disable HTTP/2 fallback on protocol errors when using HTTP/1.1 (-pr http11)"), flagSet.IntVarP(&options.MaxRedirects, "max-redirects", "maxr", 10, "max number of redirects to follow per host"), flagSet.BoolVarP(&options.FollowHostRedirects, "follow-host-redirects", "fhr", false, "follow redirects on the same host"), flagSet.BoolVarP(&options.RespectHSTS, "respect-hsts", "rhsts", false, "respect HSTS response headers for redirect requests"), @@ -547,7 +547,7 @@ func ParseOptions() *Options { flagSet.BoolVar(&options.DisableStdin, "no-stdin", false, "Disable Stdin processing"), flagSet.StringVarP(&options.HttpApiEndpoint, "http-api-endpoint", "hae", "", "experimental http api endpoint"), flagSet.StringVarP(&options.SecretFile, "secret-file", "sf", "", "path to the secret file for authentication"), - ) +) flagSet.CreateGroup("debug", "Debug", flagSet.BoolVarP(&options.HealthCheck, "hc", "health-check", false, "run diagnostic check up"), @@ -562,8 +562,8 @@ func ParseOptions() *Options { flagSet.IntVarP(&options.StatsInterval, "stats-interval", "si", 0, "number of seconds to wait between showing a statistics update (default: 5)"), flagSet.BoolVarP(&options.NoColor, "no-color", "nc", false, "disable colors in cli output"), flagSet.BoolVarP(&options.Trace, "trace", "tr", false, "trace"), - flagSet.BoolVar(&options.DisableHTTPFallback, "dhf", false,"Disable HTTP/2 fallback on protocol errors when using HTTP/1.1 (-pr http11)") - ) + +) flagSet.CreateGroup("Optimizations", "Optimizations", flagSet.BoolVarP(&options.NoFallback, "no-fallback", "nf", false, "display both probed protocol (HTTPS and HTTP)"), From d5a94ff8bda2a188d70c1678f4f9954f15132486 Mon Sep 17 00:00:00 2001 From: hetbhatt2807 Date: Wed, 4 Mar 2026 14:36:29 +0530 Subject: [PATCH 11/11] Update options.go --- runner/options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner/options.go b/runner/options.go index 0dce3942..ab7ab76f 100644 --- a/runner/options.go +++ b/runner/options.go @@ -369,7 +369,7 @@ type Options struct { ResultDatabaseTable string ResultDatabaseBatchSize int ResultDatabaseOmitRaw bool - + DisableHTTPFallback bool // Optional pre-created objects to reduce allocations Wappalyzer *wappalyzer.Wappalyze Networkpolicy *networkpolicy.NetworkPolicy