Refresh the inbound mail service's pinned dependencies
Python · Python · intermediate · modification
Our pins had not moved since the late-2024 compile, and PyPI's advisory data now lists 19 GitHub advisories against them (python-multipart 7, Starlette 7, anyio 2, h11, idna, certifi). This recompiles with the usual 14-day cooldown and puts floors in `requirements.in` for the ones that touch the webhook. The bump is large: Starlette 0.41 to 1.7 crosses 1.0, which removed `on_startup`/`on_shutdown`, so `app.py` moves the support-API client into a `lifespan` context manager (same base URL, token header and timeout, closed when the app shuts down). h11 goes to 0.16, the fix for GHSA-vqfr-h8mv-ghfj (it parses every request uvicorn serves us, so it gets a floor too), with httpcore 1.0.9, the first httpcore that accepts it. sniffio drops out, since neither anyio 4.15 nor httpx 0.28 depends on it any more; typing-extensions comes in through anyio and Starlette. certifi is the one exception to the cooldown: 2026.10.19 is four days old, but GHSA-6q48-6c7w-9gw2 (critical, published 2026-10-20) is about compromised intermediates under a root in the bundle our support-API client trusts, and 2026.10.19 is the fixed release. The exception is scoped to certifi and dated 2026-10-21, so it takes the fix and nothing newer. The suite passes on the new pins. `TestClient` now warns that using it with `httpx` is deprecated in favour of `httpx2`; I'll do that switch in the dev requirements as a separate PR.
A small Starlette service on the public internet: the mail provider posts each inbound support email to `POST /inbound` as a form, `inbound/webhook.py` (unchanged) reads it with `request.form(max_files=0, max_fields=40)`, checks the provider's HMAC signature and timestamp, and creates a ticket through `request.app.state.http`, the one outbound client (TLS to the support API's ASCII hostname, httpx's default verification against certifi's bundle). `GET /healthz` answers 204. The image is `python:3.12-slim` on Linux, built fresh by CI from `requirements.txt` on every merge and run with `uvicorn inbound.app:app` (plain `uvicorn`, no httptools, so h11 parses every request). Dev tools are in `requirements-dev.txt` (pytest only, compiled the same way, no package shared with the runtime set), unchanged here. The 14-day cooldown and its per-package exception rule are the team's written dependency policy.
Requirements
- Every pin in `requirements.txt` moves to a release that has no published advisory against it on the day of the compile (2026-10-24). For the advisories that touch this service (Starlette's form parsing limits, python-multipart's parser, h11's request parsing in uvicorn, certifi's CA bundle), `requirements.in` sets a floor at the fixed version, so a later recompile cannot resolve back below it.
- Resolution keeps the team's 14-day cooldown: `uv pip compile` runs with `--exclude-newer` set to 14 days before the compile day. A package may skip the cooldown only through its own `--exclude-newer-package` exception, for a published advisory whose fix is newer than the cooldown, and the advisory is named next to that package in `requirements.in`.
- `requirements.txt` stays the unedited output of `uv pip compile --generate-hashes`: every pin carries the sha256 of each of its release files on PyPI, and the image installs it with `pip install --no-deps --require-hashes -r requirements.txt`.
- The service behaves as before: the same two routes, one pooled support-API client per process, opened before the first request and closed at shutdown, and `inbound/webhook.py` unchanged. Code changes only where a new version removed an API the service uses.
Files touched
- requirements.in
- requirements.txt
- inbound/app.py
--- requirements.in
# Runtime dependencies of the inbound mail service.
# Recompile requirements.txt after editing (see the command in its header).
-starlette
+starlette>=1.3.1
uvicorn
-python-multipart
+python-multipart>=0.0.31
httpx
+# Transitive (via uvicorn), floor for GHSA-vqfr-h8mv-ghfj (malformed chunked bodies, request smuggling).
+h11>=0.16.0
+# Transitive (via httpx), floor for GHSA-6q48-6c7w-9gw2 (compromised emSign intermediates).
+certifi>=2026.10.19
--- requirements.txt
# This file was autogenerated by uv via the following command:
-# uv pip compile requirements.in --generate-hashes --python-version 3.12 --exclude-newer 2024-10-18T00:00:00Z -o requirements.txt
-anyio==4.6.2.post1 \
- --hash=sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c \
- --hash=sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d
+# uv pip compile requirements.in --generate-hashes --python-version 3.12 --exclude-newer 2026-10-10T00:00:00Z --exclude-newer-package certifi=2026-10-21T00:00:00Z -o requirements.txt
+anyio==4.15.1 \
+ --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \
+ --hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94
# via
# httpx
# starlette
-certifi==2024.8.30 \
- --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 \
- --hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9