Reported 26 July. Fixed by the maintainer on 29 July. The report was closed on 11 August as not accepted.
| Affected | FastAPI 0.140.0 |
| Status | Fixed in 0.141.1 |
| Reported | 2026-07-26 |
| Fixed | 2026-07-29 |
| Report closed | 2026-08-11 |
Fifth of five write-ups on findings reported privately to FastAPI. This is the only one that no longer reproduces, and the only one where the code now does what the report asked for.
I'm going to keep this post to dates and diffs.
What broke
FastAPI's frontend() helper serves a static build directory. You can protect it with dependencies — that's an advertised feature, added in PR #15908, whose title is:
Support dependencies in
app.frontend(), e.g. for automatic cookie authentication for the frontend
The documentation added by that PR says frontend responses "run inside the normal FastAPI application" and that dependencies "can be useful for protecting a frontend with cookie authentication or similar."
In 0.140.0, those dependencies ran — and every response effect they produced was discarded. Headers, Set-Cookie, status changes, background tasks.
Same application, same dependency, two routes:
def require_session(request: Request, response: Response) -> None:
if request.cookies.get("session") != "alice":
raise HTTPException(status_code=401)
response.set_cookie("session", "alice-rotated", httponly=True)
response.headers["Cache-Control"] = "private, no-store"
router = APIRouter(dependencies=[Depends(require_session)])
router.frontend("/admin", directory=DIST)
@router.get("/api-secret")
async def api_secret(): return {"secret": "x"}
/api-secret → 200
Set-Cookie : 'session=alice-rotated; HttpOnly; Path=/; SameSite=lax'
Cache-Control: 'private, no-store'
/admin/ → 200 ← the frontend route
Set-Cookie : None
Cache-Control: None
/admin/ unauthenticated → 401 ← the dependency ran, and rejected
So authentication worked and everything else the dependency did was dropped — including the cookie rotation that the feature was advertised for.
Why it happened
The frontend route group ran solve_dependencies, checked whether validation had failed, and yielded. It never read the response object or the background tasks that call returns.
Meanwhile the ordinary API route path applied both — at four separate points in the current source, covering SSE, JSONL, raw streams and normal responses. One newer code path missed a step every other path performed.
The test suite shows the gap was never in view. tests/test_frontend.py carries more than sixty tests, over ten of them specifically about dependencies — checking that they run, reject, order correctly, honour overrides, and return 422 on validation errors. Every single dependency in those tests only reads a cookie and raises to reject. Not one sets a cookie, injects a Response, or registers a background task. There is no assertion on set-cookie anywhere in the file.
A feature advertised for cookie authentication had no test in which a dependency sets a cookie.
The fix
PR #16105 — "🐛 Fix support for background tasks and headers from dependencies in app.frontend()" — authored by tiangolo, labelled bug, merged 2026-07-29T17:04:36Z, shipped in 0.141.1.
The current source at routing.py L2201-2205:
) as solved_result:
response = await route.app.get_response_for_scope(scope)
if response.background is None:
response.background = solved_result.background_tasks
response.headers.raw.extend(solved_result.response.headers.raw)
await response(scope, receive, send)
That is what the report asked for, in the function the report identified.
The timeline
| Date | Event |
|---|---|
| 2026-06-20 |
frontend() added — PR #15800
|
| 2026-07-01 | Dependency support added — PR #15908 |
| 2026-07-24 | 0.140.0 released |
| 2026-07-26 | Reported privately as GHSA-c9m3-693h-3rqv, severity medium |
| 2026-07-29 |
PR #16105 merged by tiangolo, labelled bug
|
| 2026-07-29 | Shipped in 0.141.1 |
| 2026-08-11 | Report closed. submission.accepted: false, published_at: null
|
Three days from report to fix. Sixteen days from report to the report being closed as not accepted.
I don't know whether the report caused the fix. The PR doesn't reference it, and I wasn't told either way. What I can say is that the two describe the same defect in the same function, three days apart.
What I take from it
Two things, and I'd rather state them plainly than imply them.
The behaviour was real. Whatever label applies, a fix landed. That's worth recording because the same set of reports was characterised to me as false positives, and a fix is difficult to reconcile with that characterisation.
Fixing it as a bug rather than an advisory is a defensible choice. FastAPI has published two security advisories in roughly six years, and one of those is a dependency passthrough rather than a flaw in its own code. Against that baseline, "fix it quietly as a bug" is not an evasion — it's the project's normal operating mode. The bar for an advisory is set very high, deliberately.
Those two statements sit together comfortably. The finding was valid and declining to publish an advisory was consistent with how the project has always worked. What I'd have wanted, and didn't get, was the first of those acknowledged on the thread.
What you can do today
Nothing — upgrade to 0.141.1 or later and this is handled.
If you're on 0.140.x and using frontend() with dependencies, be aware that any header, cookie, status change or background task your dependency sets is silently dropped on frontend routes. Authentication itself still works; only the response side is lost.
Verified by reading FastAPI's source at tag 0.141.1 on 2026-08-12, and by running the reproduction against 0.140.0. Code links are pinned to that tag rather than master, so the line numbers stay valid.
Top comments (0)