security: pin patched Starlette (>=1.0.1) for CVE-2026-48710 BadHost (#35118)
Starlette < 1.0.1 is affected by CVE-2026-48710 ("BadHost", CWE-444).
The HTTP Host header was not validated before being used to rebuild
`request.url`, so a malformed Host could make `request.url.path` desync
from the raw ASGI path the router actually dispatched. Middleware and
endpoints that apply path-based authorization off `request.url` (rather
than `scope["path"]`) can therefore be bypassed.
Hermes pulls Starlette transitively, never directly:
- [web] -> fastapi==0.133.1 (starlette>=0.40.0, no upper bound)
- [mcp] -> mcp==1.26.0 + sse-starlette (starlette>=0.27 / >=0.49.1)
- [computer-use] -> mcp==1.26.0
- [dev] -> mcp==1.26.0
A fresh resolve landed starlette 0.52.1 — vulnerable. With no upper
bound on the transitive specs, pip/uv could resolve any pre-1.0.1
release on a fresh install.
Fix: pin starlette==1.0.1 directly in every extra that exposes a
Starlette-backed server surface, regenerate uv.lock (only starlette
moves: 0.52.1 -> 1.0.1, hash-verified), and mirror the pin in the
lazy-install map (tools/lazy_deps.py `tool.dashboard`) so `hermes`
on-demand dashboard installs can't re-resolve a vulnerable version.
1.0.1 is the advisory's named fix floor and the oldest patched release
(more bake time than 1.1.0/1.2.0, which are days old); it satisfies
every carrier constraint and our requires-python>=3.11.
Scope note: this is a dependency-level fix complementing the
application-layer Host-header validator added in #34162
(`hermes_cli/web_server.py` `_is_accepted_host`). Defense in depth at
both the framework and app layers.
Guards: two invariant tests in tests/test_packaging_metadata.py assert
every server-surface extra pins starlette and that pyproject + uv.lock
both resolve >= the 1.0.1 CVE floor — a dropped pin or stale lock fails
in CI instead of shipping the bypass.
Closes #35067
This commit is contained in:
17
uv.lock
generated
17
uv.lock
generated
@ -1640,6 +1640,7 @@ all = [
|
||||
{ name = "ruff" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "simple-term-menu" },
|
||||
{ name = "starlette" },
|
||||
{ name = "ty" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
{ name = "youtube-transcript-api" },
|
||||
@ -1658,6 +1659,7 @@ cli = [
|
||||
]
|
||||
computer-use = [
|
||||
{ name = "mcp" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
daytona = [
|
||||
{ name = "daytona" },
|
||||
@ -1670,6 +1672,7 @@ dev = [
|
||||
{ name = "pytest-timeout" },
|
||||
{ name = "ruff" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "starlette" },
|
||||
{ name = "ty" },
|
||||
]
|
||||
dingtalk = [
|
||||
@ -1716,6 +1719,7 @@ matrix = [
|
||||
]
|
||||
mcp = [
|
||||
{ name = "mcp" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
messaging = [
|
||||
{ name = "aiohttp" },
|
||||
@ -1755,6 +1759,7 @@ termux = [
|
||||
{ name = "python-telegram-bot", extra = ["webhooks"] },
|
||||
{ name = "pywinpty", marker = "sys_platform == 'win32'" },
|
||||
{ name = "simple-term-menu" },
|
||||
{ name = "starlette" },
|
||||
]
|
||||
termux-all = [
|
||||
{ name = "agent-client-protocol" },
|
||||
@ -1769,6 +1774,7 @@ termux-all = [
|
||||
{ name = "python-telegram-bot", extra = ["webhooks"] },
|
||||
{ name = "pywinpty", marker = "sys_platform == 'win32'" },
|
||||
{ name = "simple-term-menu" },
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
tts-premium = [
|
||||
@ -1781,6 +1787,7 @@ voice = [
|
||||
]
|
||||
web = [
|
||||
{ name = "fastapi" },
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
wecom = [
|
||||
@ -1886,6 +1893,10 @@ requires-dist = [
|
||||
{ name = "slack-sdk", marker = "extra == 'messaging'", specifier = "==3.40.1" },
|
||||
{ name = "slack-sdk", marker = "extra == 'slack'", specifier = "==3.40.1" },
|
||||
{ name = "sounddevice", marker = "extra == 'voice'", specifier = "==0.5.5" },
|
||||
{ name = "starlette", marker = "extra == 'computer-use'", specifier = "==1.0.1" },
|
||||
{ name = "starlette", marker = "extra == 'dev'", specifier = "==1.0.1" },
|
||||
{ name = "starlette", marker = "extra == 'mcp'", specifier = "==1.0.1" },
|
||||
{ name = "starlette", marker = "extra == 'web'", specifier = "==1.0.1" },
|
||||
{ name = "tenacity", specifier = "==9.1.4" },
|
||||
{ name = "ty", marker = "extra == 'dev'", specifier = "==0.0.21" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'", specifier = "==2025.3" },
|
||||
@ -4084,15 +4095,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.52.1"
|
||||
version = "1.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/a3/84e821cc54b4ab50ae6dbc6ac3800a651b65ec35f045cc73785380654057/starlette-1.0.1.tar.gz", hash = "sha256:512399c5f1de7fac99c88572212ded9ddeddef2fb32afa82d724000e88b38f4f", size = 2659596, upload-time = "2026-05-21T21:58:58.433Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/e1/b2df4bc09a1e51ff664c1e17018a4274b42e5e9352e4a478ea540512dc88/starlette-1.0.1-py3-none-any.whl", hash = "sha256:7c0e69b2ee1c848bd54669d908500117a3ee13de603a21427e5c6fc1adf98dcd", size = 72802, upload-time = "2026-05-21T21:58:56.551Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user