DEV Community

Cover image for The one they fixed
Jonathan Santilli
Jonathan Santilli

Posted on

The one they fixed

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"}
Enter fullscreen mode Exit fullscreen mode
/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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)