Version in base suite: 0.46.1-3 Base version: starlette_0.46.1-3 Target version: starlette_0.46.1-3+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/s/starlette/starlette_0.46.1-3.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/s/starlette/starlette_0.46.1-3+deb13u1.dsc changelog | 18 ++++- gbp.conf | 2 patches/CVE-2025-62727.patch | 135 +++++++++++++++++++++++++++++++++++++++++++ patches/series | 1 4 files changed, 151 insertions(+), 5 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpiendfnv7/starlette_0.46.1-3.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpiendfnv7/starlette_0.46.1-3+deb13u1.dsc: no acceptable signature found diff -Nru starlette-0.46.1/debian/changelog starlette-0.46.1/debian/changelog --- starlette-0.46.1/debian/changelog 2025-07-28 09:42:53.000000000 +0000 +++ starlette-0.46.1/debian/changelog 2026-01-19 19:57:07.000000000 +0000 @@ -1,9 +1,19 @@ +starlette (0.46.1-3+deb13u1) trixie; urgency=medium + + * Team upload. + * d/p/CVE-2025-62727.patch: Backport Upstream patch to fix CVE-2025-62727 + (denial of service via crafted HTTP Range header in FileResponse) + * d/changelog: Fix changelog indentation + * d/gbp.conf: Update to Trixie + + -- Matheus Polkorny Mon, 19 Jan 2026 16:57:07 -0300 + starlette (0.46.1-3) unstable; urgency=high - [ Yang Wang ] - * Fix CVE-2025-54121: Avoid event loop blocking during multipart file uploads - by writing to disk using thread pool to prevent synchronous blocking when - SpooledTemporaryFile rolls over to disk. (Closes: #1109805) + [ Yang Wang ] + * Fix CVE-2025-54121: Avoid event loop blocking during multipart file uploads + by writing to disk using thread pool to prevent synchronous blocking when + SpooledTemporaryFile rolls over to disk. (Closes: #1109805) -- Piotr Ożarowski Mon, 28 Jul 2025 11:42:53 +0200 diff -Nru starlette-0.46.1/debian/gbp.conf starlette-0.46.1/debian/gbp.conf --- starlette-0.46.1/debian/gbp.conf 2025-07-28 09:40:03.000000000 +0000 +++ starlette-0.46.1/debian/gbp.conf 2026-01-19 19:57:07.000000000 +0000 @@ -1,3 +1,3 @@ [DEFAULT] pristine-tar = True -debian-branch = debian/master +debian-branch = debian/trixie diff -Nru starlette-0.46.1/debian/patches/CVE-2025-62727.patch starlette-0.46.1/debian/patches/CVE-2025-62727.patch --- starlette-0.46.1/debian/patches/CVE-2025-62727.patch 1970-01-01 00:00:00.000000000 +0000 +++ starlette-0.46.1/debian/patches/CVE-2025-62727.patch 2026-01-19 19:57:07.000000000 +0000 @@ -0,0 +1,135 @@ +From: Marcelo Trylesinski +Date: Tue, 28 Oct 2025 18:14:01 +0100 +Subject: Merge commit from fork + +Origin: upstream, https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5 +No changes in the backport. +--- + starlette/responses.py | 46 ++++++++++++++++++++++++++++++++-------------- + tests/test_responses.py | 28 ++++++++++++++++++++++++++++ + 2 files changed, 60 insertions(+), 14 deletions(-) + +diff --git a/starlette/responses.py b/starlette/responses.py +index 81e89fa..dbb087a 100644 +--- a/starlette/responses.py ++++ b/starlette/responses.py +@@ -4,7 +4,6 @@ import hashlib + import http.cookies + import json + import os +-import re + import stat + import typing + import warnings +@@ -283,9 +282,6 @@ class RangeNotSatisfiable(Exception): + self.max_size = max_size + + +-_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") +- +- + class FileResponse(Response): + chunk_size = 64 * 1024 + +@@ -443,8 +439,8 @@ class FileResponse(Response): + def _should_use_range(self, http_if_range: str) -> bool: + return http_if_range == self.headers["last-modified"] or http_if_range == self.headers["etag"] + +- @staticmethod +- def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]: ++ @classmethod ++ def _parse_range_header(cls, http_range: str, file_size: int) -> list[tuple[int, int]]: + ranges: list[tuple[int, int]] = [] + try: + units, range_ = http_range.split("=", 1) +@@ -456,14 +452,7 @@ class FileResponse(Response): + if units != "bytes": + raise MalformedRangeHeader("Only support bytes range") + +- ranges = [ +- ( +- int(_[0]) if _[0] else file_size - int(_[1]), +- int(_[1]) + 1 if _[0] and _[1] and int(_[1]) < file_size else file_size, +- ) +- for _ in _RANGE_PATTERN.findall(range_) +- if _ != ("", "") +- ] ++ ranges = cls._parse_ranges(range_, file_size) + + if len(ranges) == 0: + raise MalformedRangeHeader("Range header: range must be requested") +@@ -495,6 +484,35 @@ class FileResponse(Response): + + return result + ++ @classmethod ++ def _parse_ranges(cls, range_: str, file_size: int) -> list[tuple[int, int]]: ++ ranges: list[tuple[int, int]] = [] ++ ++ for part in range_.split(","): ++ part = part.strip() ++ ++ # If the range is empty or a single dash, we ignore it. ++ if not part or part == "-": ++ continue ++ ++ # If the range is not in the format "start-end", we ignore it. ++ if "-" not in part: ++ continue ++ ++ start_str, end_str = part.split("-", 1) ++ start_str = start_str.strip() ++ end_str = end_str.strip() ++ ++ try: ++ start = int(start_str) if start_str else file_size - int(end_str) ++ end = int(end_str) + 1 if start_str and end_str and int(end_str) < file_size else file_size ++ ranges.append((start, end)) ++ except ValueError: ++ # If the range is not numeric, we ignore it. ++ continue ++ ++ return ranges ++ + def generate_multipart( + self, + ranges: typing.Sequence[tuple[int, int]], +diff --git a/tests/test_responses.py b/tests/test_responses.py +index 0a00e93..fad0b4b 100644 +--- a/tests/test_responses.py ++++ b/tests/test_responses.py +@@ -746,6 +746,34 @@ def test_file_response_insert_ranges(file_response_client: TestClient) -> None: + ] + + ++def test_file_response_range_without_dash(file_response_client: TestClient) -> None: ++ response = file_response_client.get("/", headers={"Range": "bytes=100, 0-50"}) ++ assert response.status_code == 206 ++ assert response.headers["content-range"] == f"bytes 0-50/{len(README.encode('utf8'))}" ++ ++ ++def test_file_response_range_empty_start_and_end(file_response_client: TestClient) -> None: ++ response = file_response_client.get("/", headers={"Range": "bytes= - , 0-50"}) ++ assert response.status_code == 206 ++ assert response.headers["content-range"] == f"bytes 0-50/{len(README.encode('utf8'))}" ++ ++ ++def test_file_response_range_ignore_non_numeric(file_response_client: TestClient) -> None: ++ response = file_response_client.get("/", headers={"Range": "bytes=abc-def, 0-50"}) ++ assert response.status_code == 206 ++ assert response.headers["content-range"] == f"bytes 0-50/{len(README.encode('utf8'))}" ++ ++ ++def test_file_response_suffix_range(file_response_client: TestClient) -> None: ++ # Test suffix range (last N bytes) - line 523 with empty start_str ++ response = file_response_client.get("/", headers={"Range": "bytes=-100"}) ++ assert response.status_code == 206 ++ file_size = len(README.encode("utf8")) ++ assert response.headers["content-range"] == f"bytes {file_size - 100}-{file_size - 1}/{file_size}" ++ assert response.headers["content-length"] == "100" ++ assert response.content == README.encode("utf8")[-100:] ++ ++ + @pytest.mark.anyio + async def test_file_response_multi_small_chunk_size(readme_file: Path) -> None: + class SmallChunkSizeFileResponse(FileResponse): diff -Nru starlette-0.46.1/debian/patches/series starlette-0.46.1/debian/patches/series --- starlette-0.46.1/debian/patches/series 2025-07-28 09:42:53.000000000 +0000 +++ starlette-0.46.1/debian/patches/series 2026-01-19 19:57:07.000000000 +0000 @@ -1,2 +1,3 @@ json-format.patch 0002-fix-cve-2024-28849-async-write.patch +CVE-2025-62727.patch