Skip to content

Block open redirect via backslash in redirect Location#2729

Open
ATOM00blue wants to merge 1 commit into
simonw:mainfrom
ATOM00blue:fix-backslash-open-redirect
Open

Block open redirect via backslash in redirect Location#2729
ATOM00blue wants to merge 1 commit into
simonw:mainfrom
ATOM00blue:fix-backslash-open-redirect

Conversation

@ATOM00blue
Copy link
Copy Markdown
Contributor

Fixes #2680

The bug

The open-redirect protection added for #2429 collapses leading slashes in the redirect Location header so that //example.com/ becomes /example.com. It only strips forward slashes though:

location = re.sub(r"^/+", "/", location)

A request to /\example.com/ (or its %5C-encoded form, which proxies/servers may decode to a literal backslash before it reaches Datasette) therefore redirects to Location: /\example.com. Browsers treat a backslash the same as a forward slash, so the browser reads that as //example.com and follows it to https://example.com — the open redirect is back.

/\/example.com/ works the same way.

Reproduction

Against main:

/\example.com/    -> 302 Location: /\example.com    (browser -> https://example.com)
/\/example.com/   -> 302 Location: /\/example.com   (browser -> https://example.com)
//example.com/    -> 302 Location: /example.com     (already fixed by #2429)

The fix

Collapse leading backslashes as well as slashes in asgi_send_redirect:

location = re.sub(r"^[/\\]+", "/", location)

Now all three cases redirect to the same-origin path /example.com. Inner slashes/backslashes are left untouched, so normal paths are unaffected.

Testing

Extended the existing //example.com/ test (renamed to test_custom_route_pattern_open_redirect_302) to cover /\example.com/ and /\/example.com/. The two new cases fail before this change and pass after. pytest tests/test_custom_pages.py and the redirect/404 tests across the suite are green, and black reports no changes.

The leading-slash collapse added for simonw#2429 only stripped forward
slashes, so a path like /\example.com/ (or its %5C-encoded form) was
redirected to Location: /\example.com. Browsers treat a backslash as a
slash, so that resolves to //example.com and then https://example.com,
re-opening the open redirect.

Collapse leading backslashes as well as slashes. Extends the existing
slash-slash test to cover /\example.com/ and /\/example.com/.

Fixes simonw#2680
Copilot AI review requested due to automatic review settings May 22, 2026 01:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Addresses a regression in Datasette’s open-redirect protection by ensuring redirect Location headers can’t be made protocol-relative via leading backslashes (which browsers treat like forward slashes).

Changes:

  • Harden asgi_send_redirect() to collapse leading backslashes as well as forward slashes (^[/\\]+).
  • Expand and rename the existing custom-pages redirect regression test to cover backslash-based variants of the attack.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
datasette/utils/asgi.py Updates redirect sanitization to treat leading backslashes like leading slashes to prevent protocol-relative redirects.
tests/test_custom_pages.py Extends the redirect regression test to cover //example.com/, /\\example.com/, and /\\/example.com/ cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

def test_custom_route_pattern_open_redirect_302(custom_pages_client, path):
response = custom_pages_client.get(path)
assert response.status == 302
assert response.headers["location"] == "/example.com"
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.

Datasette server redirects /%5Cexample.com/ to https://example.com

2 participants