Skip to content

[v3-2-test] Require starlette>=1.0.1 for Host header parsing fix (#67326)#67460

Merged
vatsrahul1001 merged 1 commit into
v3-2-testfrom
backport-67326-v3-2-test
May 25, 2026
Merged

[v3-2-test] Require starlette>=1.0.1 for Host header parsing fix (#67326)#67460
vatsrahul1001 merged 1 commit into
v3-2-testfrom
backport-67326-v3-2-test

Conversation

@vatsrahul1001

@vatsrahul1001 vatsrahul1001 commented May 25, 2026

Copy link
Copy Markdown
Contributor

Manual backport of #67326 — auto-backport failed (https://github.com/apache/airflow/actions/runs/26391687063).

Why auto-backport failed

Conflicts in pyproject.toml and uv.lock around the [tool.uv.exclude-newer-package] cooldown overrides block:

Resolution

Resolved by dropping the stale uv = "12 hours" override (it's past its REMOVE BY date anyway) and keeping the new starlette = "6 hours" override that this PR adds. The drop effectively also brings #67383's cleanup into v3-2-test, so a separate backport of #67383 is not needed.

What this PR ships

  • starlette>=1.0.1 floor bump — Host-header parsing fix from Ignore malformed Host header when constructing request.url Kludex/starlette#3279.
  • cadwyn>=6.1.1 floor bump — required because cadwyn 6.0.4 is incompatible with starlette 1.0.x (per cadwyn's own release notes: "Fix compatibility with starlette==1.0.0"). Without this, test_access_api_contract fails on LowestDeps with TypeError: unhashable type: 'dict' in jinja2 via cadwyn's swagger dashboard.
  • starlette = "6 hours" uv cooldown override so the floor bump can resolve before the global 4-day cooldown.
  • scripts/ci/prek/upgrade_important_versions.py enhancements (auto-honor & retire per-package overrides).
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

* Require starlette>=1.0.1 to fix Host-header path divergence

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.

* Auto-honour and retire per-package exclude-newer overrides in upgrade 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.

(cherry picked from commit 518eadf)
@vatsrahul1001 vatsrahul1001 force-pushed the backport-67326-v3-2-test branch from 6f33b31 to 85f0aa5 Compare May 25, 2026 16:04
@vatsrahul1001 vatsrahul1001 merged commit 22b2c7d into v3-2-test May 25, 2026
404 of 405 checks passed
@vatsrahul1001 vatsrahul1001 deleted the backport-67326-v3-2-test branch May 25, 2026 19:48
vatsrahul1001 added a commit that referenced this pull request May 25, 2026
* Require starlette>=1.0.1 to fix Host-header path divergence

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.

* Auto-honour and retire per-package exclude-newer overrides in upgrade 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.

(cherry picked from commit 518eadf)

Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
vatsrahul1001 added a commit that referenced this pull request May 25, 2026
* Require starlette>=1.0.1 to fix Host-header path divergence

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.

* Auto-honour and retire per-package exclude-newer overrides in upgrade 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.

(cherry picked from commit 518eadf)

Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:dev-tools type:bug-fix Changelog: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants