File size: 6,006 Bytes
54c5666 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
"""Security validation for inputs and configs"""
import os
from pathlib import Path
from typing import Dict, Any, List
import logging
import re
logger = logging.getLogger(__name__)
class SecurityValidator:
"""Security validation utilities"""
# Dangerous patterns to check
DANGEROUS_PATTERNS = [
r'__import__',
r'eval\s*\(',
r'exec\s*\(',
r'compile\s*\(',
r'os\.system',
r'subprocess',
r'open\s*\(',
]
@staticmethod
def check_code_injection(text: str) -> bool:
"""
Check for potential code injection
Args:
text: String to check
Returns:
True if safe, False if dangerous patterns detected
"""
for pattern in SecurityValidator.DANGEROUS_PATTERNS:
if re.search(pattern, text, re.IGNORECASE):
logger.warning(f"Dangerous pattern detected: {pattern}")
return False
return True
def validate_model_path(path: str, allowed_dirs: List[str] = None) -> bool:
"""
Validate model path to prevent directory traversal
Args:
path: Path to validate
allowed_dirs: List of allowed base directories
Returns:
True if path is safe, False otherwise
Raises:
ValueError: If path is dangerous
FileNotFoundError: If path doesn't exist
"""
# Resolve to absolute path
try:
abs_path = Path(path).resolve()
except Exception as e:
raise ValueError(f"Invalid path: {path}") from e
# Check for directory traversal
if '..' in str(abs_path):
raise ValueError("Directory traversal detected in path")
# Check if path contains suspicious characters
suspicious_chars = ['|', ';', '&', '$', '`']
if any(char in str(path) for char in suspicious_chars):
raise ValueError(f"Suspicious characters in path: {path}")
# Check allowed directories if specified
if allowed_dirs:
allowed_dirs_resolved = [Path(d).resolve() for d in allowed_dirs]
if not any(abs_path.is_relative_to(allowed_dir) for allowed_dir in allowed_dirs_resolved):
raise ValueError(f"Path not in allowed directories: {path}")
# Check file exists (optional - comment out if creating new files)
# if not abs_path.exists():
# raise FileNotFoundError(f"Path not found: {path}")
logger.debug(f"Path validated: {abs_path}")
return True
def sanitize_config(config: dict, max_depth: int = 10) -> dict:
"""
Sanitize configuration to prevent code injection
Args:
config: Configuration dictionary
max_depth: Maximum nesting depth to check
Returns:
Sanitized configuration
Raises:
ValueError: If dangerous configuration detected
"""
dangerous_keys = ['__import__', 'eval', 'exec', 'compile', 'open', 'input']
def check_dict(d: Dict[str, Any], depth: int = 0):
"""Recursively check dictionary"""
if depth > max_depth:
raise ValueError(f"Configuration nesting too deep: {depth}")
for key, value in d.items():
# Check key names
key_lower = str(key).lower()
if any(danger in key_lower for danger in dangerous_keys):
raise ValueError(f"Dangerous configuration key: {key}")
# Check for code in string values
if isinstance(value, str):
if not SecurityValidator.check_code_injection(value):
raise ValueError(f"Potential code injection in config value: {key}")
# Recursively check nested dicts
elif isinstance(value, dict):
check_dict(value, depth + 1)
# Check lists
elif isinstance(value, list):
for item in value:
if isinstance(item, dict):
check_dict(item, depth + 1)
elif isinstance(item, str):
if not SecurityValidator.check_code_injection(item):
raise ValueError(f"Potential code injection in config list: {key}")
# Create a copy and validate
sanitized = config.copy()
check_dict(sanitized)
logger.debug("Configuration sanitized successfully")
return sanitized
def validate_file_size(file_path: str, max_size_mb: int = 1000) -> bool:
"""
Validate file size to prevent resource exhaustion
Args:
file_path: Path to file
max_size_mb: Maximum file size in MB
Returns:
True if file size is acceptable
Raises:
ValueError: If file is too large
"""
path = Path(file_path)
if not path.exists():
raise FileNotFoundError(f"File not found: {file_path}")
size_mb = path.stat().st_size / (1024 * 1024)
if size_mb > max_size_mb:
raise ValueError(
f"File too large: {size_mb:.2f}MB (max: {max_size_mb}MB)"
)
return True
def sanitize_filename(filename: str) -> str:
"""
Sanitize filename to prevent path traversal
Args:
filename: Original filename
Returns:
Sanitized filename
"""
# Remove path separators
filename = os.path.basename(filename)
# Remove dangerous characters
dangerous_chars = ['..', '/', '\\', '\0', '|', ';', '&', '$', '`', '<', '>']
for char in dangerous_chars:
filename = filename.replace(char, '_')
# Limit length
max_length = 255
if len(filename) > max_length:
name, ext = os.path.splitext(filename)
filename = name[:max_length - len(ext)] + ext
return filename
|