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

1""" 

2Pydantic-compatible security types. 

3 

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""" 

9 

10import html 

11import re 

12from typing import Annotated 

13 

14from pydantic.functional_validators import AfterValidator 

15 

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) 

27 

28 

29def _validate_safe_url(url: str) -> str: 

30 """Validate a URL is safe from SSRF attacks. 

31 

32 Args: 

33 url: The URL string to validate. 

34 

35 Returns: 

36 The validated URL. 

37 

38 Raises: 

39 ValueError: If the URL fails SSRF validation. 

40 

41 """ 

42 match guard_ssrf(url): 

43 case Ok(val): 

44 return val 

45 case Err(err): 

46 raise ValueError(str(err)) from err 

47 

48 

49def _validate_safe_path(path: str) -> str: 

50 """Validate a path is safe from traversal attacks. 

51 

52 Args: 

53 path: The path string to validate. 

54 

55 Returns: 

56 The validated path string. 

57 

58 Raises: 

59 ValueError: If path traversal is detected. 

60 

61 """ 

62 try: 

63 guard_path_traversal(path) 

64 except SecurityError as exc: 

65 raise ValueError(str(exc)) from exc 

66 return path 

67 

68 

69def _validate_safe_command(command: str) -> str: 

70 """Validate a command string is safe from injection attacks. 

71 

72 Args: 

73 command: The command string to validate. 

74 

75 Returns: 

76 The validated command string. 

77 

78 Raises: 

79 ValueError: If command injection is detected. 

80 

81 """ 

82 try: 

83 guard_command_injection([command]) 

84 except SecurityError as exc: 

85 raise ValueError(str(exc)) from exc 

86 return command 

87 

88 

89def _validate_safe_project_name(name: str) -> str: 

90 """Validate a project name conforms to safe naming rules. 

91 

92 Args: 

93 name: The project name to validate. 

94 

95 Returns: 

96 The validated project name. 

97 

98 Raises: 

99 ValueError: If the name is invalid. 

100 

101 """ 

102 return validate_project_name(name) 

103 

104 

105def _validate_safe_url_format(url: str) -> str: 

106 """Validate a URL has a correct format and allowed scheme. 

107 

108 Args: 

109 url: The URL string to validate. 

110 

111 Returns: 

112 The validated URL. 

113 

114 Raises: 

115 ValueError: If the URL format is invalid. 

116 

117 """ 

118 return validate_url(url) 

119 

120 

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.""" 

127 

128SafePath = Annotated[str, AfterValidator(_validate_safe_path)] 

129"""A filesystem path validated against path-traversal attacks.""" 

130 

131SafeCommand = Annotated[str, AfterValidator(_validate_safe_command)] 

132"""A command string validated against shell-injection attacks.""" 

133 

134SafeProjectName = Annotated[str, AfterValidator(_validate_safe_project_name)] 

135"""A project name validated for safe naming conventions.""" 

136 

137 

138_SQL_IDENTIFIER_REGEX = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*\Z") 

139 

140 

141def _sanitize_safe_html(text: str) -> str: 

142 """Sanitize a string to prevent XSS attacks. 

143 

144 Args: 

145 text: The string to sanitize. 

146 

147 Returns: 

148 The HTML-escaped string. 

149 

150 """ 

151 return html.escape(text) 

152 

153 

154def _validate_safe_sql_identifier(identifier: str) -> str: 

155 """Validate a string is a safe SQL identifier. 

156 

157 Args: 

158 identifier: The SQL identifier to validate. 

159 

160 Returns: 

161 The validated SQL identifier. 

162 

163 Raises: 

164 ValueError: If the identifier implies SQL injection risk. 

165 

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 

173 

174 

175SafeHtml = Annotated[str, AfterValidator(_sanitize_safe_html)] 

176"""A string sanitized for safe inclusion in HTML templates.""" 

177 

178SafeSqlIdentifier = Annotated[str, AfterValidator(_validate_safe_sql_identifier)] 

179"""A string validated as a safe dynamic SQL identifier (e.g., table or column name)."""