Skip to content

Conversation

@renovate
Copy link
Contributor

@renovate renovate bot commented Feb 9, 2026

This PR contains the following updates:

Package Change Age Confidence
litestar (changelog) 2.19.02.20.0 age confidence

GitHub Vulnerability Alerts

CVE-2026-25478

Summary

CORS origin validation can be bypassed because the allowed-origins allowlist is compiled into a regex without escaping metacharacters (notably .). An allowed origin like https://good.example can match https://goodXexample, resulting in Access-Control-Allow-Origin being set for an untrusted origin

Details

CORSConfig.allowed_origins_regex is constructed using a regex built from configured allowlist values and used with fullmatch() for validation. Because metacharacters are not escaped, a malicious origin can match unexpectedly. The check relies on allowed_origins_regex.fullmatch(origin).

PoC

Server (poc_cors_server.py)

from litestar import Litestar, get
from litestar.config.cors import CORSConfig

@​get("/c")
async def c() -> str:
    return "ok"

cors = CORSConfig(
    allow_origins=["https://good.example"],
    allow_credentials=True,
)
app = Litestar([c], cors_config=cors)

uvicorn poc_cors_server:app --host 127.0.0.1 --port 8002

Client (poc_cors_client.py)

import http.client

def req(origin: str) -> tuple[int, str | None]:
    c = http.client.HTTPConnection("127.0.0.1", 8002, timeout=3)
    c.request("GET", "/c", headers={"Origin": origin, "Host": "example.com"})
    r = c.getresponse()
    r.read()
    acao = r.getheader("Access-Control-Allow-Origin")
    c.close()
    return r.status, acao

print("evil:", req("https://evil.example"))
print("bypass:", req("https://goodXexample")) 

Expected (vulnerable behavior):

Origin: https://evil.example → no ACAO
Origin: https://goodXexample → ACAO: https://goodxexample/ (bypass)

Impact

Type: CORS policy bypass (cross-origin data exposure risk)
Who is impacted: apps using CORS allowlists to restrict browser cross-origin reads. If allow_credentials=True and authenticated endpoints return sensitive data, an attacker-controlled site can potentially read responses in a victim’s browser session.

CVE-2026-25479

Summary

AllowedHosts host validation can be bypassed because configured host patterns are turned into regular expressions without escaping regex metacharacters (notably .). A configured allowlist entry like example.com can match exampleXcom

Details

In litestar.middleware.allowed_hosts, allowlist entries are compiled into regex patterns in a way that allows regex metacharacters to retain special meaning (e.g., . matches any character). This enables a bypass where an attacker supplies a host that matches the regex but is not the intended literal hostname.

PoC

Server (poc_allowed_hosts_server.py)

from litestar import Litestar, get
from litestar.middleware.allowed_hosts import AllowedHostsConfig

@​get("/")
async def index() -> str:
    return "ok"

config = AllowedHostsConfig(allowed_hosts=["example.com"])
app = Litestar([index], allowed_hosts_config=config)

uvicorn poc_allowed_hosts_server:app --host 127.0.0.1 --port 8001

Client (poc_allowed_hosts_client.py)

import http.client

def req(host_header: str) -> tuple[int, bytes]:
    c = http.client.HTTPConnection("127.0.0.1", 8001, timeout=3)
    c.request("GET", "/", headers={"Host": host_header})
    r = c.getresponse()
    body = r.read()
    c.close()
    return r.status, body

print("evil.com:", *req("evil.com"))
print("exampleXcom:", *req("exampleXcom"))

Expected (vulnerable behavior):
Host: evil.com → 400 invalid host

Host: exampleXcom → 200 ok (bypass)

Impact

Type: security control bypass (host allowlist)
Who is impacted: apps relying on AllowedHosts to prevent Host header attacks (cache poisoning, absolute URL construction abuse, password reset link poisoning, etc.). The downstream impact depends on app behavior, but the bypass defeats a core mitigation layer.

CVE-2026-25480

Summary

FileStore maps cache keys to filenames using Unicode NFKD normalization and ord() substitution without separators, creating key collisions. When FileStore is used as response-cache backend, an unauthenticated remote attacker can trigger cache key collisions via crafted paths, causing one URL to serve cached responses of another (cache poisoning/mixup)

Details

litestar.stores.file._safe_file_name() normalizes input with unicodedata.normalize("NFKD", name) and builds the filename by concatenating c if alphanumeric else str(ord(c)) (no delimiter).
This transformation is not injective, e.g.:

  • "k-" and "k45" both become "k45" (because - ord('-') == 45)
  • "k/\n" becomes "k4710", colliding with "k4710"
  • "K" (Kelvin sign) normalizes to "K", colliding with "K"

When used in response caching, the default cache key includes request path and sorted query params, which are attacker-controlled.

PoC

import asyncio, tempfile
from litestar.stores.file import FileStore

async def main():
    d = tempfile.mkdtemp(prefix="ls_filestore_poc_")
    store = FileStore(d, create_directories=True)
    await store.__aenter__()

    # 1) ASCII ord-collision: "-" -> 45
    await store.set("k-", b"A")
    v = await store.get("k45")
    print("k-  ->", v)
    print("k45 ->", await store.get("k45"))
    if v == b"A":
        print("VULNERABLE: 'k-' collides with 'k45'")

    # 2) NFKD collision: Kelvin sign -> K
    await store.set("K", b"B")   # U+212A
    v2 = await store.get("K")
    print("K ->", await store.get("K"))
    print("K ->", v2)
    if v2 == b"B":
        print("VULNERABLE: 'K' collides with 'K' (NFKD)")

if __name__ == "__main__":
    asyncio.run(main())

Impact

Vulnerability type: cache poisoning / cache key collision.
Impacted deployments: applications using Litestar response caching with FileStore backend (or any attacker-influenced keying into FileStore).
Possible impact: serving incorrect cached content across distinct URLs, potential confidentiality/integrity issues depending on what endpoints are cached.


Release Notes

litestar-org/litestar (litestar)

v2.20.0

Compare Source

Sponsors 🌟

Thanks to these incredible business sponsors:

Thanks to these incredible personal sponsors:

New contributors 🎉

Changelog

https://docs.litestar.dev/2/release-notes/changelog.html#2.20.0

Compare Changes


Configuration

📅 Schedule: Branch creation - "" in timezone Australia/Brisbane, Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@codecov
Copy link

codecov bot commented Feb 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.16%. Comparing base (06561a5) to head (67fa90e).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #231   +/-   ##
=======================================
  Coverage   93.16%   93.16%           
=======================================
  Files          24       24           
  Lines        2106     2106           
=======================================
  Hits         1962     1962           
  Misses        144      144           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@itssimon itssimon merged commit 94c3d34 into main Feb 10, 2026
98 checks passed
@itssimon itssimon deleted the renovate/pypi-litestar-vulnerability branch February 10, 2026 01:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant