Middleware¶
The middleware base classes and every middleware shipped with the framework.
Middleware
¶
Bases: Auditable
Base middleware class. Subclass and override process_request/process_response.
Each middleware carries a name used by per-route exclusion
(exclude_middleware=[...] on a route). The default name is the
concrete class name; override the class attribute, or pass name= when
two instances of the same class must be addressed independently.
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_request
async
¶
Run before the route handler; return a Response to short-circuit.
BaseHTTPMiddleware
¶
Bases: Auditable
Class-based dispatch-shape middleware.
Subclass and override dispatch, or construct with dispatch=fn for a
one-off middleware. The instance is callable as
(request, call_next) -> response, so it composes with the existing
@app.middleware("http") chain.
Usage::
class TimingMW(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
start = time.perf_counter()
response = await call_next(request)
response.headers["X-Elapsed-ms"] = str(
int((time.perf_counter() - start) * 1000)
)
return response
app.add_http_middleware(TimingMW())
# Or, without subclassing:
async def my_dispatch(request, call_next): ...
app.add_http_middleware(BaseHTTPMiddleware(dispatch=my_dispatch))
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
DispatchFunction
module-attribute
¶
CORSMiddleware
¶
Bases: Middleware
Cross-Origin Resource Sharing middleware.
Usage::
app.add_middleware(
CORSMiddleware(
allow_origins=["https://example.com"],
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"],
allow_credentials=True,
)
)
allow_headers defaults to ["*"], and the Fetch standard forbids a
wildcard alongside credentials, so it must be listed explicitly whenever
allow_credentials=True - omitting it raises at construction.
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_request
async
¶
Handle CORS preflight requests and validate origins.
GZipMiddleware
¶
Bases: CompressionMiddleware
GZip compression for responses above a size threshold.
Compression runs in the thread pool executor to avoid blocking the event loop.
Offers gzip and nothing else, so a client asking for brotli or zstd is
served an uncompressed body. Use
CompressionMiddleware to negotiate across
the newer codings.
Usage::
app.add_middleware(GZipMiddleware(minimum_size=1024, compresslevel=6))
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_request
async
¶
Run before the route handler; return a Response to short-circuit.
CompressionMiddleware
¶
Bases: Middleware
Response compression, negotiated over the codings the server can emit.
Offers zstd, brotli and gzip - whichever of their packages are installed -
and picks one per response from Accept-Encoding. The client's q weights
rank the candidates; algorithms order breaks ties, so a deployment states
its own preference for clients that express none.
Compression above min_stream_chunk_offload bytes runs in the thread pool
to avoid holding the event loop.
Usage::
from veloce import CompressionMiddleware
app.add_middleware(CompressionMiddleware(algorithms=("br", "gzip")))
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_request
async
¶
Run before the route handler; return a Response to short-circuit.
ConditionalGetMiddleware
¶
Bases: Middleware
Emit 304 responses for satisfied GET/HEAD preconditions.
With auto_etag (default), a weak ETag is synthesized for a
buffered, non-empty, non-streaming 200 response that lacks one (unless
Cache-Control: no-store is set). Register this AFTER GZipMiddleware
so a synthesized/forwarded ETag reflects the post-compression bytes;
StreamingResponse bodies are intentionally not buffered for synthesis.
Usage::
app.add_middleware(GZipMiddleware())
app.add_middleware(ConditionalGetMiddleware())
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
CSRFMiddleware
¶
Bases: Middleware
Double-submit-cookie CSRF middleware.
Issues a token cookie and requires an unsafe request to echo it back in a header or form field; a request whose two copies disagree is refused.
cookie_name / header_name / form_field rename the slots the token
travels in, and safe_methods overrides which verbs bypass the check.
cookie_secure / cookie_httponly / cookie_samesite set the cookie
attributes - httponly must stay False because client-side JavaScript
has to read the cookie to echo it, while secure defaults to True, so
local HTTP development needs cookie_secure=False. Setting secret
additionally HMAC-signs the token, which proves the value was minted by
this server; the module docstring covers what that does and does not stop.
Usage::
from veloce import Veloce
from veloce.middleware.csrf import CSRFMiddleware
app = Veloce()
app.add_middleware(CSRFMiddleware(secret="a-long-random-string"))
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_request
async
¶
Validate the CSRF token on state-changing requests.
rotate_csrf_token
¶
rotate_csrf_token(request: Request) -> None
Force the active CSRFMiddleware to mint a fresh token on response.
Call this at the end of an authentication handler (login, logout, permission elevation) so the CSRF cookie issued to the pre-authentication session is replaced by a fresh one bound to the new authentication state. Without rotation an attacker who plants a known CSRF cookie on an anonymous victim can submit forged requests after the victim logs in (session-fixation pathway).
Usage::
@app.post("/login")
async def login(request: Request):
user = authenticate(...)
request.session["user_id"] = user.id
rotate_csrf_token(request)
return RedirectResponse("/")
No-op when CSRFMiddleware is not installed.
LoggingMiddleware
¶
Bases: Middleware
Structured request/response access logging.
Usage::
app.add_middleware(LoggingMiddleware())
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_request
async
¶
Record the request start time for duration logging.
RequestIDMiddleware
¶
Bases: Middleware
Assign a unique request ID to each request and echo it in the response.
Usage::
app.add_middleware(RequestIDMiddleware())
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_request
async
¶
Attach a unique request ID to each request.
ProxyFix
¶
Bases: Middleware
Reverse-proxy header trust middleware.
Trusts N hops for each X-Forwarded-* header (right-to-left).
Setting any field to 0 disables it. Negative values raise at
construction.
x_port trusts X-Forwarded-Port: the resolved port fills in the
public port for request.url / redirects when the forwarded Host
carries none, so a proxy on a non-default port (e.g. 8443) is preserved.
An explicit port in the Host / X-Forwarded-Host always wins.
trust_forwarded opts into RFC 7239 Forwarded, which supersedes the
X-Forwarded-* set and is then the sole authority for every directive it
carries. Enable it only where every trusted proxy sets or sanitizes
Forwarded itself: nginx, ALB and most CDNs emit X-Forwarded-* and
leave Forwarded untouched, so a client-supplied header would otherwise
decide the client address, scheme and host - and silence the header the
proxy does control.
Usage::
# Behind two trusted proxies forwarding client IP and scheme.
app.add_middleware(ProxyFix(x_for=2, x_proto=1, x_host=1))
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_response
async
¶
Run after the route handler; may modify the response.
SessionMiddlewareBase
¶
Bases: Middleware
Base class for a session middleware — subclass it to add a backend.
Subclassing supplies two things a backend would otherwise have to
reimplement. It inherits the documented session.permanent rule (a
permanent session takes the longer lifetime), and it becomes the type
Veloce.security_audit recognises, so veloce check warns when the
backend's session cookie is not Secure. A backend that does not
subclass gets neither, and the audit passes it in silence.
A subclass sets max_age and permanent_lifetime, then calls
cookie_lifetime(session) wherever it writes the cookie or the store
entry. Both built-in backends - SessionMiddleware (signed cookie) and
ServerSessionMiddleware (server-side store) - are subclasses, and
adding another requires no edit inside the framework.
Usage::
from veloce import SessionMiddlewareBase
class RedisSessionMiddleware(SessionMiddlewareBase):
def __init__(self, client, max_age=3600, permanent_lifetime=2592000):
self.client = client
self.max_age = max_age
self.permanent_lifetime = permanent_lifetime
async def process_response(self, request, response):
session = request.session
ttl = self.cookie_lifetime(session)
await self.client.setex(session.sid, ttl, session.serialize())
response.set_cookie("session", session.sid, max_age=ttl, secure=True)
return response
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
wire_cookie_name
property
¶
The cookie name actually used on the wire.
cookie_name with the RFC 6265bis __Host- / __Secure- prefix
applied. Anything writing or reading this backend's cookie directly
needs this name and not the configured one - seeded under the bare name
the cookie is simply never found, and the read silently takes the
anonymous path.
process_request
async
¶
Run before the route handler; return a Response to short-circuit.
process_response
async
¶
Run after the route handler; may modify the response.
cookie_lifetime
¶
Return the lifetime this session's cookie and entry should carry.
cookie_is_secure
¶
Whether this backend's session cookie will carry Secure.
Answered from the middleware alone - there is no second source to
reconcile. A subclass that never sets secure reads as not secure, so
the audit reports a cookie it cannot vouch for rather than staying
quiet about it.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report an insecure cookie, and config that no longer configures one.
SessionMiddleware
¶
Bases: SessionMiddlewareBase
Server-side session stored in a signed, timestamped cookie.
Every cookie setting comes from this constructor. An argument left out
takes the library default and does not change again - app.config is not
consulted, so what the signature says is what the cookie carries, and two
session middlewares can carry different cookies.
secret_key is the exception: left out, it is taken from SECRET_KEY
(also settable as app.secret_key) on the first request, because it is the
application's signing key rather than an attribute of this cookie. Without
either, the first request raises.
Set renew_on_access=True for sliding expiry: a session that was only read
during a request has its cookie re-signed with a fresh Max-Age on the way
out, so an active user is not logged out at the fixed max_age. Default is
off - only a modifying write rewrites the cookie.
Set chunked=True to transparently split a signed value larger than
max_cookie_size across numbered cookies (<cookie_name>.0, .1, ...) and
reassemble them on the next request. max_chunks bounds the split so an
oversized session is dropped with a warning rather than exploded into an
unbounded number of cookies. Default is off - the single oversized cookie is
dropped with a warning, unchanged from before.
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
wire_cookie_name
property
¶
The cookie name actually used on the wire.
cookie_name with the RFC 6265bis __Host- / __Secure- prefix
applied. Anything writing or reading this backend's cookie directly
needs this name and not the configured one - seeded under the bare name
the cookie is simply never found, and the read silently takes the
anonymous path.
cookie_lifetime
¶
Return the lifetime this session's cookie and entry should carry.
cookie_is_secure
¶
Whether this backend's session cookie will carry Secure.
Answered from the middleware alone - there is no second source to
reconcile. A subclass that never sets secure reads as not secure, so
the audit reports a cookie it cannot vouch for rather than staying
quiet about it.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report a backend that cannot sign, on top of the shared checks.
A cookie session is signed, so a middleware with neither a
secret_key= argument nor a configured SECRET_KEY cannot serve a
single request - it raises on the first one. That is an error, which
refuses the boot, rather than a warning discovered by traffic.
The middleware answers this and not the audit, because only it knows
whether the constructor supplied a key: reading SECRET_KEY alone
warned about a backend that was already signing correctly.
bind_secret_key
¶
Settle the signing key from config if the constructor was not given one.
A no-op once the key is settled, so it is safe to call from anywhere
that needs a usable signer before a request has run - seeding a session
in a test, or a CLI command that mints one. hint is appended to the
error raised when there is no key, so the caller can say when it needed
it.
encode_cookie
¶
Return the signed cookie value this middleware would send for session.
Minting a valid session cookie outside a request - a test fixture that starts logged in, a tool that hands a user a pre-authenticated link - otherwise means rebuilding the signer with this middleware's exact secret and salt, and any drift in that construction produces cookies the middleware rejects.
decode_cookie
¶
Return the session inside a signed cookie value, or None if invalid.
None covers a bad signature, a tampered payload, and a token older
than this middleware would accept. The request path calls this, so what
it accepts and what a request accepts cannot diverge.
process_request
async
¶
Load the session from the signed cookie into request state.
ServerSessionMiddleware
¶
Bases: SessionMiddlewareBase
Server-side session - the cookie carries only an opaque session id.
The session payload lives in a SessionStore, not in the cookie, so a
session is revocable: empty it in a handler (session.clear()) or
delete it straight from the store (await store.delete(session_id))
and it is gone server-side. A tampered or stale cookie simply fails to
resolve to a stored payload and is treated as a fresh session.
The default store is a process-local InMemorySessionStore; pass a
shared backend (e.g. a Redis-backed SessionStore) for a multi-worker
deployment. The store is a plain object the caller owns - keep a
reference to it to revoke sessions by id.
Set renew_on_access=True for sliding expiry: a session that was only read
during a request has its store TTL refreshed (via SessionStore.touch) and
its cookie re-stamped on the way out - an idle-timeout reset. Default off.
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
wire_cookie_name
property
¶
The cookie name actually used on the wire.
cookie_name with the RFC 6265bis __Host- / __Secure- prefix
applied. Anything writing or reading this backend's cookie directly
needs this name and not the configured one - seeded under the bare name
the cookie is simply never found, and the read silently takes the
anonymous path.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report an insecure cookie, and config that no longer configures one.
cookie_lifetime
¶
Return the lifetime this session's cookie and entry should carry.
cookie_is_secure
¶
Whether this backend's session cookie will carry Secure.
Answered from the middleware alone - there is no second source to
reconcile. A subclass that never sets secure reads as not secure, so
the audit reports a cookie it cannot vouch for rather than staying
quiet about it.
process_request
async
¶
Load the session from the server-side store by cookie id.
CSPMiddleware
¶
Bases: Middleware
Emit Content-Security-Policy with optional per-request nonce.
policy and report_only_policy each accept a str template containing
the literal {nonce} placeholder, or a directive mapping where the
'nonce' source is substituted with a fresh per-request nonce.
Usage::
app.add_middleware(
CSPMiddleware(
policy={"default-src": "'self'", "script-src": ["'self'", "'nonce'"]},
report_only_policy="default-src 'self'",
)
)
Static (no-nonce) policies can stay on SecurityHeadersMiddleware; use this when a per-request nonce or a report-only policy is needed.
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
csp_nonce
¶
csp_nonce(request: Request | None = None) -> str | None
Return the per-request CSP nonce, materializing it on first access.
Templating helpers and handlers embed this on <script>/<style> tags
as nonce="...". Pass the request explicitly, or omit it to read the one
currently being handled. Returns None when CSPMiddleware did not arm a
nonce for this request, or when there is no request in scope.
HTTPSRedirectMiddleware
¶
Bases: Middleware
Redirect HTTP requests to HTTPS.
Resolves the request scheme in this order
- ASGI scope
"scheme"if set to"https"/"wss"(the server already terminated TLS). X-Forwarded-Protoheader (when aProxyFix-style middleware ran upstream this is already the trusted value).- Default
http.
Uses 308 Permanent Redirect (RFC 9110 Sec. 15.4.9) so non-GET methods
preserve their method and body. The earlier 301 form was wrong
for POST/PUT callers - those would silently become GET.
Pass exempt_paths=("/health/", ...) to serve some paths over plain HTTP
(prefix match - use a trailing slash to scope to a segment). By default
/.well-known/acme-challenge/ is exempt (RFC 8555 Sec. 8.3: the HTTP-01
challenge MUST be reachable over plain HTTP for certificate issuance and
renewal); pass exempt_acme_challenge=False to drop that default.
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_response
async
¶
Run after the route handler; may modify the response.
RateLimitMiddleware
¶
Bases: Middleware
Per-client rate limiter with a selectable algorithm and backend.
Two ways to configure it:
- The default
max_requestsperwindow_secondsruns a process-local sliding-log limiter - simple, zero-dependency, intended for a single worker. Counters are NOT shared across workers, souvicorn --workers Nsees roughlyN x max_requestsper window. - Pass a
strategy-FixedWindow,SlidingWindow, orTokenBucket- to choose the algorithm, and abackendto choose where state lives:InMemoryRateLimitBackend(default) orveloce.contrib.redis.RedisRateLimitBackendfor one limit shared across every worker and host.
Give a route its own limit by decorating its handler with rate_limit - the
limit lives on the handler, so there is no route string to mistype::
from veloce import rate_limit
@app.post("/login")
@rate_limit(TokenBucket(rate=5, per=60))
async def login(request): ...
The overrides map is the central alternative for handlers you cannot
decorate: it maps a route's full path template to a strategy. The key is
the template as matched at runtime - the value of request.url_rule - so a
blueprint route includes its url_prefix (/api/login, not /login); an
override key that matches no route raises on the first request. An explicit
overrides entry wins over a rate_limit tag on the same route.
Either way, an overridden route gets its own per-client counter, independent
of the default budget; routes without an override keep the shared default.
Like exclude_middleware, the per-route strategy is resolved against the
route matched at dispatch entry, so a before_request hook that rewrites the
path does not change which limit applies.
Usage::
from veloce import RateLimitMiddleware, TokenBucket
app.add_middleware(
RateLimitMiddleware(
strategy=TokenBucket(rate=1000, per=60),
overrides={"/login": TokenBucket(rate=5, per=60)},
)
)
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
process_request
async
¶
Enforce per-client request rate limits.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report overrides keys that match no registered route.
A key matching nothing means a route the operator believes is throttled
but is not. Under strict_overrides that is an error, so the boot
fails naming the keys rather than the app serving unthrottled; otherwise
it is a warning and the overrides are simply inactive.
Reads the route table, so audit_needs_routes keeps it from running
against an app that was imported but never started - routes registered
during startup are not there yet, and would read as missing.
SecurityHeadersMiddleware
¶
Bases: Middleware
Attach common hardening response headers to every response.
Set by default:
X-Content-Type-Options: nosniff- stop MIME sniffing.X-Frame-Options: DENY- block framing (clickjacking).Referrer-Policy: strict-origin-when-cross-origin.
Off unless configured:
Strict-Transport-Security- passhsts_max_age(seconds). Browsers honour HSTS only over HTTPS, so it is inert in plain-HTTP development, but it is still opt-in because it pins clients to HTTPS for the configured lifetime.Content-Security-Policy- passcontent_security_policy.Permissions-Policy- passpermissions_policy.
A header a handler already set on the response is left untouched - these are defaults, not overrides.
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
process_request
async
¶
Run before the route handler; return a Response to short-circuit.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Note the opt-in headers this instance is not sending.
The three defaults are always safe; Strict-Transport-Security and
Content-Security-Policy are not, so they stay off until asked for.
That leaves a registered instance looking hardened while sending
neither, which is said here rather than left to a scanner to find.
Informational: an app served over plain HTTP, or one whose policy is
set at a proxy, is right to leave them off.
TrustedHostMiddleware
¶
Bases: Middleware
Validates Host header against an allow-list.
Supports literal hostnames, the catch-all *, and subdomain wildcards
of the form *.example.com (matches api.example.com,
a.b.example.com, etc. - never the bare example.com). Matching is
case-insensitive; the port portion of Host: is stripped before
comparison (RFC 9110 Sec. 7.2).
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_response
async
¶
Run after the route handler; may modify the response.
is_host_allowed
¶
Whether host (bare hostname, no port) passes the allow-list.
Public so the WebSocket dispatch path can apply the same check -
a WebSocket handshake never reaches an HTTP middleware's
process_request.
WebSocketOriginMiddleware
¶
Bases: Middleware
Reject cross-site WebSocket handshakes (CSWSH).
A WebSocket handshake is not subject to the Same-Origin Policy and
bypasses CORS entirely, so a page on any origin can open a socket to
your app unless the handshake Origin is checked. Register this
with the origins your own front-end is served from; a handshake whose
Origin is present but unlisted is refused with close code 1008.
Browsers always send Origin on a WebSocket handshake (RFC 6455
Sec. 4.1), so allow_missing=True (the default) still blocks every
browser-driven CSWSH attempt while leaving non-browser clients
(mobile apps, service-to-service) - which legitimately omit Origin
- able to connect. Set allow_missing=False to additionally refuse
handshakes that carry no Origin at all.
Plain HTTP requests pass straight through - Origin enforcement for
HTTP is CORSMiddleware's job.
middleware_name
property
¶
Resolved exclusion name - the instance/class name or class name.
audit
¶
audit(ctx: AuditContext) -> Iterable[Finding]
Report what is wrong with this component's own configuration.
The audit collects these from everything registered, so a check belongs to the thing it is about and an app that registers none never loads the code that checks them. Return nothing when there is nothing to say.
Severity decides what a finding does: an error refuses the boot, a
warning fails veloce check without stopping anything, and info
fails nothing. Set audit_needs_routes when the check reads
ctx.app's routes. Runs at audit and startup time only - never on a
request path.
process_request
async
¶
Run before the route handler; return a Response to short-circuit.
process_response
async
¶
Run after the route handler; may modify the response.
is_websocket_origin_allowed
¶
Whether a handshake carrying origin may proceed.
Public so the WebSocket dispatch path can apply the check - a
handshake never reaches an HTTP middleware's process_request.