fix: validate host URL in GitHub integration OAuth callback by nimish-ks · Pull Request #806 · phasehq/console

Run the following command in your local git repository to apply this patch

cat << 'EOF' | git apply
diff --git a/backend/api/views/auth.py b/backend/api/views/auth.py
--- a/backend/api/views/auth.py
+++ b/backend/api/views/auth.py
@@ -43,6 +43,7 @@
 from ee.authentication.sso.oidc.okta.views import (
     OktaOpenIDConnectAdapter,
 )
+from urllib.parse import urlparse
 
 CLOUD_HOSTED = settings.APP_HOST == "cloud"
 
@@ -205,17 +206,28 @@
     state = json.loads(state_decoded)
     original_url = state.get("returnUrl", "/")
 
+    # Sanitize original_url to ensure it is a safe relative path
+    if not isinstance(original_url, str):
+        original_url = "/"
+    original_url = original_url.replace("\\", "")
+    parsed_original = urlparse(original_url)
+    if parsed_original.scheme or parsed_original.netloc or original_url.startswith("//"):
+        safe_original_url = "/"
+    else:
+        # Ensure it starts with a slash to keep redirection within the app
+        safe_original_url = original_url if original_url.startswith("/") else f"/{original_url}"
+
     if error:
         # User denied the OAuth consent
         return redirect(
-            f"{os.getenv('ALLOWED_ORIGINS')}{original_url}?error=access_denied"
+            f"{os.getenv('ALLOWED_ORIGINS')}{safe_original_url}?error=access_denied"
         )
 
     code = request.GET.get("code")
     if not code:
         # Something went wrong (missing code)
         return redirect(
-            f"{os.getenv('ALLOWED_ORIGINS')}{original_url}?error=missing_code"
+            f"{os.getenv('ALLOWED_ORIGINS')}{safe_original_url}?error=missing_code"
         )
 
     is_enterprise = bool(state.get("isEnterprise", False))
@@ -225,20 +228,19 @@
     name = state.get("name")
 
     # Validate host_url to prevent SSRF
-    from urllib.parse import urlparse
     from api.utils.network import validate_url_is_safe
 
     parsed = urlparse(host_url)
     if parsed.scheme not in ("https", "http"):
         return redirect(
-            f"{os.getenv('ALLOWED_ORIGINS')}{original_url}?error=invalid_host_url"
+            f"{os.getenv('ALLOWED_ORIGINS')}{safe_original_url}?error=invalid_host_url"
         )
 
     try:
         validate_url_is_safe(host_url)
     except Exception:
         return redirect(
-            f"{os.getenv('ALLOWED_ORIGINS')}{original_url}?error=invalid_host_url"
+            f"{os.getenv('ALLOWED_ORIGINS')}{safe_original_url}?error=invalid_host_url"
         )
 
     client_id = (
@@ -267,10 +258,10 @@
     access_token = response.json().get("access_token")
     if not access_token:
         return redirect(
-            f"{os.getenv('ALLOWED_ORIGINS')}{original_url}?error=token_exchange_failed"
+            f"{os.getenv('ALLOWED_ORIGINS')}{safe_original_url}?error=token_exchange_failed"
         )
 
     store_oauth_token("github", name, access_token, host_url, api_url, org_id)
 
     # Redirect back to Next.js app
-    return redirect(f"{os.getenv('ALLOWED_ORIGINS')}{original_url}")
+    return redirect(f"{os.getenv('ALLOWED_ORIGINS')}{safe_original_url}")
EOF