From ea3ca94e53ca28a78e0b058b5504123f7fe3bcb1 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Tue, 23 Jun 2026 19:57:45 +0800 Subject: [PATCH] fix(handler): move auth before request validation so probe resistance works (#875) Probe resistance (probeResist=code:403) was always returning 400 Bad Request instead of the configured status code because the non-proxy-form request validation check (req.URL.Scheme != "http") ran before Authenticate(). Browser/scanner probes send GET / HTTP/1.1 which has an empty URL scheme, so they were rejected at the validation gate before probe resistance ever had a chance to respond. Swap the order: run Authenticate() first so probe resistance can intercept non-proxy-form probes with the configured decoy response. Legitimate clients with correct credentials proceed to request validation and routing as before. --- handler/http/handler.go | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/handler/http/handler.go b/handler/http/handler.go index 0c09bfe1..c35ef9e0 100644 --- a/handler/http/handler.go +++ b/handler/http/handler.go @@ -445,20 +445,9 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt ro.HTTP.Response.Header = resp.Header }() - // HTTP/2 connection preface "PRI * HTTP/2.0" is rejected, as are - // non-CONNECT requests without an http:// scheme. - if req.Method == "PRI" || - (req.Method != http.MethodConnect && req.URL.Scheme != "http") { - resp.StatusCode = http.StatusBadRequest - - if log.IsLevelEnabled(logger.TraceLevel) { - dump, _ := httputil.DumpResponse(resp, false) - log.Trace(string(dump)) - } - - return resp.Write(conn) - } - + // Authenticate before request validation so that probe resistance + // can intercept non-proxy-form requests (e.g., browser/scanner probes + // that send "GET / HTTP/1.1" instead of proxy-form URLs). result := h.auth.Authenticate(ctx, req) if !result.OK { if result.PipeTo != "" { @@ -477,6 +466,20 @@ func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *htt return errors.New("authentication failed") } + // HTTP/2 connection preface "PRI * HTTP/2.0" is rejected, as are + // non-CONNECT requests without an http:// scheme. + if req.Method == "PRI" || + (req.Method != http.MethodConnect && req.URL.Scheme != "http") { + resp.StatusCode = http.StatusBadRequest + + if log.IsLevelEnabled(logger.TraceLevel) { + dump, _ := httputil.DumpResponse(resp, false) + log.Trace(string(dump)) + } + + return resp.Write(conn) + } + log = log.WithFields(map[string]any{"clientID": result.ClientID}) ro.ClientID = result.ClientID