Skip to content

Require starlette>=1.0.1 for Host header parsing fix#67326

Merged
potiuk merged 2 commits into
apache:mainfrom
potiuk:bump-starlette-1.0.1-host-header-fix
May 25, 2026
Merged

Require starlette>=1.0.1 for Host header parsing fix#67326
potiuk merged 2 commits into
apache:mainfrom
potiuk:bump-starlette-1.0.1-host-header-fix

Conversation

@potiuk
Copy link
Copy Markdown
Member

@potiuk potiuk commented May 22, 2026

Summary

  • Bumps the starlette floor in airflow-core/pyproject.toml from
    >=0.45.0 to >=1.0.1 to pick up the Host-header parsing fix in
    Kludex/starlette#3279.
  • Adds a matching [tool.uv.exclude-newer-package] override
    (starlette = "6 hours") so the bump can resolve before 1.0.1
    ages past the project's global 4-day cooldown.
  • Teaches scripts/ci/prek/upgrade_important_versions.py to honour
    manual cooldown overrides when checking PyPI and to retire those
    overrides automatically once the global window catches up, so each
    workaround line — and its # REMOVE BY … marker — clean themselves
    out without anyone watching the calendar.

Why this matters for Airflow

The upstream PR closes a case where request.url.path could diverge
from the ASGI scope["path"] when the Host header contains
characters that are invalid per RFC 9110 §7.2 (/, ?, #, @,
\, space). Airflow has authorisation paths that compare against
request.url.path while the downstream app serves the file at
scope["path"]:

  • airflow-core/src/airflow/utils/serve_logs/log_server.py
    (JWTAuthStaticFiles)
  • providers/edge3/src/airflow/providers/edge3/worker_api/auth.py
    (jwt_token_authorization_rest)

Bumping the floor closes the underlying divergence. Defence-in-depth
follow-ups (comparing scope["path"] directly instead of
request.url.path) can come in a separate PR.

Test plan

  • uv run --project scripts pytest scripts/tests/ci/prek/test_upgrade_important_versions.py — 19 passed (5 existing + 14 new covering _parse_duration_hours, _parse_manual_overrides, _remove_override_entry, and the per-package cooldown branch).
  • prek run --files … on every touched file — all hooks pass (ruff, ruff-format, mypy-scripts, license headers, codespell, uv.lock sync, …).
  • uv lock resolves starlette==1.0.1 once the per-package override is in place; verified by diffing uv.lock.

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 4.7)

Generated-by: Claude Code (Opus 4.7) following the guidelines


Drafted-by: Claude Code (Opus 4.7); reviewed by @potiuk before posting

@boring-cyborg boring-cyborg Bot added area:dev-tools backport-to-v3-2-test Mark PR with this label to backport to v3-2-test branch labels May 22, 2026
@potiuk potiuk added this to the Airflow 3.2.2 milestone May 22, 2026
@potiuk potiuk requested a review from vatsrahul1001 May 22, 2026 13:13
@github-actions
Copy link
Copy Markdown
Contributor

uv.lock on main just moved via #67383 ("Cleanup older/outddated uv cooldown exception"), commit 62845dd and this PR currently conflicts.

Quickest fix:

git fetch upstream main && git rebase upstream/main
rm uv.lock && uv lock
git add uv.lock && git rebase --continue
git push --force-with-lease

Automated nudge — ignore if you're not ready to rebase. This comment is updated in place on future uv.lock bumps.

@vatsrahul1001 vatsrahul1001 force-pushed the bump-starlette-1.0.1-host-header-fix branch from 6b5f873 to c0dd9ee Compare May 25, 2026 04:52
@vatsrahul1001
Copy link
Copy Markdown
Contributor

@potiuk @jscheffl I had to rebase as there was conflict in pyproject.toml after #67383 , Also noticed CI was breaking

CI fix — bumped httpx floor:
The LowestDeps jobs were all failing on test_access_api_contract with TypeError: unhashable type: 'dict' inside starlette's _mapping[key] via httpx TestClient. Starlette 1.0.1's PyPI metadata requires
httpx<0.29.0,>=0.27.0 for TestClient, but airflow-core/pyproject.toml was at httpx>=0.25.0. Bumped to >=0.27.0 to match.

@potiuk
Copy link
Copy Markdown
Member Author

potiuk commented May 25, 2026

Nice! thanks ! - I was just looking :)

potiuk added 2 commits May 25, 2026 11:33
Starlette 1.0.1 carries a Host-header parsing fix
(Kludex/starlette#3279): when the `Host`
header contains characters that are invalid per RFC 9110 §7.2
(`/`, `?`, `#`, `@`, `\`, spaces, ...), the URL string Starlette
builds before calling `urlsplit` would push parts of `scope["path"]`
into the netloc / query / fragment, leaving `request.url.path`
disagreeing with the ASGI `scope["path"]` that downstream apps and
`StaticFiles` actually serve.

Airflow has two places that authorise off `request.url.path` and
dispatch off `scope["path"]`:

- `airflow-core/src/airflow/utils/serve_logs/log_server.py` —
  `JWTAuthStaticFiles.validate_jwt_token` compares
  `request.url.path` against the JWT's `filename` claim; the
  `StaticFiles` superclass then serves the file at `scope["path"]`.
  A malformed `Host` header makes those two disagree, letting a
  holder of any valid log-read token read any other task log on
  the same worker.

- `providers/edge3/src/airflow/providers/edge3/worker_api/auth.py` —
  `jwt_token_authorization_rest` derives the called "method" from
  `request.url.path` while FastAPI routes by `scope["path"]`. Same
  shape of bypass on the Edge3 worker control plane.

Bumping the floor to 1.0.1 closes both. A matching
`[tool.uv.exclude-newer-package]` override is added so the security
floor can be resolved before 1.0.1 ages past the project's global
4-day cooldown — the next commit teaches
`upgrade_important_versions.py` to retire that override automatically
once the cooldown catches up.
… script

`upgrade_important_versions.py` enforced its own 4-day PyPI cooldown
(`COOLDOWN_DAYS = 4`), which mirrored the root pyproject.toml's global
`exclude-newer = "4 days"`. When a per-package override was added under
`[tool.uv.exclude-newer-package]` (e.g. `uv = "12 hours"`) to let a
freshly-published release through the global window, the script kept
applying its broader cooldown and would pick a stale version that
disagreed with what `uv lock` would resolve against pyproject.toml.

This change makes the script:

1. Parse manual override blocks (the lines after the
   "# End of automatically generated …" sentinels under
   `[tool.uv.exclude-newer-package]` and
   `[tool.uv.pip.exclude-newer-package]`) and use any duration-shaped
   override as the per-package cooldown when checking PyPI.
2. Sweep up overrides whose target package is already older than the
   global 4-day window — the entry, plus its `# REMOVE BY …` markers,
   are removed from pyproject.toml so the workaround retires itself
   without anyone having to remember the calendar date in the comment.

The "Manual overrides" header and broader context comments are left
in place on purpose — the diff makes them obviously orphaned for a
reviewer to prune in the same PR, but the script doesn't try to guess
which surrounding lines belonged to which entry.
@vatsrahul1001 vatsrahul1001 force-pushed the bump-starlette-1.0.1-host-header-fix branch from c0dd9ee to 6991fe8 Compare May 25, 2026 06:06
@vatsrahul1001
Copy link
Copy Markdown
Contributor

Pervious understating of bumping httpx was not right. CI was still failling, Below seems correct

Actual CI fix: cadwyn>=6.1.1
The test_access_api_contract LowestDeps failure was caused by cadwyn 6.0.4 (airflow-core's floor) being incompatible with starlette 1.0.x. cadwyn 6.1.1's release notes literally say "Fix compatibility with
starlette==1.0.0" — LowestDeps resolves to the floor and hits the bug. The unhashable-dict TypeError surfaces in cadwyn's swagger dashboard via starlette's templating → jinja2 cache lookup.

Bumped cadwyn>=6.0.4cadwyn>=6.1.1 to make airflow-core's floor honest with the new starlette requirement.

cc: @potiuk

@potiuk potiuk merged commit 518eadf into apache:main May 25, 2026
292 checks passed
@potiuk potiuk deleted the bump-starlette-1.0.1-host-header-fix branch May 25, 2026 08:40
@github-actions
Copy link
Copy Markdown
Contributor

Backport failed to create: v3-2-test. View the failure log Run details

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
v3-2-test Commit Link

You can attempt to backport this manually by running:

cherry_picker 518eadf v3-2-test

This should apply the commit to the v3-2-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

If you don't have cherry-picker installed, see the installation guide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:dev-tools backport-to-v3-2-test Mark PR with this label to backport to v3-2-test branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants