From 8ad83e8d52b29f4162a10dbb8300f734943dc370 Mon Sep 17 00:00:00 2001 From: Ben Browning Date: Thu, 2 Apr 2026 20:11:02 +0000 Subject: [PATCH] Return HTTP 403 for blocked CONNECT requests to stop client retry storms ConnectReject with no ctx.Resp caused a bare TCP close, which HTTP clients (npm, etc.) interpreted as a transient error and retried in a tight loop. Setting ctx.Resp to a 403 response gives clients a clear signal that the request was intentionally denied. Uses a generic message across all reject paths to avoid leaking policy details to the client. Co-Authored-By: Claude Opus 4.6 --- internal/proxy/proxy.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 7063639..185e23c 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -174,6 +174,10 @@ func (cf *ClientFilter) String() string { return strings.Join(parts, ", ") } +// rejectMsg is the generic response body for all rejected requests (CONNECT and plain HTTP). +// Intentionally vague to avoid revealing why the request was blocked. +const rejectMsg = "Request blocked by proxy policy" + // Config holds proxy configuration. type Config struct { ListenAddr string @@ -219,6 +223,7 @@ func New(cfg Config) *http.Server { srcIP := parseClientIP(ctx) if srcIP == nil || !cfg.ClientFilter.IsAllowed(srcIP) { log.Printf("CLIENT_REJECTED CONNECT %s from %s (not in allowed clients)", host, clientIP(ctx)) + ctx.Resp = goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, rejectMsg) return rejectConnect, host } } @@ -232,6 +237,7 @@ func New(cfg Config) *http.Server { if cfg.BlockedLogger != nil { cfg.BlockedLogger.Log(clientIP(ctx), "CONNECT", host) } + ctx.Resp = goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, rejectMsg) return rejectConnect, host } @@ -240,6 +246,7 @@ func New(cfg Config) *http.Server { if cfg.BlockedLogger != nil { cfg.BlockedLogger.Log(clientIP(ctx), "CONNECT", host) } + ctx.Resp = goproxy.NewResponse(ctx.Req, goproxy.ContentTypeText, http.StatusForbidden, rejectMsg) return rejectConnect, host } @@ -264,7 +271,7 @@ func New(cfg Config) *http.Server { return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, - "Client IP not allowed by proxy policy", + rejectMsg, ) } } @@ -284,7 +291,7 @@ func New(cfg Config) *http.Server { return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, - "Port not allowed by proxy policy", + rejectMsg, ) } } @@ -298,7 +305,7 @@ func New(cfg Config) *http.Server { return req, goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusForbidden, - "Domain not allowed by proxy policy", + rejectMsg, ) }