Feature request: response callbacks for serve_files() — on_response and on_fallback_document_response
Summary
Add two optional async callbacks to serve_files() that allow callers to inspect and mutate
the response before it is sent to the client:
on_fallback_document_response — called whenever the fallback document file is served,
whether via the fallback mechanism (unknown path) or as an explicit direct request
(e.g. GET /index.html)
on_response — called for every response produced by the static file handler
Motivation
Applications that serve a Single-Page Application via serve_files(..., fallback_document="index.html")
often need to inject dynamic, per-request HTTP response headers when the SPA shell is served.
The most important example is Content-Security-Policy: frame-ancestors, but the pattern applies to
any header whose value depends on request context (path, query string, authenticated identity, etc.).
Concrete security use case
In an iframe-embedding workflow, venezia.js creates an iframe whose src is always the server
root with query parameters:
https://venezia-host/?site=my-blog&topic=example.com/post&origin=...
The server must respond with a frame-ancestors directive that is specific to the site identified
in the query string — allowing only the domains registered for that site to embed the widget.
Without a response callback, the only option is a middleware that wraps the handler and
post-processes the response. This approach has two gaps:
-
Path whitelist gap — the middleware must explicitly list which URL paths receive the CSP
header. Any unlisted path (e.g. /bypass?site=my-blog) still serves index.html via the
fallback mechanism without the header, allowing the page to be embedded from any origin.
-
Direct file request gap — a request for /index.html is a direct file hit; the fallback
mechanism is never invoked. A middleware that only fires on "fallback" responses misses this
case entirely, creating an additional bypass vector.
on_fallback_document_response closes both gaps: it fires whenever the fallback document file
is about to be sent — regardless of whether the path matched it directly or the fallback kicked
in — so coverage is structural and complete.
Proposed API
from collections.abc import Awaitable, Callable
from blacksheep import Request, Response
async def on_index_response(request: Request, response: Response) -> None:
site_slug = request.query.get("site")
frame_ancestors = await get_frame_ancestors(site_slug) # dynamic, per-site
response.add_header(b"content-security-policy", frame_ancestors)
response.add_header(b"x-content-type-options", b"nosniff")
response.add_header(b"referrer-policy", b"strict-origin")
app.serve_files(
Path("app/static"),
fallback_document="index.html",
allow_anonymous=True,
on_fallback_document_response=on_index_response, # direct request OR fallback
# on_response=on_every_response, # fires for every served file (broader)
)
Signature
ResponseCallback = Callable[[Request, Response], Awaitable[None]]
def serve_files(
source_folder: Path,
*,
fallback_document: str | None = None,
allow_anonymous: bool = False,
on_fallback_document_response: ResponseCallback | None = None,
on_response: ResponseCallback | None = None,
# ... existing parameters unchanged
) -> None: ...
Both callbacks are:
- async — they may perform I/O (e.g. a cached DB lookup for allowed hosts)
- mutating — they receive the
Response object and modify it in place; no return value needed
- optional — existing behaviour is completely unchanged when neither is provided
on_fallback_document_response fires whenever the file named by fallback_document is served —
both when a path directly names it (e.g. GET /index.html) and when the fallback kicks in for an
unrecognised path (e.g. GET /some-spa-route). This is the security-critical callback.
on_response fires for every file the handler serves, including assets (JS, CSS, images).
When both are provided, on_response fires first, then on_fallback_document_response.
Why not a middleware?
A BlackSheep middleware can already wrap any handler, so technically this is achievable today.
However, a middleware must duplicate the fallback-detection logic and either whitelist URL paths
or detect when the file handler served index.html specifically — both of which are fragile.
The two bypass vectors described above (arbitrary path + direct /index.html request) make
the middleware approach unsuitable for security-sensitive header injection.
The callback approach moves that knowledge inside the framework where it belongs, and makes
correct behaviour the default.
Feature request: response callbacks for
serve_files()—on_responseandon_fallback_document_responseSummary
Add two optional async callbacks to
serve_files()that allow callers to inspect and mutatethe response before it is sent to the client:
on_fallback_document_response— called whenever the fallback document file is served,whether via the fallback mechanism (unknown path) or as an explicit direct request
(e.g.
GET /index.html)on_response— called for every response produced by the static file handlerMotivation
Applications that serve a Single-Page Application via
serve_files(..., fallback_document="index.html")often need to inject dynamic, per-request HTTP response headers when the SPA shell is served.
The most important example is
Content-Security-Policy: frame-ancestors, but the pattern applies toany header whose value depends on request context (path, query string, authenticated identity, etc.).
Concrete security use case
In an iframe-embedding workflow,
venezia.jscreates an iframe whosesrcis always the serverroot with query parameters:
The server must respond with a
frame-ancestorsdirective that is specific to thesiteidentifiedin the query string — allowing only the domains registered for that site to embed the widget.
Without a response callback, the only option is a middleware that wraps the handler and
post-processes the response. This approach has two gaps:
Path whitelist gap — the middleware must explicitly list which URL paths receive the CSP
header. Any unlisted path (e.g.
/bypass?site=my-blog) still servesindex.htmlvia thefallback mechanism without the header, allowing the page to be embedded from any origin.
Direct file request gap — a request for
/index.htmlis a direct file hit; the fallbackmechanism is never invoked. A middleware that only fires on "fallback" responses misses this
case entirely, creating an additional bypass vector.
on_fallback_document_responsecloses both gaps: it fires whenever the fallback document fileis about to be sent — regardless of whether the path matched it directly or the fallback kicked
in — so coverage is structural and complete.
Proposed API
Signature
Both callbacks are:
Responseobject and modify it in place; no return value neededon_fallback_document_responsefires whenever the file named byfallback_documentis served —both when a path directly names it (e.g.
GET /index.html) and when the fallback kicks in for anunrecognised path (e.g.
GET /some-spa-route). This is the security-critical callback.on_responsefires for every file the handler serves, including assets (JS, CSS, images).When both are provided,
on_responsefires first, thenon_fallback_document_response.Why not a middleware?
A BlackSheep middleware can already wrap any handler, so technically this is achievable today.
However, a middleware must duplicate the fallback-detection logic and either whitelist URL paths
or detect when the file handler served
index.htmlspecifically — both of which are fragile.The two bypass vectors described above (arbitrary path + direct
/index.htmlrequest) makethe middleware approach unsuitable for security-sensitive header injection.
The callback approach moves that knowledge inside the framework where it belongs, and makes
correct behaviour the default.