Skip to content

fix: harden auth middleware against Starlette BadHost (CVE-2026-48710)#2375

Merged
yeldarby merged 3 commits into
mainfrom
fix/badhost-starlette-cve-2026-48710
May 27, 2026
Merged

fix: harden auth middleware against Starlette BadHost (CVE-2026-48710)#2375
yeldarby merged 3 commits into
mainfrom
fix/badhost-starlette-cve-2026-48710

Conversation

@yeldarby
Copy link
Copy Markdown
Contributor

Summary

Patches CVE-2026-48710 ("BadHost"), a Starlette < 1.0.1 vulnerability where request.url.path is derived from the unvalidated Host header. A crafted Host (e.g. Host: x/docs?) makes request.url.path read as an allowlisted route while ASGI routes the request to an authenticated handler — bypassing the API-key check.

Both API-key auth middlewares in inference/core/interfaces/http/http_api.py gate their public-path allowlist on request.url.path, so both were affected:

Defense in depth:

  • Bump deps: requirements/_requirements.txt:8-9fastapi>=0.116.0,<0.120 plus an explicit starlette>=1.0.1 floor (belt-and-suspenders in case a transitive dep narrows FastAPI's resolution).
  • Swap path source: read from request.scope["path"] (the raw ASGI path, untouched by Host) inside both middlewares. Allowlist contents and startswith logic preserved exactly — only the source of the path string changed.

Logging/telemetry uses of request.url.path (lines 479, 501, 580, 795, 1120, 1155) are informational, not authorization decisions, and are intentionally left as-is per the remediation scope.

Test plan

Added test_serverless_auth_middleware_rejects_host_header_path_injection which sends six Host-injection variants (testserver/docs?, testserver?/docs, testserver/healthz?, testserver/_next/x, testserver/static/x, testserver#/docs) to a protected endpoint and asserts each returns 401, not 200.

  • New Host-injection test passes against the local environment (Starlette 0.37.2, a vulnerable version) — confirming the scope["path"] defense holds independent of the Starlette version.
  • Same test was verified to fail when the code change is reverted, proving it catches the bypass.
  • All 11 tests in tests/inference/unit_tests/core/interfaces/http/test_http_api.py pass. The 5 pre-existing failures in test_run_uvicorn_script.py (test-pollution when run together with test_http_api.py) are present on main and unrelated to this change.

Follow-ups (deliberately out of scope)

  • No TrustedHostMiddleware — needs per-service decision once legitimate Host variance is enumerated across GCP Serverless and Dedicated Deployment.
  • Logging/telemetry call sites left unchanged.
  • No changes to allowlist contents or middleware structure.

References: badhost.org, CVE-2026-48710, OSTIF disclosure.

🤖 Generated with Claude Code

The serverless and dedicated-deployment auth middlewares in http_api.py
gated their public-path allowlist on request.url.path, which vulnerable
Starlette (< 1.0.1) derives from the unvalidated Host header. A request
with a crafted Host (e.g. `Host: x/docs?`) could make request.url.path
read as an allowlisted path while ASGI routed to an authenticated
handler — bypassing API-key auth.

Defense in depth:
  - Bump fastapi to a release line that ships patched Starlette and add
    an explicit `starlette>=1.0.1` floor.
  - Read the path from `request.scope["path"]` (the raw ASGI path,
    untouched by Host header) inside both auth middlewares.

Logging/telemetry uses of request.url.path are left as-is; they are
informational and not authorization decisions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5101e201fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread requirements/_requirements.txt Outdated
@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 27, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​starlette@​1.1.0100100100100100
Updatedpypi/​fastapi@​0.115.14 ⏵ 0.136.3100 +1100100100100

View full report

FastAPI < 0.133 caps its starlette dependency below 1.0 (e.g. 0.119.x
requires `starlette<0.49.0,>=0.40.0`), so `fastapi<0.120` together with
the `starlette>=1.0.1` security floor was an unsatisfiable resolver
problem and broke CI installs.

FastAPI 0.133.0 dropped the starlette upper bound (`starlette>=0.40.0`,
no cap), letting Starlette 1.0.1+ resolve cleanly. Verified locally:
unit tests pass on both fastapi==0.133.0 + starlette==1.1.0 (the new
floor) and fastapi==0.135.4 + starlette==1.1.0. on_event is still
deprecated-but-functional in this range.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…textVar and auth span

Addresses reviewer items #1-#3.

Test: adds test_dedicated_deployment_auth_middleware_rejects_host_header_path_injection
(plus a _build_dedicated_deployment_interface helper) so the check_authorization
middleware has symmetric coverage with check_authorization_serverless. Verified
under vulnerable Starlette 0.37.2: reverting just the dedicated middleware to
request.url.path makes this test fail on the very first injection variant
("Host-injection bypass for header 'testserver/docs?': expected 401, got 200").

Code: two more request.url.path → request.scope["path"] swaps in the same
spirit as the auth middlewares:
  - set_request_path_context middleware: the current_request_path ContextVar
    flows into ModelManagerBase._model_request_paths and is reported back in
    model-info responses (base.py:644). Reviewer flagged this as the
    "leaks into model_load_info / request_model_ids" case.
  - check_authorization_serverless OTel span: the span records the auth
    decision itself ("serverless.authorization.check"), so its http.target
    attribute must not be Host-forgeable.

The remaining informational request.url.path call sites
(_log_serverless_authorization_denial L479, _log_serverless_request_received
L501, the logging at L1120 / L1155) are deliberately left for a follow-up:
they're outside the auth middleware and the original remediation scope
explicitly excluded "logging usages". Worth a separate sweep that updates the
helpers' signatures consistently rather than wedging the change here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yeldarby
Copy link
Copy Markdown
Contributor Author

yeldarby commented May 27, 2026

Pushed an update based on internal code review:

# Item Resolution
1 Dedicated-deployment middleware test Fixed. Added test_dedicated_deployment_auth_middleware_rejects_host_header_path_injection + _build_dedicated_deployment_interface helper. Verified under vulnerable Starlette 0.37.2: reverting just the dedicated middleware back to request.url.path makes the new test fail on the first variant ('testserver/docs?': expected 401, got 200).
2 current_request_path.set(request.url.path) Fixed. Swapped to request.scope["path"]. Traced the ContextVar → _model_request_paths (base.py:217-221) → request_paths field on the model-info response (base.py:644). Currently informational, but cheap to harden and prevents future surprises.
3 http.target span attribute Fixed. Swapped to scope_path (already in scope at L795). The span describes the auth decision itself, so the log-forging concern there is the strongest.
4 Log helpers (_log_serverless_authorization_denial, _log_serverless_request_received) Deferred. The original remediation writeup explicitly scoped these out (Logging uses... are informational only — leave them alone). I'd rather do them as a focused follow-up that updates the helpers' signatures consistently than wedge it in here. Filed as TODO.
5 safe_request_path(request) DRY helper Skipped. Out of scope per writeup's "don't refactor" instruction; happy to add it in the same follow-up that touches the log helpers.
6 Comment on serverless-vs-dedicated allowlist divergence Skipped. Out of scope.
7 FastAPI bump size Acknowledged. Verified locally that the unit tests pass on both fastapi==0.133.0 and fastapi==0.135.4 paired with Starlette 1.1.0, and that on_event is still deprecated-but-functional. The scope-path swap alone is sufficient to close the CVE (proven by the regression test on Starlette 0.37.2), so if the dep bump introduces a regression, the requirements line can be reverted without losing the security fix.

Re: log-forging coverage in the deferred follow-up — I'll open a tracking issue for items 4-6 once this lands.

@yeldarby yeldarby merged commit f76051a into main May 27, 2026
48 checks passed
@yeldarby yeldarby deleted the fix/badhost-starlette-cve-2026-48710 branch May 27, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants