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.
This commit is contained in:
ginuerzh
2026-06-23 19:57:45 +08:00
parent 5d6b00c3a6
commit ea3ca94e53
+17 -14
View File
@@ -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