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.
Metadata
Release files for safeuploads 1.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| safeuploads-1.1.3.tar.gz | 255.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| safeuploads-1.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 311.6 kB
Release files / safeuploads-1.1.3.tar.gz
| Download URL | safeuploads-1.1.3.tar.gz |
|---|---|
| Size | 255.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a2836f4e4088f07d6d76f1db0d572b9c6204c7e4a49e7780f6262d3dd8619556
|
|
BLAKE2b-256 checksum How to use checksums |
5c7fd3a4c7ee28c45f140f7f115751e61ad67a6c5b73f32ca9cfed732a66220b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.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.3-py3-none-any.whl
| Download URL | safeuploads-1.1.3-py3-none-any.whl |
|---|---|
| Size | 55.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a159602c5e4ea5b9f4bae547be946d7981de96ade0795fa60b0f77cc1acd55cb
|
|
BLAKE2b-256 checksum How to use checksums |
5c458fc578a5fe58b426d195465d2aee69d1a0b9c7fc55f5ea020a5c71692959
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.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}
|
Security for safeuploads 1.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.