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
{{ message }}
Repository navigation
Opt-in request security for env servers (rate limiting, IP lists, payload screening) #1360
Env servers are plain HTTP/WebSocket services, and the common deployments (Spaces, containers on public ingress) expose reset/step/MCP to anyone who finds the URL. There's nothing at the request layer today: no rate limiting, no IP blocking, no payload screening. The concurrency limits stop honest overload, not someone deliberately hammering /step or scanning the surface.
I maintain guard-core / fastapi-guard and I'd like to wire an opt-in security middleware into create_fastapi_app and the gradio web path. The shape I went with:
default off; enabling without the extra installed raises a clear ImportError with the install command
dependency lives in a security extra so the core dependency set doesn't grow
config through OPENENV_GUARD_* env vars: rate limit + window, auto-ban, IP block/allow lists, user-agent blocklist, trusted proxies
Redis optional for shared limits/bans across workers, in-memory otherwise
/health, /schema, /metadata and docs paths stay probe-able, but IP lists still apply on them (fastapi-guard enforces IP lists and rate limiting on excluded paths by design, which is the behavior you want)
I have a branch ready: 5 new tests, both app creation paths covered, zero behavior change when disabled. Opening the PR right after this issue so you get a deliverable and a discussion in the same motion. If you'd rather see this go through an RFC first, say so and I'll write it up.
Before merging #1361, should protection cover WebSocket connections and messages too? Please specify the supported threat model and proxy trust defaults.
'Yes' on connections, and #1361 now covers them. 'No' on messages, by design.
WebSocket: The new commit runs fastapi-guard's guard_websocket on every handshake when the guard is on (ban check, IP and country lists, a rate-limit bucket separate from HTTP, and penetration detection on the path, query and headers). Messages on an accepted socket aren't screened: they're actions and observations, the same model content that keeps body scanning opt-in. 2 limits I can think of: there's no per-message rate limit (the concurrent-session cap still applies), and a ban issued mid-session applies from the next handshake, not to the socket that's already open.
Threat model. An env server on a public URL (a Space, a container behind ingress) facing unauthenticated clients. In scope: request floods from one IP (per-IP limits, and repeated 429s turn into a temporary ban), scanners probing paths, query strings and headers, and sources the operator wants out (IP/CIDR lists, user agents, countries, cloud providers). Out of scope: authentication (anyone who gets through can still reset and step; a private Space or a token does that job), WebSocket messages, request bodies unless OPENENV_GUARD_SCAN_BODY is set, and floods spread across many IPs beyond what per-IP limits catch.
Proxy trust. No proxy is trusted by default: X-Forwarded-For is ignored and the client is the TCP peer, so no client can spoof its way past bans or limits. Behind a proxy, set OPENENV_GUARD_TRUSTED_PROXIES (IPs or CIDRs) and OPENENV_GUARD_TRUSTED_PROXY_DEPTH (default 1); without it every client shares the proxy's IP for limits and bans. X-Forwarded-Proto is only honored from a trusted proxy, so OPENENV_GUARD_ENFORCE_HTTPS behind a TLS-terminating proxy needs the proxy listed too. That setting covers HTTP requests; handshakes don't go through the HTTPS check.
Tests: 12 in test_guard_security.py; the two websocket refusal tests fail on the previous head. Against a live uvicorn server with my IP blocked, /ws and /mcp both answer the upgrade with 403; unblocked, /ws resets and returns an observation.
Done: #1506. Same branch merged with current main, your lifespan commits included.
It also answers the passive-mode thread you left open on #1361: passive mode now lets Redis outages through too (fastapi-guard's redis_fail_open), at startup and after it, HTTP and WebSocket alike. Active mode is unchanged. Live numbers against a real Redis are in the PR body.
Activity
burtenshaw commentedon Oct 26, 2026
Before merging #1361, should protection cover WebSocket connections and messages too? Please specify the supported threat model and proxy trust defaults.
rennf93 commentedon Oct 26, 2026
Hey @burtenshaw ! Thx for taking the time.
'Yes' on connections, and #1361 now covers them. 'No' on messages, by design.
WebSocket: The new commit runs fastapi-guard's
guard_websocketon every handshake when the guard is on (ban check, IP and country lists, a rate-limit bucket separate from HTTP, and penetration detection on the path, query and headers). Messages on an accepted socket aren't screened: they're actions and observations, the same model content that keeps body scanning opt-in. 2 limits I can think of: there's no per-message rate limit (the concurrent-session cap still applies), and a ban issued mid-session applies from the next handshake, not to the socket that's already open.Threat model. An env server on a public URL (a Space, a container behind ingress) facing unauthenticated clients. In scope: request floods from one IP (per-IP limits, and repeated 429s turn into a temporary ban), scanners probing paths, query strings and headers, and sources the operator wants out (IP/CIDR lists, user agents, countries, cloud providers). Out of scope: authentication (anyone who gets through can still reset and step; a private Space or a token does that job), WebSocket messages, request bodies unless
OPENENV_GUARD_SCAN_BODYis set, and floods spread across many IPs beyond what per-IP limits catch.Proxy trust. No proxy is trusted by default:
X-Forwarded-Foris ignored and the client is the TCP peer, so no client can spoof its way past bans or limits. Behind a proxy, setOPENENV_GUARD_TRUSTED_PROXIES(IPs or CIDRs) andOPENENV_GUARD_TRUSTED_PROXY_DEPTH(default 1); without it every client shares the proxy's IP for limits and bans.X-Forwarded-Protois only honored from a trusted proxy, soOPENENV_GUARD_ENFORCE_HTTPSbehind a TLS-terminating proxy needs the proxy listed too. That setting covers HTTP requests; handshakes don't go through the HTTPS check.Tests: 12 in
test_guard_security.py; the two websocket refusal tests fail on the previous head. Against a live uvicorn server with my IP blocked,/wsand/mcpboth answer the upgrade with 403; unblocked,/wsresets and returns an observation.burtenshaw commentedon Oct 27, 2026
Great. Could you open an up to date version of 1361 with these changes.
rennf93 commentedon Oct 27, 2026
Done: #1506. Same branch merged with current main, your lifespan commits included.
It also answers the passive-mode thread you left open on #1361: passive mode now lets Redis outages through too (fastapi-guard's
redis_fail_open), at startup and after it, HTTP and WebSocket alike. Active mode is unchanged. Live numbers against a real Redis are in the PR body.