Fix cards for Python and FastAPI

A01:2025 Broken Access Control

  • Missing object-level authorization (IDOR and BOLA)CWE-862 · CWE-639 · CWE-863

    Build the signed-in user in a dependency (current_user) from the authenticated session and the users table, never from request data, and put the same scope in every query's WHERE clause: same tenant, and the caller's own rows unless they are a billing admin.

  • Open redirectCWE-601

    Refuse backslashes and control characters, then split next with urlsplit: accept a path that starts with exactly one slash, or an https URL whose hostname is in an exact allowlist, with no port and no @.

  • Path traversalCWE-22 · CWE-23 · CWE-36 · CWE-59

    Resolve both paths and compare whole components before building the FileResponse: base = Path(base_dir).resolve(), target = (base / name).resolve(), and serve target only if target.is_relative_to(base). resolve() collapses ../ and follows symlinks, and the check also catches absolute names, which replace base in the / operator.

  • Server-side request forgery (SSRF)CWE-918

    Attach the policy to the connection, not the URL string or its Pydantic type.

A04:2025 Cryptographic Failures

  • Weak password hashingCWE-916 · CWE-759 · CWE-327

    Use argon2-cffi's PasswordHasher with explicit parameters at or above OWASP's minimum, PasswordHasher(time_cost=2, memory_cost=19456, parallelism=1), and store ph.hash(password) from a plain def path operation.

A05:2025 Injection

  • Code injectionCWE-94 · CWE-95

    Never evaluate user input as code. Parse the expression with ast.parse and walk the tree, allowing only numeric literals, the supplied variables and arithmetic operators; refuse everything else.

  • SQL injectionCWE-89

    Pass values as bound parameters: text("... WHERE c.name = :customer") with session.execute(sql, {"customer": customer}), or build the query with select() and column comparisons.

A06:2025 Insecure Design

  • Unbounded resource consumptionCWE-770 · CWE-400

    Bound every resource the client controls. Cap the request body while you read it -- count the stream and return 413 as soon as it exceeds the limit, never trusting Content-Length -- and bound pagination with Query(limit, ge=1, le=100) so an out-of-range page size is a 422.

  • Unrestricted file uploadCWE-434 · CWE-646 · CWE-73 · CWE-770

    Decide the type from the bytes and never let the client pick the name.

A07:2025 Authentication Failures

  • JWT validation in APIs (bearer tokens)CWE-347 · CWE-287

    Put verification in one dependency that every protected route declares: HTTPBearer() reads the token, then jwt.decode(token, key, algorithms=["RS256"], issuer=..., audience=..., leeway=30, options={"require": ["exp", "iss", "aud", "sub"]}) with the key from configuration, and any PyJWTError becomes 401.

  • Missing authentication for a critical functionCWE-306

    Put an authentication dependency on every protected path operation.

A08:2025 Software or Data Integrity Failures

  • Insecure deserializationCWE-502

    Do not rebuild objects from client data. Parse the token as JSON with a Pydantic model (Profile.model_validate_json(raw)), which keeps only declared fields of declared types; pickle.loads runs code chosen by the bytes.

  • Mass assignment (over-posting)CWE-915

    Give each write route its own Pydantic body model that declares only the fields that caller may change (here displayName and bio), never the model that describes the whole stored user, and copy model_dump(exclude_none=True) onto the record. extra="forbid" turns any other key into a 422.

Not covered in FastAPI yet

These weaknesses have fix cards in other stacks but not in this one.

Supported versions

FastAPI has no long-term support lines: fixes ship in new releases, and its documentation tells you to pin the version you test. FastAPI accepts any Starlette from 0.46.0, so pin Starlette yourself. FastAPI and Starlette support policy.

FastAPI and Starlette
VersionLatestStatusReleasedEnd of support
FastAPI 0.1430.143.0SupportedLatest release; older releases get no fixes.8 October 2026No date set
Starlette 1.x1.7.0Supported1.0.0 was released on 2026-03-22.22 March 2026No date set
CPython
VersionLatestStatusReleasedEnd of support
3.153.15.0Supported9 October 2026October 2031
3.143.14.8Supported7 October 2025October 2030
3.133.13.16Security fixes only7 October 2024October 2029
3.123.12.15Security fixes only2 October 2023October 2028
3.113.11.17Security fixes only24 October 2022October 2027

FastAPI 0.143 still accepts Python 3.10, but Python 3.10 reached its end of life on 2026-10-01, so run 3.11 or later. CPython release schedule

How the fix cards choose versions

Minimum secure versions

The lowest release in each supported line that fixes every published advisory listed here. Anything older is missing a security fix.

PackageAt leastWhy
starlette1.3.1CVE-2026-54283 (form size limits ignored for URL-encoded bodies), CVE-2026-48818 (SSRF and NTLM credential theft through UNC paths in StaticFiles on Windows), CVE-2026-48710 and others. Advisory
python-multipart0.0.31CVE-2026-53539 (quadratic-time parsing, fixed in 0.0.30), CVE-2026-53540 and earlier denial-of-service fixes; FastAPI does not require a fixed version. Advisory
CPython3.15.0, 3.14.8, 3.13.16, 3.12.15 or 3.11.17Six CVEs fixed on 30 September and 1 October 2026, such as an ssl use-after-free (CVE-2026-19445), a tarfile filter bypass (CVE-2026-19672) and unbounded zipfile decompression (CVE-2026-15310); 3.11 to 3.13 also fix two more tarfile CVEs, and the fix for one of them, CVE-2026-87910, was in no 3.14 release on 11 October 2026. Advisory

Middleware and security order

  1. The last middleware added runs first: Each app.add_middleware() wraps the app, so the last one added is the outermost. Add them in reverse of the order requests should meet them.
  2. Host and scheme: TrustedHostMiddleware with your host names outermost, then HTTPSRedirectMiddleware if the app is reached directly; behind a proxy, set Uvicorn's --forwarded-allow-ips to the proxy's address.
  3. CORS: CORSMiddleware with a list of origins.
  4. Authentication and authorization as dependencies: Declare the current-user dependency on the router or each route, and check object ownership in the route or its query.
  5. Exception handlers: Return generic errors for unexpected exceptions; keep debug=False.

Secure defaults and settings

SettingValueWhy
docs_url, redoc_url, openapi_url/docs, /redoc, /openapi.jsonSet them to None in production unless the API is meant to be public.
Pydantic extra fieldsIgnoredUse separate input models; extra='forbid' makes probing visible with a 422.
debugFalseKeep it off; debug mode returns tracebacks.
Validation errors422 with details, including the inputFine for APIs, but do not echo secrets submitted in bodies.

Escape hatches to search for

Framework APIs that switch a protection off. Each one is sometimes needed; search for them in review and make each use justify itself.

APIRiskInstead
CORSMiddleware(allow_origins=["*"], allow_credentials=True)Starlette then echoes the request's Origin and allows credentials, so any site can read authenticated responses.A list of origins.
Returning ORM objects without response_modelEvery column reaches the client, including password hashes and internal flags.A response model with only the public fields.
HTMLResponse with an f-stringNo escaping.Jinja2Templates, which autoescapes HTML templates.
Uvicorn --forwarded-allow-ips='*'Any client can set its own address and scheme.The proxy's address.
SQLAlchemy text() with an f-stringSQL injection.Bound parameters or the ORM.
pickle.loads() or yaml.load() on request dataCode execution while deserializing.Pydantic models over JSON, or yaml.safe_load().

Checklist, mapped to ASVS 5.0.0

  • v5.0.0-8.2.2 Every route that takes an object ID checks the current user owns it.
  • v5.0.0-15.3.1 Every route declares a response_model with only the fields callers may see.
  • v5.0.0-15.3.3 Request bodies use input models that hold only the editable fields.
  • v5.0.0-3.4.2 CORS allows a list of origins, never * with credentials.
  • v5.0.0-13.4.5 The interactive docs and OpenAPI schema are off in production unless intended.
  • v5.0.0-1.2.4 Queries bind parameters.
  • v5.0.0-9.1.1 Bearer tokens are verified (signature, algorithm, issuer, audience, expiry) in a dependency.
  • v5.0.0-15.2.1 Starlette and python-multipart are pinned at or above the minimum secure versions above.

References

  1. FastAPI: middleware
  2. FastAPI: CORS
  3. FastAPI: advanced middleware (TrustedHost, HTTPSRedirect)
  4. FastAPI: about FastAPI versions
  5. Starlette CORSMiddleware source
  6. Uvicorn: settings (proxy headers)

All fix cards