fix: prevent XSS via javascript: URIs in recipe actions (#6885)

This commit is contained in:
Hayden
2026-01-16 12:19:27 -06:00
committed by GitHub
parent a72641b32e
commit 3e306638d0
2 changed files with 47 additions and 2 deletions

View File

@@ -1,7 +1,7 @@
from enum import Enum
from typing import Any
from pydantic import UUID4, ConfigDict
from pydantic import UUID4, ConfigDict, field_validator
from mealie.schema._mealie import MealieModel
from mealie.schema.response.pagination import PaginationBase
@@ -22,6 +22,14 @@ class CreateGroupRecipeAction(MealieModel):
model_config = ConfigDict(use_enum_values=True)
@field_validator("url")
def validate_url_scheme(url: str) -> str:
"""Validate that the URL uses a safe scheme to prevent XSS via javascript: URIs."""
url_lower = url.lower().strip()
if not (url_lower.startswith("http://") or url_lower.startswith("https://")):
raise ValueError("URL must use http or https scheme")
return url
class SaveGroupRecipeAction(CreateGroupRecipeAction):
group_id: UUID4

View File

@@ -25,7 +25,7 @@ def create_action(action_type: GroupRecipeActionType = GroupRecipeActionType.lin
return CreateGroupRecipeAction(
action_type=action_type,
title=random_string(),
url=random_string(),
url=f"https://example.com/{random_string()}",
)
@@ -194,3 +194,40 @@ def test_group_recipe_actions_trigger_invalid_type(api_client: TestClient, uniqu
)
assert response.status_code == 400
@pytest.mark.parametrize(
"url,should_pass",
[
("https://example.com", True),
("http://example.com", True),
("HTTPS://EXAMPLE.COM", True),
("HTTP://EXAMPLE.COM", True),
("javascript:alert('xss')", False),
("JAVASCRIPT:alert('xss')", False),
("data:text/html,<script>alert('xss')</script>", False),
("file:///etc/passwd", False),
("ftp://example.com", False),
("//example.com", False),
("example.com", False),
],
)
def test_group_recipe_actions_url_scheme_validation(
api_client: TestClient, unique_user: TestUser, url: str, should_pass: bool
):
"""Test that only http and https URLs are allowed to prevent XSS via javascript: URIs."""
action_data = {
"action_type": "link",
"title": random_string(),
"url": url,
}
response = api_client.post(
api_routes.households_recipe_actions,
json=action_data,
headers=unique_user.token,
)
if should_pass:
assert response.status_code == 201
else:
assert response.status_code == 422