ZCore LogoZCore
How to

How to securely upload files

Store files safely using Magic Byte inspection, file size guards, and path traversal defenses.

File uploads are high-risk entry points. ZCore's LocalStorageProvider defends your server against Path Traversal, Denial of Service (large files), and executable masquerading (e.g., PHP scripts or .exe binaries disguised as .jpg).

1. Configure the Storage Provider

Initialize LocalStorageProvider with your security validators and register it in the IoC container:

# main.py or storage_config.py
from zcore.storage import (
    LocalStorageProvider,
    SafeMimeTypeValidator,
    MaxFileSizeValidator,
    FileExtensionValidator,
    StorageProvider
)
from zcore.kernel.di import container

storage = LocalStorageProvider(
    base_path="./storage/uploads",
    url_prefix="/storage",
    validators=[
        # 1. Enforce strict 5 MB file size limit
        MaxFileSizeValidator(max_size_mb=5),
        
        # 2. Check file extension
        FileExtensionValidator(allowed_extensions=[".png", ".jpg", ".jpeg", ".webp", ".pdf"]),
        
        # 3. Inspect binary Magic Bytes and scan against disguised scripts/XSS
        SafeMimeTypeValidator(allowed_mimes=["image/png", "image/jpeg", "image/webp", "application/pdf"])
    ]
)

# Register as the active storage provider singleton
container.register_singleton(StorageProvider, storage)

2. Create the Upload Endpoint

Inject StorageProvider into your FastAPI route and upload the UploadFile payload:

# routers.py
from fastapi import APIRouter, UploadFile, File
from zcore import Inject
from zcore.storage import StorageProvider

router = APIRouter(prefix="/media", tags=["Media"])

@router.post("/avatar")
async def upload_avatar(
    file: UploadFile = File(...),
    storage: Inject[StorageProvider] = None
):
    # Saves to ./storage/uploads/avatars/ with collision-free UUID filename
    # Returns normalized web URL: "/storage/avatars/a3b8...png"
    web_url = await storage.upload(file, folder="avatars")
    return {"url": web_url}

Web URL Resolution: Both upload() and upload_stream() save the file securely and immediately return the normalized, web-accessible public path (e.g., /storage/avatars/5f8c41...png) ready to be persisted into your database or sent back to clients.

3. Secure File Deletion

Delete stored assets safely. ZCore accepts either the web URL or relative path, verifies that the target resides strictly inside base_path, and prevents sandbox traversal:

@router.delete("/avatar")
async def delete_avatar(
    url: str,
    storage: Inject[StorageProvider]
):
    # Accepts "/storage/avatars/5f8c41...png" or relative paths
    success = await storage.delete(url)
    return {"deleted": success}

Triple-Layer Security Architecture:

  • Magic Bytes & Script Inspection: Reads the initial 8192 header bytes against known binary signatures and scans against PHP execution tags (<?php), HTML scripts (<script), event handlers (onerror=), Unix shebangs (#!/), and Windows PE headers (MZ).
  • Path Traversal Defense: upload(), upload_stream(), and delete() resolve system paths strictly and assert .is_relative_to(base). Any directory breakout attempt (e.g., ../../etc/passwd) is safely blocked.
  • Collision-Free Naming: Uploaded files are automatically renamed with high-entropy randomized UUIDs (uuid4().hex + ext) to prevent filesystem overwrites and preserve privacy.

On this page