Files
sub2api/deploy/EDGE_SECURITY.md
T

5.8 KiB

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

server.trusted_proxies must contain only the CIDR/IP addresses that connect directly to Sub2API, normally the local Nginx/Caddy address or the private load balancer subnet. An empty list disables forwarded-IP trust.

Never trust CF-Connecting-IP, X-Real-IP, or X-Forwarded-For 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.

Example for a proxy on the same host:

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.

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:

{
	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.