mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-16 02:51:56 +00:00
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:
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
@@ -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
|
||||
|
||||
@@ -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"})
|
||||
|
||||
Reference in New Issue
Block a user