diff --git a/CHANGELOG.md b/CHANGELOG.md index d55e200..02a999c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.2] + +- Harden App Home Redirect URL handling. + ## [1.0.1] - Redact log response for exchange and refresh methods. diff --git a/shopify_app/_version.py b/shopify_app/_version.py index e5eefef..d162526 100644 --- a/shopify_app/_version.py +++ b/shopify_app/_version.py @@ -2,4 +2,4 @@ from __future__ import annotations -__version__ = "1.0.1" +__version__ = "1.0.2" diff --git a/shopify_app/helpers/app_home_redirect.py b/shopify_app/helpers/app_home_redirect.py index 93690c0..9f011b6 100644 --- a/shopify_app/helpers/app_home_redirect.py +++ b/shopify_app/helpers/app_home_redirect.py @@ -70,7 +70,7 @@ def app_home_redirect( shop=shop, log=LogWithReq( code="invalid_redirect_url", - detail=f"Redirect URL must be a relative path starting with '/'. Received {redirect_url}. Respond 400 Bad Request using the provided response.", + detail="Redirect URL was not a safe root-relative path. Respond 400 Bad Request using the provided response.", req=req, ), response=Res(status=400, body="Bad Request", headers={}), @@ -171,6 +171,11 @@ def _is_valid_relative_url(redirect_url: str) -> bool: if redirect_url.startswith("//"): return False + # Browsers remove tabs, line feeds, and carriage returns during URL + # preprocessing, which can turn an accepted URL into a protocol-relative URL + if any(control in redirect_url for control in ("\t", "\n", "\r")): + return False + # Must not be backslash-prefixed (/\evil.com) — browsers normalize \ to / # per the WHATWG URL Standard, turning it into a protocol-relative URL if len(redirect_url) > 1 and redirect_url[1] == "\\":