Security Considerations
This guide covers security considerations when using psd2svg, especially when processing untrusted input files.
Overview
psd2svg processes PSD files which can contain:
Complex layer structures
Embedded images (potentially very large)
Font references
External resource references
When processing untrusted PSD files, you should be aware of potential security risks and implement appropriate mitigations.
Security Features
Path Traversal Protection
The image_prefix parameter in save() and tostring() methods has built-in protections:
from psd2svg import SVGDocument
document = SVGDocument.from_psd(psdimage)
# ✓ Safe: Relative paths are allowed
document.save("output.svg", image_prefix="images/output")
# ✗ Blocked: Path traversal attempts are rejected
try:
document.save("output.svg", image_prefix="../../../etc/output")
except ValueError as e:
print(e) # "image_prefix cannot contain '..' (path traversal not allowed)"
# ✗ Blocked: Absolute paths without svg_filepath are rejected
try:
document.tostring(image_prefix="/tmp/output")
except ValueError as e:
print(e) # "image_prefix must be relative when svg_filepath is not provided"
These protections prevent malicious or compromised code from writing files outside intended directories.
Font File Validation
When extracting font files from SVG @font-face rules (used by ResvgRasterizer), psd2svg validates:
File extensions: Only valid font formats are accepted (
.ttf,.otf,.woff,.woff2,.ttc)File existence: Non-existent files are skipped with a warning
from psd2svg.rasterizer import ResvgRasterizer
# Font files are automatically validated
rasterizer = ResvgRasterizer()
# Valid font files are used, invalid ones are skipped
image = rasterizer.from_string(svg_content)
This prevents:
Access to arbitrary file paths (e.g.,
/etc/passwd)Log pollution from invalid paths
Potential information disclosure
Security Best Practices
Processing Untrusted Files
When processing PSD files from untrusted sources, psd2svg provides built-in resource limits to prevent denial-of-service attacks.
Built-in Resource Limits
New in version 0.4.0: psd2svg includes automatic DoS prevention via the ResourceLimits class.
By default, resource limits are automatically enabled with sensible defaults:
from psd_tools import PSDImage
from psd2svg import SVGDocument, convert
# Option 1: Using convert() - limits applied automatically
convert("input.psd", "output.svg")
# Applies default limits: 2GB file size, 3 minute timeout,
# 100 layer depth, 16K image dimension
# Option 2: Using from_psd() - limits applied automatically
psdimage = PSDImage.open("input.psd")
document = SVGDocument.from_psd(psdimage)
# Same default limits applied
Important: This is a breaking change from previous versions where no limits were enforced.
Customizing Resource Limits
For untrusted input, use stricter limits:
from psd2svg import ResourceLimits, convert
# Define stricter limits for untrusted input
limits = ResourceLimits(
max_file_size=500 * 1024 * 1024, # 500MB (vs 2GB default)
timeout=60, # 1 minute (vs 3 minutes default)
max_layer_depth=50, # 50 levels (vs 100 default)
max_image_dimension=8192 # 8K (vs 16K default)
)
# Use with convert()
convert("untrusted.psd", "output.svg", resource_limits=limits)
# Or with from_psd()
psdimage = PSDImage.open("untrusted.psd")
document = SVGDocument.from_psd(psdimage, resource_limits=limits)
Default Limits (Trusted Input)
The default limits are designed for professional PSD files from trusted sources:
from psd2svg import ResourceLimits
# Explicitly use default limits (same as passing None)
limits = ResourceLimits.default()
# - max_file_size: 2GB (typical for professional PSDs)
# - timeout: 180 seconds (3 minutes)
# - max_layer_depth: 100 layers deep
# - max_image_dimension: 16383 pixels (WebP hard limit)
Disabling Limits (Use with Caution)
For fully trusted input where you need to process very large files:
from psd2svg import ResourceLimits, convert
# WARNING: Only use for trusted input in controlled environments!
limits = ResourceLimits.unlimited()
convert("huge_trusted_file.psd", "output.svg", resource_limits=limits)
Environment Variable Configuration
Resource limits can be configured via environment variables. See Configuration Options for details on available environment variables and their usage.
Resource Limit Errors
When limits are exceeded, specific errors are raised:
from psd2svg import convert, ResourceLimits
try:
convert("large.psd", "output.svg")
except ValueError as e:
# File size limit exceeded:
# "File size 3221225472 bytes exceeds limit 2147483648 bytes.
# To process: set PSD2SVG_MAX_FILE_SIZE=3326459872 environment variable,
# or use ResourceLimits(max_file_size=3326459872) in Python API."
print(f"Limit exceeded: {e}")
except TimeoutError as e:
# Timeout exceeded:
# "PSD conversion timed out after 180 seconds. File may be complex.
# To process: set PSD2SVG_TIMEOUT=360 environment variable,
# or use ResourceLimits(timeout=360) in Python API."
print(f"Conversion took too long: {e}")
Note on Timeout: Cross-platform timeout is supported:
Unix/macOS: Uses
signal.SIGALRMfor reliable timeoutWindows: Uses threading-based timeout (may not interrupt native C code)
Sandboxing with Subprocess
Run conversion in a separate process with resource limits:
import subprocess
import json
from pathlib import Path
def convert_sandboxed(
psd_path: str,
output_path: str,
timeout: int = 60,
max_memory_mb: int = 512
) -> bool:
"""Convert PSD in sandboxed subprocess."""
script = f"""
from psd_tools import PSDImage
from psd2svg import SVGDocument
psdimage = PSDImage.open({psd_path!r})
document = SVGDocument.from_psd(psdimage)
document.save({output_path!r})
"""
try:
# Run in subprocess with timeout
result = subprocess.run(
["python", "-c", script],
timeout=timeout,
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Conversion failed: {result.stderr}")
return False
return True
except subprocess.TimeoutExpired:
print(f"Conversion timed out after {timeout} seconds")
return False
except Exception as e:
print(f"Conversion error: {e}")
return False
Container Isolation
For maximum isolation, run conversions in Docker containers:
# Dockerfile
FROM python:3.10-slim
RUN pip install psd2svg
WORKDIR /workspace
# Run with resource limits
docker run --rm \
--memory=512m \
--cpus=1 \
--network=none \
-v /path/to/input:/input:ro \
-v /path/to/output:/output:rw \
psd2svg-image \
python -m psd2svg /input/file.psd /output/file.svg
Path Validation
Always validate user-provided paths:
import os
from pathlib import Path
def validate_path(path: str, base_dir: str) -> str:
"""Validate path is within base directory."""
# Resolve to absolute path
resolved = Path(path).resolve()
base = Path(base_dir).resolve()
# Check if path is within base directory
try:
resolved.relative_to(base)
except ValueError:
raise ValueError(f"Path {path} is outside base directory {base_dir}")
return str(resolved)
# Usage
safe_output = validate_path(user_provided_path, "/safe/output/dir")
Font Security
When using custom fonts or font embedding:
from pathlib import Path
# Define trusted font directories
TRUSTED_FONT_DIRS = [
"/usr/share/fonts",
"/System/Library/Fonts",
str(Path.home() / ".fonts"),
]
def validate_font_path(font_path: str) -> bool:
"""Check if font comes from trusted directory."""
resolved = Path(font_path).resolve()
return any(
str(resolved).startswith(trusted_dir)
for trusted_dir in TRUSTED_FONT_DIRS
)
# Usage
if not validate_font_path(font_file):
raise ValueError("Untrusted font file")
Known Limitations
Resource Consumption
PSD processing can be resource-intensive:
Memory: Large PSD files or many embedded images can consume significant memory
CPU: Complex layer effects and deeply nested layers require substantial processing
Disk: Output SVG files with embedded images can be very large
Impact: Can lead to denial of service if not properly limited.
Mitigation (New in version 0.4.0):
psd2svg now includes built-in DoS prevention via automatic resource limits:
File size limits: 2GB default (customizable via
ResourceLimits)Timeout protection: 3 minutes default with cross-platform support
Layer depth limits: 100 levels default to prevent stack overflow
Image dimension limits: 16K pixels default (WebP hard limit)
See “Processing Untrusted Files” section above for configuration details.
For additional protection:
Run in resource-constrained environments (containers, VMs)
Monitor resource usage in production
Use stricter limits for untrusted input (500MB, 60 seconds)
WebP Dimension Limits
Critical Limitation: WebP format has a hard limit of 16383 pixels per dimension.
This is a common issue with professional Photoshop files that contain:
High-resolution print designs (e.g., 300 DPI posters)
Large billboard or banner designs
Detailed panoramic images
Individual layers exceeding 16383 pixels on either width or height
Impact: Conversion will fail with a WebP encoding error when encountering oversized layers.
Automatic Validation (New in version 0.4.0):
psd2svg now automatically validates image dimensions before conversion:
from psd2svg import convert
# Automatically rejects layers exceeding 16K dimension limit
try:
convert("large.psd", "output.svg")
except ValueError as e:
print(e)
# "Layer 'Background' dimensions 20000x15000 exceed limit 16383x16383"
To customize the dimension limit or disable it:
from psd2svg import ResourceLimits, convert
# Use a smaller limit (e.g., for untrusted input)
limits = ResourceLimits(max_image_dimension=8192)
convert("file.psd", "output.svg", resource_limits=limits)
# Or disable validation (not recommended)
limits = ResourceLimits(max_image_dimension=0)
convert("file.psd", "output.svg", resource_limits=limits)
Additional Mitigations:
Use alternative image formats for large layers:
# Save with PNG format for layers that might exceed WebP limits document.save("output.svg", image_format="PNG")
Downscale large layers before conversion (if quality loss is acceptable)
Rasterize the entire PSD instead of per-layer conversion for very large files
Note: This is a fundamental WebP format limitation, not a psd2svg limitation. PNG format supports up to 2^31-1 pixels per dimension and can be used as an alternative.
External Dependencies
psd2svg depends on several libraries:
psd-tools: PSD file parsingfonttools: Font handlingpillow: Image processingresvg-py: SVG rasterization (default)playwright: Browser-based rasterization (optional)
Impact: Vulnerabilities in dependencies could affect psd2svg.
Mitigation:
Automated security scanning via Dependabot and GitHub Actions
Regular dependency updates
See
SECURITY.mdfor vulnerability reporting
SVG Output Security
Generated SVG files may contain:
Embedded images (data URIs)
Font data (data URIs)
External resource references
Impact: SVG files rendered in browsers could potentially exploit browser vulnerabilities.
Mitigation:
Sanitize SVG output before rendering in untrusted contexts
Use Content Security Policy (CSP) when serving SVG files
Consider server-side rendering for untrusted content
Security Checklist
When deploying psd2svg in production:
Priority |
Recommendation |
|---|---|
High |
✓ Implement file size limits for input files |
High |
✓ Implement timeout protection for conversions |
High |
✓ Validate all user-provided file paths |
High |
✓ Keep dependencies up to date |
Medium |
✓ Run conversions in sandboxed environments |
Medium |
✓ Monitor resource usage (CPU, memory, disk) |
Medium |
✓ Validate font file sources |
Medium |
✓ Implement rate limiting for conversion APIs |
Low |
✓ Use container isolation for maximum security |
Low |
✓ Sanitize SVG output for web rendering |
Security Updates
Security features and fixes are documented in the changelog. Subscribe to:
GitHub Security Advisories: https://github.com/kyamagu/psd2svg/security/advisories
Release Notes: https://github.com/kyamagu/psd2svg/releases
Dependabot Alerts: Enable for your repository
Reporting Security Issues
See SECURITY.md in the repository root for instructions on reporting security vulnerabilities.
Additional Resources
SECURITY.md - Security policy