130 lines
3.1 KiB
Python
130 lines
3.1 KiB
Python
"""
|
|
Security utilities for Blender Toolkit addon
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
def validate_file_path(file_path: str, allowed_root: Optional[str] = None) -> str:
|
|
"""
|
|
Validate file path to prevent path traversal attacks.
|
|
|
|
Args:
|
|
file_path: Path to validate
|
|
allowed_root: Optional allowed root directory. If None, only checks for dangerous patterns.
|
|
|
|
Returns:
|
|
Validated absolute path
|
|
|
|
Raises:
|
|
ValueError: If path is invalid or outside allowed directory
|
|
"""
|
|
if not file_path:
|
|
raise ValueError("File path cannot be empty")
|
|
|
|
# Resolve to absolute path
|
|
try:
|
|
abs_path = os.path.abspath(os.path.expanduser(file_path))
|
|
except Exception as e:
|
|
raise ValueError(f"Invalid file path: {e}")
|
|
|
|
# Check for null bytes (security risk)
|
|
if '\0' in file_path:
|
|
raise ValueError("File path contains null bytes")
|
|
|
|
# If allowed_root is specified, ensure path is within it
|
|
if allowed_root:
|
|
allowed_abs = os.path.abspath(os.path.expanduser(allowed_root))
|
|
|
|
# Resolve symlinks to prevent bypass
|
|
try:
|
|
real_path = os.path.realpath(abs_path)
|
|
real_root = os.path.realpath(allowed_abs)
|
|
except Exception:
|
|
# If realpath fails, use absolute paths
|
|
real_path = abs_path
|
|
real_root = allowed_abs
|
|
|
|
# Check if path is within allowed root
|
|
try:
|
|
Path(real_path).relative_to(real_root)
|
|
except ValueError:
|
|
raise ValueError(f"Path outside allowed directory: {file_path}")
|
|
|
|
return abs_path
|
|
|
|
|
|
def validate_port(port: int) -> int:
|
|
"""
|
|
Validate WebSocket port number.
|
|
|
|
Args:
|
|
port: Port number to validate
|
|
|
|
Returns:
|
|
Validated port number
|
|
|
|
Raises:
|
|
ValueError: If port is invalid
|
|
"""
|
|
if not isinstance(port, int):
|
|
raise ValueError("Port must be an integer")
|
|
|
|
if port < 1024 or port > 65535:
|
|
raise ValueError("Port must be between 1024 and 65535")
|
|
|
|
return port
|
|
|
|
|
|
# Whitelist for safe object attributes
|
|
ALLOWED_OBJECT_ATTRIBUTES = {
|
|
'location',
|
|
'rotation_euler',
|
|
'rotation_quaternion',
|
|
'rotation_axis_angle',
|
|
'scale',
|
|
'name',
|
|
'hide',
|
|
'hide_viewport',
|
|
'hide_render',
|
|
'hide_select',
|
|
}
|
|
|
|
# Whitelist for safe armature bone attributes
|
|
ALLOWED_BONE_ATTRIBUTES = {
|
|
'name',
|
|
'head',
|
|
'tail',
|
|
'roll',
|
|
'use_connect',
|
|
'use_deform',
|
|
'use_inherit_rotation',
|
|
'use_inherit_scale',
|
|
'use_local_location',
|
|
}
|
|
|
|
|
|
def validate_attribute_name(attr_name: str, allowed_attributes: set) -> str:
|
|
"""
|
|
Validate attribute name against whitelist.
|
|
|
|
Args:
|
|
attr_name: Attribute name to validate
|
|
allowed_attributes: Set of allowed attribute names
|
|
|
|
Returns:
|
|
Validated attribute name
|
|
|
|
Raises:
|
|
ValueError: If attribute is not allowed
|
|
"""
|
|
if not attr_name:
|
|
raise ValueError("Attribute name cannot be empty")
|
|
|
|
if attr_name not in allowed_attributes:
|
|
raise ValueError(f"Attribute '{attr_name}' is not allowed")
|
|
|
|
return attr_name
|