safeuploads
Secure file upload validation for Python 3.13+ applications. Catches dangerous filenames, malicious extensions, Windows reserved names, and compression-based attacks before you accept an upload.
Features
- Framework-agnostic async validation (FastAPI, generic)
- Filename sanitization and Unicode security checks
- Extension validation with configurable allow/block lists
- ZIP bomb detection, nested archive inspection, and recursive structure protection
- MIME type verification with file signature validation
- Activity file support (.gpx, .tcx, .fit) with XXE-safe XML parsing
- Gzip archive validation with decompression bomb detection
- Streaming validation for memory-efficient large file processing
- Resource monitoring (CPU time and memory limits)
- Content analysis with malware signature and polyglot detection
- Structured audit logging with correlation IDs
- Rich exception hierarchy with machine-readable error codes
- Zero configuration required—secure defaults out of the box
Installation
pip install safeuploads
For FastAPI integration:
pip install safeuploads[fastapi]
Quick Start
from fastapi import FastAPI, UploadFile, HTTPException
from safeuploads import FileValidator
from safeuploads.exceptions import FileValidationError
app = FastAPI()
validator = FileValidator()
@app.post("/upload")
async def upload_image(file: UploadFile):
try:
await validator.validate_image_file(file)
except FileValidationError as e:
raise HTTPException(status_code=400, detail=str(e))
return {"status": "success", "filename": file.filename}
Configuration
from safeuploads import FileValidator, FileSecurityConfig
# Use default secure configuration
validator = FileValidator()
# Or customize limits
config = FileSecurityConfig()
config.limits.max_image_size = 10 * 1024 * 1024 # 10 MiB
config.limits.max_compression_ratio = 50
# Opt in to strict ZIP checking: decompress every entry to
# reject archives with forged central-directory metadata
config.limits.verify_zip_decompression = True
validator = FileValidator(config=config)
# Optionally offload blocking inspection to a bounded pool
from concurrent.futures import ThreadPoolExecutor
pooled_validator = FileValidator(
config=config,
executor=ThreadPoolExecutor(max_workers=4),
)
Exception Handling
from safeuploads.exceptions import (
FileValidationError, # Base exception
FileSizeError, # File too large
ExtensionSecurityError, # Dangerous extension
ZipBombError, # Compression attack
)
try:
await validator.validate_image_file(file)
except FileSizeError as err:
return {"error": "File too large", "max_size": err.max_size}
except ExtensionSecurityError as err:
return {"error": "File type not allowed", "extension": err.extension}
except FileValidationError as err:
return {"error": str(err), "code": err.error_code}
Current Status
Implemented
- Filename Security: Unicode normalization, directory traversal prevention, Windows reserved names blocking
- Extension Validation: Allow/block lists with configurable rules, dangerous extension detection
- Compression Security: ZIP bomb detection, nested archive inspection, recursive structure and quine detection, size and ratio limits, optional strict decompression verification
- Content Inspection: Deep ZIP content analysis with configurable depth and entry limits
- MIME Type Verification: Magic number validation for images, ZIP, activity files, and gzip
- Streaming Validation: Memory-efficient processing via
SpooledTemporaryFilefor large files - Resource Monitoring: CPU time and memory limits enforced via
ResourceMonitor - Activity File Support: GPX, TCX, and FIT file validation with XXE-safe XML parsing
- Gzip Support: Gzip archive validation with decompression bomb detection
- Content Analysis: Optional malware signature, web shell, and polyglot file detection
- Audit Logging: Structured security event logging with correlation IDs via
contextvars - Performance Optimizations: Pre-compiled pattern sets,
frozensetlookups, LRU-cached MIME guessing - Rich Exception System: Machine-readable error codes with detailed context
- Fuzzing Tests: Hypothesis-based property testing for filenames, ZIP, images, and config
Known Limitations
- No built-in rate limiting (application-level concern — see documentation)
- MIME detection covers first 8 KB; advanced polyglot attacks may require
enable_content_analysis SpooledTemporaryFileuses the system default temp directory
Documentation
Full documentation is available at the safeuploads docs site.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions welcome! See Contributing Guidelines for guidelines.
Built with ❤️ from Portugal | Part of the Endurain ecosystem
Release files for safeuploads 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| safeuploads-1.1.1.tar.gz | 231.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| safeuploads-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 286.8 kB
Release files / safeuploads-1.1.1.tar.gz
| Download URL | safeuploads-1.1.1.tar.gz |
|---|---|
| Size | 231.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d599db9913e2800127f7c1991bee06f0335812a953006f05a27af96e86f559d7
|
|
BLAKE2b-256 checksum How to use checksums |
e267abacf0e02389aece8230d603bbc2e2cb9fa77713702bb6838fb0521bd377
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / safeuploads-1.1.1-py3-none-any.whl
| Download URL | safeuploads-1.1.1-py3-none-any.whl |
|---|---|
| Size | 55.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f4e1706c783c96796bcf88d94678c89dde55b863a56854c2f82fadf163b746ff
|
|
BLAKE2b-256 checksum How to use checksums |
4399a4cfcb8d8249c525ef4b6f0d2c351e89dff54b28c8a235f0b3b4fe7a5ff1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|