Repository navigation
Conversation
Attack screening with automatic bans, per-client budgets, upload limits and IP, country, cloud and user-agent rules on the HTTP routes and the /asr and /v1/listen WebSocket handshakes. Off by default; with it off the guard package is never imported. Uvicorn trusts exactly the proxies the guard trusts, so X-Forwarded-For cannot be used to change identity.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add an optional fastapi-guard security layer
Relates to discussion #463
Summary
WhisperLiveKit is often run where other people can reach it, and every WebSocket session it accepts costs a decoder. This adds an opt-in application-layer security layer built on fastapi-guard, off by default:
/asrand/v1/listen, with automatic bans for clients that keep probing. A banned or refused client is turned away beforeaccept(), so noAudioProcessor, FFmpeg process or decoder is started for it.POST /v1/audio/transcriptionsa stricter request budget plus a size limit and a content-type filter.Enable with
--security-guardorWLK_SECURITY_GUARD=1, after installing thesecurityextra. Settings areWhisperLiveKitConfigfields with matching CLI flags (--security-guard-*), documented in the newdocs/security.md. The guard runs inside the process and complements a reverse proxy, WAF or firewall in front of the server; where there is none, it is the protection. TLS stays at the proxy.On #455: the guard limits how fast one client can open sessions and shuts out abusers, but it does not cap concurrent sessions or close idle ones. Those two stay server fixes.
User impact
guardis never imported, no middleware is added andcreate_appbuilds the same routes. The only structural change is that the two WebSocket routes now live on a second router (ws_router), so the guard can run its handshake dependency on exactly those routes.enable_redis=False), no agent, no response headers and no CORS handling from the guard (CORSMiddlewarestays outermost, so a 403 or 429 from the guard still carries CORS headers), request bodies are never scanned, and the free-textcontextquery parameter is excluded from screening. Thessrfdetection category is disabled because, with guard-core 4.4.0, aRefererpointing at localhost or a LAN address is flagged and the browser UI sends exactly that.GET /plus the browser's/favicon.ico(the page and its assets are inline), and the UI reconnects with a 1 s to 30 s backoff, so 60 leaves roughly ten times the headroom a single user needs. The reasoning is indocs/security.md.X-Forwarded-Forbefore any middleware runs. With the guard on,main()passes--forwarded-allow-ipsto both Uvicorn and the guard, and turns Uvicorn's proxy headers off when none is configured (even withFORWARDED_ALLOW_IPSin the environment), so a client cannot rotate its identity to dodge a budget or a ban. The manualuvicorn whisperlivekit.basic_server:applaunch is documented.security = ["fastapi-guard>=8.1.1"]. It is also in thetestextra so CI (pip install -e ".[test]") anduv sync --extra testrun the new tests.uv.lockgainsfastapi-guard,guard-core,maxminddb,redis,async-timeoutand nothing else changes. Both packages are MIT.Validation
Hardware and tools: Apple Silicon Mac, Python 3.12.15, uv 0.11.1, ruff 0.16, fastapi 0.143.3, starlette 1.8.0, uvicorn 0.54.0, fastapi-guard 8.1.1, guard-core 4.4.0.
The CI gate on
mainbefore the change and on the branch after it:ruff check .uv lock --checkpytest -q tests/ --ignore=tests/test_pipeline.py --ignore=tests/test_asr_coalescing_pipeline.pypytest -q tests/test_asr_coalescing_pipeline.py(Whisper tiny, real audio)python -m buildandtwine checktests/test_security_guard.py(25 tests) goes throughcreate_app()with real HTTP requests and real WebSocket handshakes (StarletteTestClient, plus one real Uvicorn socket with awebsocketsclient). It covers: off adds nothing and never importsguard(clean subprocess); the missing-package error; middleware order and CORS headers on 429 and 403; handshakes from banned, blocklisted, emergency-blocked and attack-payload clients are refused before accept and the test countsAudioProcessorand FFmpeg starts (zero); the session budget; HTTP attack query returning 400 and repeated attacks banning; the upload budget, size limit and content-type filter; more than five 401s banning the client on HTTP and at the handshake; spoofedX-Forwarded-Forignored from an untrusted peer and honored from a trusted proxy;uvicorn.Config(...).load()withFORWARDED_ALLOW_IPS=*and rotatingX-Forwarded-Forstill tripping the budget; localhost and LANRefererreturning 200 and a<script>Refererstill refused; free text incontext; allow, block and user-agent rules; emergency and passive modes; normal streaming through/asr; and the warning logged when--forwarded-allow-ips '*'is combined with the guard, since*would let any client choose the address that is rate limited and banned. Each test uses its own client address. I ran the file five times and the whole tree three more times in random order (pytest-randomly, not added to the project) with no failure.I also ran the real server on 127.0.0.1 (
--no-transcription --no-vac --pcm-input, so no model is needed) with the guard on and off and probed it with curl-style HTTP requests and awebsocketsclient: with the guard on, an attack query and a scriptRefererget 400, WebSocket handshakes with an attack query or a blocked user-agent get 403 with no session, the HTTP and session budgets trip while a normal/asrsession still works, localhost and LANRefererget 200, and a client with more than five 401s is banned and then refused on HTTP and at the handshake. With it off, all of those are accepted as before.One note on the gate: one of three runs of the real-audio Whisper test on the branch exited with status 139 after reporting
4 passed(interpreter teardown); two reruns exited 0. That test does not touch this code.Disclosure: I maintain fastapi-guard and guard-core, the engine underneath it.
I used AI assistance to prepare this and reviewed and verified everything myself.
Checklist
ruff check ..Project background: Guard Core's first library is fastapi-guard, public since August 2024 (first PyPI release, 0.1.0, on 2024-08-26). It has 825 GitHub stars, 65 releases and about 62,000 PyPI downloads in the last month (pypistats.org). The framework-agnostic engines and the adapters for other frameworks and languages came later and run the same detection rules, checked in CI against shared conformance fixtures.