You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The `Forward`, `DomainForward`, and `BalancerForward` functions automatically set the `X-Real-IP` header to the actual client IP address obtained from `c.IP()` before forwarding the request upstream. This protects against IP spoofing attacks where a malicious client attempts to forge their IP address by sending a fake `X-Real-IP` header. Any existing `X-Real-IP` header on the incoming request is overwritten with the real client IP. Note that `DomainForward` only applies this overwrite when the request host matches the configured hostname; non-matching requests are not forwarded and are passed through unchanged.
32
+
The proxy middleware applies several defenses by default. They can be relaxed via `Config.SecurityPolicy` (for `Balancer`) or `proxy.WithSecurityPolicy` (for the runtime helpers `Do`, `Forward`, `DoRedirects`, `DoTimeout`, `DoDeadline`).
33
33
34
-
If you're using the `Balancer` function with the `Config` struct, you can achieve the same protection by using the `ModifyRequest` callback as shown in the examples below.
34
+
### SSRF protection
35
35
36
-
When using `Do`, `DoRedirects`, `DoDeadline`, or `DoTimeout` directly, the `X-Real-IP` header is not automatically set. You should set it manually if your upstream server requires it:
36
+
Upstream addresses that resolve to loopback, RFC 1918 private, link-local (including the `169.254.169.254` cloud-metadata address), multicast, unspecified, or RFC 6598 CGNAT ranges are rejected with `ErrUpstreamHostBlocked`. If any resolved IP falls in a blocked range the upstream is rejected, mitigating DNS-rebinding attempts that return a mix of public and private answers.
37
+
38
+
For `Balancer`, the resolved IP is re-validated at **dial time** (via a guarded `Dial` on each upstream `fasthttp.HostClient`), which both defeats DNS-rebinding and avoids resolving hostnames at startup — a transient DNS failure won't panic your application. DNS lookups are bounded by a 5-second timeout.
39
+
40
+
:::caution DNS-rebinding scope
41
+
The dial-time re-validation only applies to `Balancer`, because those `HostClient`s are constructed by the middleware. The runtime helpers — `Do`, `DoRedirects`, `DoTimeout`, `DoDeadline`, `Forward`, `DomainForward`, and `BalancerForward` — validate the upstream host up front, then dispatch through the shared or user-supplied `*fasthttp.Client`, which re-resolves the name without the guard. Against a **rebinding-capable resolver** these paths have a check/use window and are not fully mitigated. If that is part of your threat model, use `Balancer` (with `AllowPrivateIPs = false`), or supply a client whose `Dial` performs its own resolved-IP validation.
42
+
:::
43
+
44
+
Set `SecurityPolicy.AllowPrivateIPs = true` to opt out — required when proxying to internal services on the same network.
45
+
46
+
### Scheme allowlist
47
+
48
+
Only `http` and `https` upstream schemes are accepted by default; `file://`, `gopher://`, `ftp://`, and other schemes are rejected. Override via `SecurityPolicy.AllowedSchemes`.
49
+
50
+
### HTTPS-to-HTTP redirect downgrades
51
+
52
+
`DoRedirects` rejects redirects from HTTPS origins to plaintext HTTP targets with `ErrRedirectDowngrade`. Following such a redirect would leak any cookies or `Authorization` headers established under TLS. Set `SecurityPolicy.AllowHTTPSDowngrade = true` to override.
53
+
54
+
When a redirect crosses to a **different host**, `DoRedirects` strips the `Authorization`, `Proxy-Authorization`, and `Cookie` headers so credentials bound to the original origin are not forwarded to a third-party upstream. Same-host redirects retain these headers.
55
+
56
+
### RFC 7230 hop-by-hop header stripping
57
+
58
+
`Connection`, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `TE`, `Trailer`, `Transfer-Encoding`, and `Upgrade` are stripped from both the outbound request and the inbound response, along with every header listed in the `Connection` field per RFC 7230 §6.1. This prevents request smuggling (`TE`/`Transfer-Encoding`), proxy-credential forwarding, and protocol-upgrade leaks. The legacy `KeepConnectionHeader` option preserves only the literal `Connection` header for backwards compatibility; the other hop-by-hop headers are still stripped. To preserve every hop-by-hop header (not recommended), set `SecurityPolicy.KeepHopByHopHeaders = true`.
59
+
60
+
### TLS minimum version
61
+
62
+
`Config.TLSConfig` is cloned with `MinVersion: tls.VersionTLS12` if no minimum is configured, so deprecated TLS versions cannot be negotiated by accident.
63
+
64
+
### Response body size and connection caps
65
+
66
+
`Config.MaxResponseBodySize` bounds upstream response bodies to protect against memory exhaustion. `Config.MaxConnsPerHost` (default `1024`) caps concurrent connections per upstream to limit fan-out from a single hot host.
67
+
68
+
### X-Real-IP spoof prevention
69
+
70
+
`Forward`, `DomainForward`, and `BalancerForward` automatically overwrite the `X-Real-IP` header with `c.IP()` before forwarding, so clients cannot spoof their address. `DomainForward` only applies the overwrite when the request host matches the configured hostname (matched case-insensitively per RFC 9110 §4.2.3); non-matching requests are passed through unchanged.
71
+
72
+
If you're using `Balancer` with the `Config` struct, you can replicate the protection in `ModifyRequest`. When using `Do`, `DoRedirects`, `DoDeadline`, or `DoTimeout` directly, the `X-Real-IP` header is not set automatically — set it manually if needed:
37
73
38
74
```go
39
75
c.Request().Header.Set("X-Real-IP", c.IP())
40
76
```
41
77
78
+
### Path concatenation safety
79
+
80
+
`DomainForward` and `BalancerForward` previously concatenated the configured upstream with `c.OriginalURL()`. Crafted request paths beginning with `//` could exploit URL parsing to redirect the proxy at a different host (network-path reference injection). The proxy now sanitises the joined path so the upstream host pinned in configuration is preserved regardless of the inbound request.
| MaxConnsPerHost |`int`| Maximum number of connections per upstream host. The default proxy client and balancer host clients use this limit unless you override it with `WithClient`, a per-handler client, or `proxy.Config`. |`1024`|
182
235
| ReadBufferSize |`int`| Per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers (for example, BIG cookies). | (Not specified) |
| KeepConnectionHeader |`bool`| Keeps the `Connection` header when set to `true`. By default the header is removed to comply with RFC 7230 §6.1 and avoid proxy loops. |`false`|
185
-
| TLSConfig |`*tls.Config`| TLS config for the HTTP client. |`nil`|
237
+
| KeepConnectionHeader |`bool`| Keeps the `Connection` header when set to `true`. By default the header is removed to comply with RFC 7230 §6.1 and avoid proxy loops. Other hop-by-hop headers are still stripped regardless of this setting. |`false`|
238
+
| TLSConfig |`*tls.Config`| TLS config for the HTTP client. Cloned with `MinVersion: tls.VersionTLS12` when no minimum is set. |`nil`|
186
239
| DialDualStack |`bool`| Client will attempt to connect to both IPv4 and IPv6 host addresses if set to true. |`false`|
187
240
| Client |`*fasthttp.LBClient`| Client is a custom client when client config is complex. |`nil`|
241
+
| SecurityPolicy |`*SecurityPolicy`| Overrides the default SSRF, redirect, and hop-by-hop header rules for this balancer. When `nil`, the package-level policy set via `WithSecurityPolicy` is used. See [Security](#security). |`nil`|
242
+
| MaxResponseBodySize |`int`| Maximum upstream response body size in bytes. `0` keeps fasthttp's unlimited default. |`0`|
188
243
189
244
## Default Config
190
245
@@ -198,3 +253,19 @@ var ConfigDefault = Config{
198
253
KeepConnectionHeader: false,
199
254
}
200
255
```
256
+
257
+
## Default SecurityPolicy
258
+
259
+
When `Config.SecurityPolicy` is `nil` (and `proxy.WithSecurityPolicy` has not been called), the package falls back to the value returned by `proxy.DefaultSecurityPolicy()`:
260
+
261
+
```go
262
+
// DefaultSecurityPolicy returns the secure-by-default policy.
0 commit comments