Potential fix for code scanning alert no. 6: URL redirection from remote source (#1406)

* Potential fix for code scanning alert no. 6: URL redirection from remote source

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fix: reject scheme-bearing EOSdash redirect paths

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
Normann
2026-10-14 15:23:02 +02:00
committed by GitHub
co-authored by Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
parent 812aa3dda9
commit 1f2faf3f7e
2 changed files with 37 additions and 4 deletions
+12 -4
View File
@@ -10,7 +10,7 @@ import traceback
from contextlib import asynccontextmanager
from enum import Enum
from typing import Annotated, Any, AsyncGenerator, Dict, List, Optional, Union
from urllib.parse import urlsplit
from urllib.parse import urljoin, urlsplit
import psutil
import uvicorn
@@ -2387,7 +2387,13 @@ def _sanitize_redirect_path(path: str) -> Optional[str]:
depth += 1
if depth < 0:
return None
return "/".join(parts)
safe_path = "/".join(parts)
# urljoin treats a colon in the first segment as a scheme, even without "://".
# Check after normalization, which can expose a scheme hidden by "./".
parsed = urlsplit(safe_path)
if parsed.scheme or parsed.netloc:
return None
return safe_path
def _trusted_request_hosts() -> set[str]:
@@ -2480,8 +2486,10 @@ def redirect(request: Request, path: str) -> Union[HTMLResponse, RedirectRespons
if get_config().server.eosdash_port:
if base_url is None:
return _eosdash_unknown_page(request, 404)
safe_path = _sanitize_redirect_path(path) or ""
url = f"{base_url}/{safe_path}"
safe_path = _sanitize_redirect_path(path)
if safe_path is None:
return _eosdash_unknown_page(request, 404)
url = urljoin(f"{base_url.rstrip('/')}/", safe_path)
return RedirectResponse(url=url, status_code=303)
# Redirect the root URL to the site map
+25
View File
@@ -464,6 +464,31 @@ class TestEosdashRedirect:
assert response.status_code == HTTPStatus.SEE_OTHER
assert response.headers["location"] == "http://localhost:8504/eosdash/health"
@pytest.mark.parametrize(
"path",
[
"/eosdash:payload",
"/eosdash+custom:payload",
"/eosdash:%09payload",
"/eosdash:%0Apayload",
],
)
def test_scheme_bearing_path_is_rejected(self, client, path):
"""User paths must not replace the configured dashboard URL with a scheme."""
response = client.get(path, headers={"Host": "localhost:8503"})
assert response.status_code == HTTPStatus.NOT_FOUND
assert "location" not in response.headers
@pytest.mark.parametrize(
"path",
["eosdash:payload", "./eosdash:payload", "eosdash:\tpayload", "eosdash:\npayload"],
)
def test_sanitizer_rejects_schemes_after_normalization(self, path):
"""Normalization must not turn an accepted path into a scheme-bearing URL."""
from akkudoktoreos.server.eos import _sanitize_redirect_path
assert _sanitize_redirect_path(path) is None
def test_unknown_path_error_page_links_to_request_host(self, client):
"""The 404 page links to EOSdash on the host the client used."""
response = client.get("/no-such-page", headers={"Host": "localhost:8503"})