2026-07-18 00:11:18 +08:00
|
|
|
# Edge and HTTP Ingress Security
|
|
|
|
|
|
|
|
|
|
Sub2API supports long-lived SSE and WebSocket requests. Protect the request
|
|
|
|
|
ingress without imposing a response `WriteTimeout`: a write deadline would
|
|
|
|
|
terminate healthy long generations and streams.
|
|
|
|
|
|
|
|
|
|
## Application defaults
|
|
|
|
|
|
|
|
|
|
- `server.max_header_bytes: 65536` limits HTTP/1 request headers to 64 KiB;
|
|
|
|
|
Go maps it to the corresponding HTTP/2 header-list limit.
|
|
|
|
|
- `server.read_header_timeout: 10` bounds slow-header attacks. It does not
|
|
|
|
|
limit request processing or response streaming.
|
|
|
|
|
- `server.max_request_body_size: 268435456` is the absolute 256 MiB safety net.
|
|
|
|
|
- `gateway.max_body_size: 268435456` remains available to multimodal, Gemini,
|
|
|
|
|
image, video, and batch-image endpoints.
|
|
|
|
|
- `gateway.text_max_body_size: 33554432` limits the known pure-text
|
|
|
|
|
`/embeddings` and `/alpha/search` endpoints to 32 MiB.
|
|
|
|
|
- H2C defaults to 50 concurrent streams per connection, a 2 MiB connection
|
|
|
|
|
upload window, and a 512 KiB stream upload window.
|
|
|
|
|
- Invalid credential abuse is limited in process by trusted client IP (IPv6
|
|
|
|
|
`/64`): 120 failures per 60 seconds followed by a 60-second block. This is a
|
|
|
|
|
per-instance safety net; multi-instance enforcement still belongs at the
|
|
|
|
|
load balancer, CDN, or WAF.
|
|
|
|
|
|
|
|
|
|
Do not add a single application-wide request semaphore: an SSE request may
|
|
|
|
|
legitimately occupy it for many minutes. Apply connection and unauthenticated
|
|
|
|
|
request controls at the edge; authenticated user/API-key concurrency remains
|
|
|
|
|
the application's responsibility.
|
|
|
|
|
|
|
|
|
|
## Trusted client IPs
|
|
|
|
|
|
2026-07-19 19:41:01 +08:00
|
|
|
`server.trusted_proxies` controls forwarded-IP trust for security-sensitive
|
|
|
|
|
paths such as API-key ACLs, session binding, and rejection aggregation. Fresh
|
|
|
|
|
installations default to local/container ranges (`127.0.0.0/8`, `::1/128`,
|
|
|
|
|
`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, and `fc00::/7`) so a local
|
|
|
|
|
Nginx/Caddy or Docker bridge works without a migration. For a remote load
|
|
|
|
|
balancer, replace the defaults with only the CIDRs that connect directly to
|
|
|
|
|
Sub2API. An explicit empty list disables forwarded-IP trust for these paths;
|
|
|
|
|
ordinary request/usage metadata keeps its legacy compatibility behavior.
|
2026-07-18 00:11:18 +08:00
|
|
|
|
2026-07-19 19:41:01 +08:00
|
|
|
Never use `CF-Connecting-IP`, `X-Real-IP`, or `X-Forwarded-For` for an ACL or
|
|
|
|
|
session decision merely because the header exists. A CDN deployment must
|
|
|
|
|
firewall the origin so only the CDN or load balancer can reach it, and the proxy
|
|
|
|
|
must overwrite forwarded headers.
|
2026-07-18 00:11:18 +08:00
|
|
|
|
2026-07-19 19:41:01 +08:00
|
|
|
Example for a proxy on the same host (the default already covers this case):
|
2026-07-18 00:11:18 +08:00
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
server:
|
|
|
|
|
trusted_proxies:
|
|
|
|
|
- 127.0.0.1/32
|
|
|
|
|
- ::1/128
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Nginx baseline
|
|
|
|
|
|
|
|
|
|
Define shared zones in the `http` block. Tune rates to measured legitimate
|
|
|
|
|
traffic; the values below are conservative starting points, not universal
|
|
|
|
|
capacity targets.
|
|
|
|
|
|
|
|
|
|
```nginx
|
|
|
|
|
limit_conn_zone $binary_remote_addr zone=sub2api_conn:20m;
|
|
|
|
|
limit_req_zone $binary_remote_addr zone=sub2api_auth:20m rate=5r/s;
|
|
|
|
|
limit_req_zone $binary_remote_addr zone=sub2api_api:40m rate=30r/s;
|
|
|
|
|
map $http_upgrade $connection_upgrade {
|
|
|
|
|
default upgrade;
|
|
|
|
|
'' close;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
listen 443 ssl http2;
|
|
|
|
|
server_name api.example.com;
|
|
|
|
|
|
|
|
|
|
client_header_timeout 10s;
|
|
|
|
|
client_max_body_size 256m;
|
|
|
|
|
large_client_header_buffers 4 16k;
|
|
|
|
|
limit_conn sub2api_conn 40;
|
|
|
|
|
|
|
|
|
|
location ~ ^/(auth|api/auth)/ {
|
|
|
|
|
limit_req zone=sub2api_auth burst=10 nodelay;
|
|
|
|
|
proxy_pass http://127.0.0.1:8080;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
location ~ ^/(v1/)?(embeddings|alpha/search)$ {
|
|
|
|
|
client_max_body_size 32m;
|
|
|
|
|
limit_req zone=sub2api_api burst=60 nodelay;
|
|
|
|
|
proxy_pass http://127.0.0.1:8080;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
location / {
|
|
|
|
|
limit_req zone=sub2api_api burst=60 nodelay;
|
|
|
|
|
proxy_http_version 1.1;
|
|
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
proxy_set_header X-Forwarded-For $remote_addr;
|
|
|
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
|
|
|
proxy_set_header Connection $connection_upgrade;
|
|
|
|
|
proxy_buffering off;
|
|
|
|
|
proxy_request_buffering off;
|
|
|
|
|
proxy_read_timeout 1800s;
|
|
|
|
|
proxy_send_timeout 1800s;
|
|
|
|
|
proxy_pass http://127.0.0.1:8080;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Do not use an incoming `$http_x_forwarded_for` value unless Nginx real-IP
|
|
|
|
|
processing is restricted to explicit trusted proxy CIDRs.
|
|
|
|
|
|
|
|
|
|
## Caddy and CDN
|
|
|
|
|
|
|
|
|
|
The bundled `deploy/Caddyfile` sets a 64 KiB header limit, a 10-second header
|
|
|
|
|
timeout, a 256 MiB absolute body limit, and overwrites forwarded addresses from
|
|
|
|
|
the TCP peer. It is therefore a direct-to-Caddy baseline. Do not use its
|
|
|
|
|
`{remote_host}` forwarding lines unchanged behind a CDN: all clients would be
|
|
|
|
|
attributed to a CDN egress address, collapsing rejection aggregation and the
|
|
|
|
|
invalid-auth limiter onto unrelated users.
|
|
|
|
|
|
|
|
|
|
For a CDN deployment, first firewall the origin so only current CDN egress
|
|
|
|
|
CIDRs can connect. Then configure those exact ranges as Caddy trusted proxies
|
|
|
|
|
and derive upstream headers from Caddy's parsed `{client_ip}`. For example:
|
|
|
|
|
|
|
|
|
|
```caddyfile
|
|
|
|
|
{
|
|
|
|
|
servers {
|
|
|
|
|
trusted_proxies static 192.0.2.0/24 2001:db8:1234::/48
|
|
|
|
|
trusted_proxies_strict
|
|
|
|
|
client_ip_headers CF-Connecting-IP X-Forwarded-For
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
api.example.com {
|
|
|
|
|
reverse_proxy 127.0.0.1:8080 {
|
|
|
|
|
header_up X-Real-IP {client_ip}
|
|
|
|
|
header_up X-Forwarded-For {client_ip}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Replace the documentation ranges with the CDN's published, automatically
|
|
|
|
|
maintained egress ranges. `CF-Connecting-IP` is safe here only because direct
|
|
|
|
|
origin access is blocked and Caddy trusts only those TCP peers. Configure
|
|
|
|
|
Sub2API `server.trusted_proxies` with the Caddy address/private subnet so the
|
|
|
|
|
application accepts only Caddy's rewritten headers.
|
|
|
|
|
|
|
|
|
|
Caddy core does not provide a general request-rate limiter; use a trusted
|
|
|
|
|
CDN/WAF, a supported rate-limit module, or host firewall controls.
|
|
|
|
|
|
|
|
|
|
At a CDN/WAF, configure connection limits, header/body limits, bot challenges,
|
|
|
|
|
and per-IP/ASN rates before traffic reaches the origin. Allow origin ingress
|
|
|
|
|
only from CDN egress CIDRs or a private load balancer. Keep the application port
|
|
|
|
|
off the public Internet.
|
|
|
|
|
|
|
|
|
|
## DDoS boundary
|
|
|
|
|
|
|
|
|
|
Application checks reduce amplification after a connection reaches Go. They
|
|
|
|
|
cannot absorb volumetric attacks, TLS floods, bandwidth saturation, or a large
|
|
|
|
|
distributed source set. Those require upstream network capacity, CDN/WAF
|
|
|
|
|
filtering, provider firewall rules, and origin isolation. Avoid high-cardinality
|
|
|
|
|
metrics or per-request database security logs during rejection storms.
|