# Risk map — fastapi/fastapi

> Canonical page: https://codevetter.com/unpack/fastapi/risk-map

# Risk map — fastapi/fastapi

Scanned commit 50113da16fec53b66b80d75e80a89296de4fa5a5 (2026-09-01). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

As a library, the dangerous surfaces are file IO (the new frontend static server), auth scheme edge cases, and the streaming/exit-stack machinery where resource teardown is subtle. The frontend server is the newest and widest-blast-radius addition: it reads arbitrary files from a user-supplied directory on every unmatched request. The included-router rework is the deepest structural change and introduces a per-request handler rebuild plus a ContextVar hand-off that the code itself flags as temporary. Two caches — `_endpoint_context_cache` and the `lru_cache`d callable classifiers — are keyed by object identity, which is correct only as long as the keyed objects outlive the cache. Repo-level supply-chain risk is actively managed: all Actions are SHA-pinned, `permissions: {}` is the default, `persist-credentials: false` on every checkout, and zizmor lints the workflows.
- `_FrontendStaticFiles` serves files from a user-supplied directory on every request that no API route matched; it mitigates traversal by normalizing the path (`os.path.normpath(os.path.join(*path.split("/")))`), delegating to Starlette's `lookup_path`, and passing `follow_symlink=False`, but the blast radius of a bug here is arbitrary file disclosure. — [fastapi/routing.py#L1899-1940](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L1899-L1940) [fastapi/routing.py#L1974-1985](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L1974-L1985)
- `_lookup_path` maps a `PermissionError` on the filesystem to an HTTP 401 response, which conflates a server-side filesystem permission problem with a client authentication failure and can leak the existence of unreadable paths. — [fastapi/routing.py#L1974-1985](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L1974-L1985)
- `APIRoute.handle` rebuilds the entire request handler — `request_response(self.get_route_handler())`, which re-runs `get_request_handler` — on every single request whenever an `_EffectiveRouteContext` is present, i.e. for all routes reached through `include_router`. — [fastapi/routing.py#L1261-1284](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L1261-L1284) [fastapi/routing.py#L1225-1250](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L1225-L1250)
- The effective-route context is passed from `handle()` to `get_route_handler()` through a module-level ContextVar rather than an argument, and the code carries a `TODO: Replace or deprecate this no-scope hook` — any subclass overriding `get_route_handler()` interacts with this implicitly. — [fastapi/routing.py#L1225-1233](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L1225-L1233)
- `_endpoint_context_cache` is an unbounded module-level `dict[int, EndpointContext]` keyed by `id(func)`; entries are never evicted, so a process that creates endpoint callables dynamically grows the dict without bound and could in principle collide on a recycled `id`. — [fastapi/routing.py#L275-300](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L275-L300)
- The callable classifiers `_is_gen_callable`/`_is_async_gen_callable`/`_is_coroutine_callable` use `lru_cache(maxsize=4096)` keyed by a `_CallIdentity` whose `__hash__` is `id(self.call)`, so the cache holds a strong reference to every classified callable and identity reuse after GC is the correctness assumption. — [fastapi/dependencies/models.py#L55-65](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/dependencies/models.py#L55-L65) [fastapi/dependencies/models.py#L130-160](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/dependencies/models.py#L130-L160)
- `HTTPBasic.__call__` base64-decodes the Authorization header and splits on `:`; it returns the raw username and password with no timing-safe comparison anywhere in the framework — constant-time credential checking is left entirely to the application. — [fastapi/security/http.py#L202-220](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/security/http.py#L202-L220)
- Every security scheme has an `auto_error=False` mode that returns `None` instead of raising 401; a dependency written against that mode that forgets to check for `None` silently authenticates everyone. — [fastapi/security/oauth2.py#L423-431](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/security/oauth2.py#L423-L431) [fastapi/security/http.py#L94-104](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/security/http.py#L94-L104) [fastapi/security/http.py#L303-317](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/security/http.py#L303-L317)
- The default Swagger UI and ReDoc pages load JavaScript and CSS from `cdn.jsdelivr.net` (and Google Fonts for ReDoc) with no SRI hashes, so `/docs` and `/redoc` execute third-party CDN code by default in every FastAPI app. — [fastapi/openapi/docs.py#L79-92](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/openapi/docs.py#L79-L92) [fastapi/openapi/docs.py#L236](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/openapi/docs.py#L236) [fastapi/openapi/docs.py#L275](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/openapi/docs.py#L275)
- Swagger UI parameter injection is defended by `_html_safe_json`, which escapes `<`, `>` and `&` before interpolating user-supplied `swagger_ui_parameters` and `init_oauth` into a `<script>` block — but `oauth2_redirect_url` is interpolated into the same script with no escaping. — [fastapi/openapi/docs.py#L9-19](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/openapi/docs.py#L9-L19) [fastapi/openapi/docs.py#L178-181](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/openapi/docs.py#L178-L181)
- `_extract_endpoint_context` calls `inspect.getsourcefile` and `getsourcelines`, embedding absolute server file paths into `ValidationException.__str__`; these appear in logs and in `ResponseValidationError` tracebacks but not in the 422 JSON body, which only serializes `exc.errors()`. — [fastapi/routing.py#L278-299](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L278-L299) [fastapi/exceptions.py#L192-210](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/exceptions.py#L192-L210) [fastapi/exception_handlers.py#L19-26](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/exception_handlers.py#L19-L26)
- `request_response` detects a swallowed exception in a `yield` dependency only after the fact, by checking a `response_awaited` flag and raising a `FastAPIError` with a docs link — a heuristic guard rather than a structural fix. — [fastapi/routing.py#L137-158](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L137-L158)
- `scripts/translate.py` sends documentation content to OpenAI through `pydantic_ai.Agent("openai-chat:gpt-5.5")`, and `.github/workflows/translate.yml` supplies `OPENAI_API_KEY` from repo secrets with two `zizmor: ignore[secrets-outside-env]` suppressions. — [scripts/translate.py#L140](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/scripts/translate.py#L140) [.github/workflows/translate.yml#L112-118](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/.github/workflows/translate.yml#L112-L118)
- Two workflows use elevated triggers with explicit zizmor suppressions: `guard-dependencies.yml` runs on `pull_request_target` (mitigated by never checking out PR code and only reading `context.payload`), and `deploy-docs.yml`/`smokeshow.yml` run on `workflow_run`. — [.github/workflows/guard-dependencies.yml#L1-14](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/.github/workflows/guard-dependencies.yml#L1-L14) [.github/workflows/deploy-docs.yml#L1-9](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/.github/workflows/deploy-docs.yml#L1-L9) [.github/workflows/smokeshow.yml#L1-9](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/.github/workflows/smokeshow.yml#L1-L9)
- Legacy/deprecated code still present: `openapi_prefix` (warns and aliases to `root_path`), `on_event`, `_DefaultLifespan` re-vendored after Starlette deleted it, and the deprecated `ORJSONResponse`/`UJSONResponse` pair kept only for compatibility. — [fastapi/applications.py#L926-935](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/applications.py#L926-L935) [fastapi/applications.py#L4662-4682](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/applications.py#L4662-L4682) [fastapi/routing.py#L250-275](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/routing.py#L250-L275) [fastapi/responses.py#L42-101](https://github.com/fastapi/fastapi/blob/50113da16fec53b66b80d75e80a89296de4fa5a5/fastapi/responses.py#L42-L101)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
