Repository navigation
Conversation
Off by default (OPENENV_GUARD_ENABLED=1 to enable): IP block/allow lists, rate limiting with auto-ban, user-agent blocking, and penetration detection, wired into both create_fastapi_app and the gradio web-interface path. Ships as a 'security' extra; in-memory state by default, Redis via OPENENV_GUARD_REDIS_URL.
…loud blocking, and log options Brings the OPENENV_GUARD_* surface to parity: passive/log-only mode, optional security headers, enforce-HTTPS, blocked/allowed countries and cloud-provider blocking, custom log file/format, and IPINFO_TOKEN geo lookups. Country rules require IPINFO_TOKEN (guard-core resolves a geo handler from it).
…enforcement, strict booleans, rate-limit auto-ban knob - web_interface no longer attaches guard twice (create_fastapi_app already covers it) and attach_guard is idempotent via app.state - enforce_https now sets trust_x_forwarded_proto by default so TLS-terminating proxies are read correctly (explicit override via OPENENV_GUARD_TRUST_X_FORWARDED_PROTO) - invalid boolean env values now fail loudly instead of silently disabling security - new OPENENV_GUARD_RATE_LIMIT_AUTO_BAN knob
…security-middleware
- Trusted proxies default to empty: trusting RFC1918 ranges let any LAN peer spoof X-Forwarded-For past the blocklist and rate limits. Behind a reverse proxy, list it in OPENENV_GUARD_TRUSTED_PROXIES. - Penetration-detection body scanning is now opt-in via OPENENV_GUARD_SCAN_BODY: request bodies are model input (observations, prompts, completions), not commands, so signature scans false-positived on ordinary content and rejected legitimate requests with 400. URL, query, and header screening stay on. - Rate-limit auto-ban defaults on; OPENENV_GUARD_RATE_LIMIT_AUTO_BAN=0 keeps 429-only enforcement. _env_bool gained a keyword-only default.
burtenshaw
left a comment
There was a problem hiding this comment.
Independent review: basic HTTP/WebSocket blocklist and rate-limit tests pass using the installed dependency. Pushed simple enable-flag and optional-test fixes. Two WebSocket behavior gaps remain; both reproduced on fastapi-guard 8.0.3 and 8.1.2 with guard-core 4.4.0, using real FastAPI TestClient lifespan and guard implementations. No workflow approvals or merge actions taken.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ce6d931. Configure here.
| getattr(websocket.state, "client_ip", None), | ||
| exc.reason, | ||
| ) | ||
| await self.app(scope, receive, send) |
There was a problem hiding this comment.
Passive mode admits Redis-down sockets
Medium Severity
After Redis has initialized, an outage surfaces as WebSocketException and passive_mode accepts the handshake, so WebSocket sessions skip distributed limits while HTTP still returns 503. A missing handler already maps to 1013 even in passive mode.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit ce6d931. Configure here.
There was a problem hiding this comment.
Independently reproduced on real Redis 7.2.5 + Uvicorn + WebSocket clients, with fastapi-guard/guard-core 8.0.3/4.1.0 and 8.1.2/4.4.0. Passive mode: cold Redis-down startup refuses the upgrade (HTTP 403, ASGI 1013); after successful initialization, stopping Redis allows /ws reset to return an observation. HTTP /health returns 500 in this post-initialization outage, not 503 (503 is the initialization failure path). Active mode refuses the upgrade after the same outage; recovery succeeds.
Leaving this unresolved without tightening passive-mode policy. Upstream documents passive mode as never blocking, and uses a distinct redis_fail_open/fail_secure policy for infrastructure failures; this PR's module documentation simultaneously promises passive acceptance and Redis-down refusal. We need an explicit decision whether OpenEnv's passive mode also bypasses infrastructure failures or only policy violations, then align cold/warm startup, HTTP/WS behavior, documentation, and tests. A blanket close on every 1013 would also change non-Redis detection-error behavior. No policy change was made in this round.
Upstream references: https://github.com/Guard-Core/fastapi-guard/blob/master/guard/.agents/skills/fastapi-guard/SKILL.md and https://rennf93.github.io/fastapi-guard/7.8.2/tutorial/redis-integration/caching/
|
@burtenshaw I think this got auto-closed by #1492: its body says "These tests do not ... resolve #1361 policy gates", GitHub reads "resolve #1361" as a closing keyword, and this closed 2 seconds after #1492 merged. Can you reopen it? I can't from my side. Once it's back:
|


Closes #1360
Implementation for the discussion in #1360.
Summary
Opt-in fastapi-guard middleware wired into both app factories (
create_fastapi_appand the gradio web-interface path), covering HTTP requests and WebSocket handshakes. Default off: withOPENENV_GUARD_ENABLEDunset,attach_guard()is a no-op and env servers run exactly as before. Enabling without the extra installed raises a clear ImportError with the install command, so misconfiguration is loud instead of silent.Type of Change
Alignment Checklist
.claude/docs/PRINCIPLES.mdand this PR aligns with our principles.claude/docs/INVARIANTS.mdand no invariants are violated (no API signature or wire-type change, env-var only config, nothing secret logged)/pre-submit-pr(orbash .claude/hooks/lint.shand tests) and addressed all issuesRFC Status
What you get when enabled
pip install "openenv[security]"plusOPENENV_GUARD_ENABLED=1:OPENENV_GUARD_RATE_LIMIT/_RATE_LIMIT_WINDOW(defaults 100 req / 60s),_AUTO_BAN_THRESHOLD/_AUTO_BAN_DURATION(defaults 10 / 300s); repeated 429s feed the ban (OPENENV_GUARD_RATE_LIMIT_AUTO_BAN=0keeps 429-only)OPENENV_GUARD_BLOCKED_IPS/_ALLOWED_IPSOPENENV_GUARD_BLOCKED_USER_AGENTSOPENENV_GUARD_SCAN_BODY)/ws,/mcp,/harnessand/ws/uigo through fastapi-guard'sguard_websocket: ban check, IP and country lists, a rate-limit bucket separate from HTTP, and penetration detection on the handshake. A refused handshake is closed beforeaccept(), so the client gets HTTP 403 on the upgrade and no socket. Messages on an accepted socket are not screened, and a ban issued mid-session applies from the next handshake. In passive mode a handshake that would be refused is logged and accepted. With Redis configured, the guard initializes at startup (fastapi-guard'smake_lifespanwraps the app lifespan), and while Redis is unreachable handshakes are closed with 1013, the WebSocket side of the HTTP 503OPENENV_GUARD_PASSIVE_MODE), optional security headers (OPENENV_GUARD_SECURITY_HEADERS), enforce-HTTPS (OPENENV_GUARD_ENFORCE_HTTPS), custom log file/format, and blocked/allowed countries plus cloud-provider blocking (countries requireIPINFO_TOKEN)OPENENV_GUARD_TRUSTED_PROXIES/_TRUSTED_PROXY_DEPTH. No proxy is trusted by default, soX-Forwarded-Foris ignored and the client is the TCP peer; behind a proxy, list it or every client shares the proxy's IP.X-Forwarded-Protois only honored from a trusted proxy, so enforce-HTTPS behind a TLS-terminating proxy needs it listed tooOPENENV_GUARD_REDIS_URL, in-memory otherwise. I explicitly disabled the library's localhost default; an opt-in feature shouldn't require a running Redis./health,/schema,/metadataand the docs paths are excluded from detection checks so orchestrators can keep probing, but IP lists and rate limiting still apply on them. That's fastapi-guard'senforced_on_excluded_pathsdesign and I think it's the right call: excluded shouldn't mean free pass.Test Plan
uv run --extra security pytest tests/core/test_guard_security.py: 19 passed on fastapi-guard 8.0.3 / guard-core 4.3.2 and on 8.1.2 / 4.4.0 (default-off no-op, enable-flag parsing, middleware wiring, blocked IP 403, IP lists on excluded paths, rate-limit 429, zero-trust defaults, env parsing, the websocket handshake refused with 1008 on/wsand/mcp, rate-limited, untouched when the guard is off, passive mode logging and accepting, startup init, and 1013 for a WebSocket-first request while Redis is unreachable). The websocket, passive and Redis tests fail on the code before them. Rate-limit state in the library is process-wide, so each test drives its own TEST-NET client IP to stay hermetic.OPENENV_GUARD_BLOCKED_IPS,/wsand/mcpanswer the upgrade with 403; with the guard on and the IP allowed, or the guard off,/wsaccepts and a reset returns an observation.pytest tests/with its ignores and markers) on 28985ec; the commits after it change onlysecurity.pyand its tests: 3620 passed, 155 skipped, 3 failed, 1 collection error, none in the guard code. The collection error issmolagentsmissing from my venv (CI installs it). The failures are a harness process test hittingPermissionError: [Errno 1] Operation not permittedatharness/process.py:188in my sandbox (same with this PR stashed),test_harbor_capture_server.py:104andtest_prepare_hf_deployment.py, neither of which imports the guard.src/andtests/.Claude Code Review
N/A