Skip to content

Opt-in request security for env servers (rate limiting, IP lists, payload screening) #1360

Open

Description

@rennf93

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.

Activity

  1. burtenshaw commented on Oct 26, 2026

    @burtenshaw
    Collaborator

    Before merging #1361, should protection cover WebSocket connections and messages too? Please specify the supported threat model and proxy trust defaults.

  2. rennf93 commented on Oct 26, 2026

    @rennf93
    Author

    Hey @burtenshaw ! Thx for taking the time.

    'Yes' on connections, and #1361 now covers them. 'No' on messages, by design.

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

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

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

  3. burtenshaw commented on Oct 27, 2026

    @burtenshaw
    Collaborator

    Great. Could you open an up to date version of 1361 with these changes.

  4. rennf93 commented on Oct 27, 2026

    @rennf93
    Author

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions