This commit is contained in:
2026-05-04 07:20:20 +02:00
parent 58173bd6a9
commit 744275d17f
4 changed files with 62 additions and 34 deletions

View File

@@ -41,6 +41,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from app.utils.display_sanitizer import sanitize_for_display
if TYPE_CHECKING:
from app.models.response import ErrorMetadata
@@ -137,7 +139,7 @@ class JailNotFoundError(NotFoundError):
def __init__(self, name: str) -> None:
self.name = name
super().__init__(f"Jail not found: {name!r}")
super().__init__(f"Jail not found: {sanitize_for_display(name)!r}")
def get_error_metadata(self) -> ErrorMetadata:
return {"jail_name": self.name}
@@ -277,8 +279,7 @@ class FilterRegexTooLongError(BadRequestError):
self.max_length = max_length
self.actual_length = len(pattern)
super().__init__(
f"Regex pattern exceeds maximum length of {max_length} characters: "
f"{self.actual_length} provided"
f"Regex pattern exceeds maximum length of {max_length} characters: {self.actual_length} provided"
)
def get_error_metadata(self) -> ErrorMetadata:
@@ -318,7 +319,7 @@ class JailNotFoundInConfigError(NotFoundError):
def __init__(self, name: str) -> None:
self.name = name
super().__init__(f"Jail not found in config: {name!r}")
super().__init__(f"Jail not found in config: {sanitize_for_display(name)!r}")
def get_error_metadata(self) -> ErrorMetadata:
return {"jail_name": self.name}
@@ -350,7 +351,7 @@ class JailAlreadyActiveError(ConflictError):
def __init__(self, name: str) -> None:
self.name = name
super().__init__(f"Jail is already active: {name!r}")
super().__init__(f"Jail is already active: {sanitize_for_display(name)!r}")
def get_error_metadata(self) -> ErrorMetadata:
return {"jail_name": self.name}
@@ -363,7 +364,7 @@ class JailAlreadyInactiveError(ConflictError):
def __init__(self, name: str) -> None:
self.name = name
super().__init__(f"Jail is already inactive: {name!r}")
super().__init__(f"Jail is already inactive: {sanitize_for_display(name)!r}")
def get_error_metadata(self) -> ErrorMetadata:
return {"jail_name": self.name}
@@ -463,7 +464,6 @@ class ActionReadonlyError(ConflictError):
return {"action_name": self.name}
class SetupAlreadyCompleteError(ConflictError):
"""Raised when attempting to run setup when it has already been completed."""
@@ -473,7 +473,6 @@ class SetupAlreadyCompleteError(ConflictError):
super().__init__("Setup has already been completed.")
class BlocklistSourceNotFoundError(NotFoundError):
"""Raised when a blocklist source is not found."""
@@ -495,8 +494,7 @@ class BlocklistSourceHasLogsError(ConflictError):
def __init__(self, source_id: int) -> None:
self.source_id = source_id
super().__init__(
f"Blocklist source {source_id} cannot be deleted because it has import logs. "
"Delete the import logs first."
f"Blocklist source {source_id} cannot be deleted because it has import logs. Delete the import logs first."
)
def get_error_metadata(self) -> ErrorMetadata:

View File

@@ -0,0 +1,28 @@
"""Display sanitization utilities for HTML render contexts.
All user-supplied values echoed in error messages or other HTML-rendered
output MUST be sanitized first. This module provides the canonical
sanitize_for_display() function.
"""
from __future__ import annotations
import html
def sanitize_for_display(value: str) -> str:
"""Escape HTML special characters in user-supplied strings.
Use this before interpolating user input into any string that will be
rendered in an HTML context (e.g. error messages, admin UI, email).
Does NOT over-escape: JSON responses are not HTML contexts and do not
need this treatment. Apply sanitization only at HTML render boundaries.
Args:
value: Raw user-supplied string.
Returns:
The string with HTML special characters escaped.
"""
return html.escape(value, quote=True)