Skip to content

feat: add PureASGISecurityMiddleware (pure-ASGI variant, no BaseHTTPMiddleware) - #156

Merged
rennf93 merged 3 commits into
masterfrom
feat/pure-asgi-middleware
Oct 10, 2026
Merged

rennf93 merged 3 commits into
masterfrom
feat/pure-asgi-middleware

Conversation

@rennf93

@rennf93 rennf93 commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Closes #157

What

Adds PureASGISecurityMiddleware: a pure-ASGI variant of SecurityMiddleware that runs the identical screening pipeline (init, CORS preflight, exclusions, IP extraction, bans, rate limiting, UA blocking, penetration detection, behavioral usage rules) without BaseHTTPMiddleware.

Why

BaseHTTPMiddleware wraps the downstream app in an anyio task group whose cancel scope cancels in-flight downstream awaits on client disconnect. Framework projects ban it for exactly that reason (open-webui bans it in main.py with a written rationale), which locks them out of guard entirely. This variant removes the blocker: no task group, downstream awaits are never cancelled by guard, responses stream through unbuffered.

How

  • guard/asgi.py: PureASGISecurityMiddleware reuses the classic middleware as its engine (constructor builds pipeline, handlers, rate limiter, event bus, CORS handler - no duplication) and replicates the dispatch flow on raw ASGI: body reads go through a caching receive that replays to the downstream app, security + CORS headers are injected directly on http.response.start, WebSocket/lifespan scopes pass through untouched
  • Behavioral return rules (return patterns) and custom_response_modifier need a materialized Response and remain BaseHTTPMiddleware-path features (documented in the module docstring)
  • Exported from guard alongside SecurityMiddleware; classic middleware unchanged
  • WebSocket-level checks stay on make_guard_websocket as today

Validation

  • New tests/test_asgi_pure.py (8 tests): blocked IP 403, rate limit 200/200/429, passive mode, IP lists enforced on excluded paths, rate limiting also enforced on excluded paths (by design), security headers injected on the ASGI response start, body read by guard and still received downstream, non-http scope passthrough
  • Existing middleware suite: no new failures (4 failures on clean master locally are pre-existing env-related, same count with and without this branch)
  • ruff + mypy clean; behavior validated in a live venv (blocked 403, 200/200/429, headers present)

…iddleware)

Runs the same screening pipeline as SecurityMiddleware (init, CORS
preflight, exclusions, IP extraction, bans, rate limiting, UA blocking,
penetration detection, behavioral usage rules) without wrapping the
downstream app in an anyio task group, so client disconnects can never
cancel in-flight downstream calls. Security and CORS headers are
injected on http.response.start; responses stream through unbuffered.
Decorator-driven response rules (return patterns) and
custom_response_modifier remain BaseHTTPMiddleware-path features.
… update

Covers chunked-body caching and replay, disconnect passthrough, CORS on
both success and blocked responses, guard-redis 503 path, decorator
bypass via the buffered call path, and behavioral usage rules.
@rennf93 rennf93 self-assigned this Oct 10, 2026
@rennf93
rennf93 merged commit 4d315ed into master Oct 10, 2026
12 checks passed
@rennf93
rennf93 deleted the feat/pure-asgi-middleware branch October 13, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: integrations Touches public re-exports or framework wiring (guard/__init__.py) tests Test suite changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: pure-ASGI security middleware (no BaseHTTPMiddleware)

1 participant