fix(regex_validator): add ReDoS detection via regexploit
Detect catastrophic backtracking patterns before regex compilation using regexploit library. Add ReDoSDetectedError exception and _MINIMUM_STARRINESS threshold (>=3) to catch dangerous patterns like (a+)+b. Update pyproject.toml deps, add tests for detection.
This commit is contained in:
@@ -5,7 +5,7 @@ Request, response, and domain models used by the ban router and service.
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic import Field, field_validator
|
||||
|
||||
from app.models.response import BanGuiBaseModel, CollectionResponse, PaginatedListResponse
|
||||
|
||||
@@ -67,6 +67,18 @@ class Ban(BanGuiBaseModel):
|
||||
description="Whether this ban came from a blocklist import or fail2ban itself.",
|
||||
)
|
||||
|
||||
@field_validator("country")
|
||||
@classmethod
|
||||
def _normalize_empty_country(cls, v: str | None) -> str | None:
|
||||
"""Coerce empty strings to None for country.
|
||||
|
||||
Geo enrichment may produce an empty string instead of None for
|
||||
unresolved IPs, which breaks frontend truthiness checks.
|
||||
"""
|
||||
if v == "":
|
||||
return None
|
||||
return v
|
||||
|
||||
class BanResponse(BanGuiBaseModel):
|
||||
"""Response containing a single ban record."""
|
||||
|
||||
@@ -97,6 +109,18 @@ class ActiveBan(BanGuiBaseModel):
|
||||
ban_count: int = Field(default=1, ge=1, description="Running ban count for this IP.")
|
||||
country: str | None = Field(default=None, description="ISO 3166-1 alpha-2 country code.")
|
||||
|
||||
@field_validator("country")
|
||||
@classmethod
|
||||
def _normalize_empty_country(cls, v: str | None) -> str | None:
|
||||
"""Coerce empty strings to None for country.
|
||||
|
||||
Geo enrichment may produce an empty string instead of None for
|
||||
unresolved IPs, which breaks frontend truthiness checks.
|
||||
"""
|
||||
if v == "":
|
||||
return None
|
||||
return v
|
||||
|
||||
class ActiveBanListResponse(CollectionResponse[ActiveBan]):
|
||||
"""List of all currently active bans across all jails.
|
||||
|
||||
@@ -154,6 +178,20 @@ class DashboardBanItem(BanGuiBaseModel):
|
||||
description="Whether this ban came from a blocklist import or fail2ban itself.",
|
||||
)
|
||||
|
||||
@field_validator("country_code")
|
||||
@classmethod
|
||||
def _normalize_empty_country_code(cls, v: str | None) -> str | None:
|
||||
"""Coerce empty strings to None for country_code.
|
||||
|
||||
The geo enrichment layer may produce an empty string instead of None
|
||||
for unresolved IPs. Frontend type narrowing uses truthiness, so an
|
||||
empty string would slip through ``if (ban.country_code)`` checks and
|
||||
appear as a falsy-but-not-null value — breaking UI rendering.
|
||||
"""
|
||||
if v == "":
|
||||
return None
|
||||
return v
|
||||
|
||||
class DashboardBanListResponse(PaginatedListResponse[DashboardBanItem]):
|
||||
"""Paginated dashboard ban-list response.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user