Coverage for src / taipanstack / security / types.py: 100%
48 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-23 14:54 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-23 14:54 +0000
1"""
2Pydantic-compatible security types.
4Provide ``Annotated`` type aliases that integrate TaipanStack's
5runtime security guards with Pydantic v2 validation, enabling
6declarative, type-safe input validation inside ``BaseModel``
7definitions.
8"""
10import html
11import re
12from typing import Annotated
14from pydantic.functional_validators import AfterValidator
16from taipanstack.core.result import Err, Ok
17from taipanstack.security.guards import (
18 SecurityError,
19 guard_command_injection,
20 guard_path_traversal,
21 guard_ssrf,
22)
23from taipanstack.security.validators import (
24 validate_project_name,
25 validate_url,
26)
29def _validate_safe_url(url: str) -> str:
30 """Validate a URL is safe from SSRF attacks.
32 Args:
33 url: The URL string to validate.
35 Returns:
36 The validated URL.
38 Raises:
39 ValueError: If the URL fails SSRF validation.
41 """
42 match guard_ssrf(url):
43 case Ok(val):
44 return val
45 case Err(err):
46 raise ValueError(str(err)) from err
49def _validate_safe_path(path: str) -> str:
50 """Validate a path is safe from traversal attacks.
52 Args:
53 path: The path string to validate.
55 Returns:
56 The validated path string.
58 Raises:
59 ValueError: If path traversal is detected.
61 """
62 try:
63 guard_path_traversal(path)
64 except SecurityError as exc:
65 raise ValueError(str(exc)) from exc
66 return path
69def _validate_safe_command(command: str) -> str:
70 """Validate a command string is safe from injection attacks.
72 Args:
73 command: The command string to validate.
75 Returns:
76 The validated command string.
78 Raises:
79 ValueError: If command injection is detected.
81 """
82 try:
83 guard_command_injection([command])
84 except SecurityError as exc:
85 raise ValueError(str(exc)) from exc
86 return command
89def _validate_safe_project_name(name: str) -> str:
90 """Validate a project name conforms to safe naming rules.
92 Args:
93 name: The project name to validate.
95 Returns:
96 The validated project name.
98 Raises:
99 ValueError: If the name is invalid.
101 """
102 return validate_project_name(name)
105def _validate_safe_url_format(url: str) -> str:
106 """Validate a URL has a correct format and allowed scheme.
108 Args:
109 url: The URL string to validate.
111 Returns:
112 The validated URL.
114 Raises:
115 ValueError: If the URL format is invalid.
117 """
118 return validate_url(url)
121SafeUrl = Annotated[
122 str,
123 AfterValidator(_validate_safe_url_format),
124 AfterValidator(_validate_safe_url),
125]
126"""A URL validated for correct format and safe from SSRF attacks."""
128SafePath = Annotated[str, AfterValidator(_validate_safe_path)]
129"""A filesystem path validated against path-traversal attacks."""
131SafeCommand = Annotated[str, AfterValidator(_validate_safe_command)]
132"""A command string validated against shell-injection attacks."""
134SafeProjectName = Annotated[str, AfterValidator(_validate_safe_project_name)]
135"""A project name validated for safe naming conventions."""
138_SQL_IDENTIFIER_REGEX = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*\Z")
141def _sanitize_safe_html(text: str) -> str:
142 """Sanitize a string to prevent XSS attacks.
144 Args:
145 text: The string to sanitize.
147 Returns:
148 The HTML-escaped string.
150 """
151 return html.escape(text)
154def _validate_safe_sql_identifier(identifier: str) -> str:
155 """Validate a string is a safe SQL identifier.
157 Args:
158 identifier: The SQL identifier to validate.
160 Returns:
161 The validated SQL identifier.
163 Raises:
164 ValueError: If the identifier implies SQL injection risk.
166 """
167 if not _SQL_IDENTIFIER_REGEX.match(identifier):
168 raise ValueError(
169 f"Invalid SQL identifier: '{identifier}'. "
170 "Must match ^[a-zA-Z_][a-zA-Z0-9_]*$"
171 )
172 return identifier
175SafeHtml = Annotated[str, AfterValidator(_sanitize_safe_html)]
176"""A string sanitized for safe inclusion in HTML templates."""
178SafeSqlIdentifier = Annotated[str, AfterValidator(_validate_safe_sql_identifier)]
179"""A string validated as a safe dynamic SQL identifier (e.g., table or column name)."""