Move open redirect fix to asgi_send_redirect, refs #2429

See https://github.com/simonw/datasette/pull/2500#issuecomment-3488632278
This commit is contained in:
Simon Willison 2025-11-04 17:08:06 -08:00
commit 8b371495dc
3 changed files with 7 additions and 7 deletions

View file

@ -2150,11 +2150,6 @@ class DatasetteRouter:
context = {}
if path.endswith(b"/"):
path = path.rstrip(b"/")
# If you redirect with a // at the beginning, you end up with an open redirect, so
# https://my.site//foo/ - will redirect to https://foo
path = re.sub(rb"^/+", b"/", path)
if request.scope["query_string"]:
path += b"?" + request.scope["query_string"]
await asgi_send_redirect(send, path.decode("latin1"))

View file

@ -6,6 +6,7 @@ from pathlib import Path
from http.cookies import SimpleCookie, Morsel
import aiofiles
import aiofiles.os
import re
# Workaround for adding samesite support to pre 3.8 python
Morsel._reserved["samesite"] = "SameSite"
@ -248,6 +249,9 @@ async def asgi_send_html(send, html, status=200, headers=None):
async def asgi_send_redirect(send, location, status=302):
# Prevent open redirect vulnerability: strip multiple leading slashes
# //example.com would be interpreted as a protocol-relative URL (e.g., https://example.com/)
location = re.sub(r"^/+", "/", location)
await asgi_send(
send,
"",

View file

@ -100,6 +100,7 @@ def test_custom_route_pattern_404(custom_pages_client):
def test_custom_route_pattern_with_slash_slash_302(custom_pages_client):
response = custom_pages_client.get("//nastyOpenRedirect/")
# https://github.com/simonw/datasette/issues/2429
response = custom_pages_client.get("//example.com/")
assert response.status == 302
assert response.headers["location"] == "/nastyOpenRedirect"
assert response.headers["location"] == "/example.com"