Zip Slip in Fastapi with Firestore
Zip Slip in Fastapi with Firestore — how this specific combination creates or exposes the vulnerability
Zip Slip is a path traversal vulnerability that occurs when a filename from a user-supplied archive is used to construct filesystem paths without proper validation. In a Fastapi application that interacts with Google Cloud Firestore, the risk arises not from Firestore itself, which stores documents and files as references rather than raw filesystem paths, but from how the application handles file downloads, extractions, and storage decisions before data reaches Firestore.
Consider a scenario where Fastapi receives an uploaded archive (e.g., a ZIP file), extracts it, and uses extracted filenames to create or update Firestore documents that record file metadata or object paths. If the application trusts the archive entries and concatenates them directly into a destination directory—such as os.path.join(UPLOAD_DIR, extracted_name)—an attacker can craft entries with path sequences like ../../../etc/passwd. During extraction, these sequences traverse outside the intended directory, potentially overwriting system or application files. Even though Firestore stores metadata (e.g., gs://bucket/file.txt or a document ID), the traversal can corrupt local state, expose sensitive files, or manipulate paths used later when generating signed URLs or referencing files in Firestore documents.
In the context of middleBrick’s 12 security checks, this vulnerability would surface under Input Validation and Unsafe Consumption. The scan tests whether the API correctly normalizes and restricts paths before using them in any downstream operation, including interactions with external services like Firestore. Because Fastapi does not inherently validate uploaded filenames, developers must explicitly sanitize inputs and resolve paths to ensure they remain within a designated directory. Without such controls, an unauthenticated attacker can probe the endpoint and induce path traversal, leading to information disclosure or integrity compromise.
An additional concern involves the handling of Firestore document IDs derived from filenames. If a malicious path results in a crafted document ID (e.g., using .. or unexpected separators), it may affect data isolation, especially if access control rules are not strictly enforced. While Firestore enforces its own security rules, an API layer that constructs references from unchecked inputs can still create references that bypass intended constraints, exposing data across tenants or users. middleBrick’s BOLA/IDOR and Property Authorization checks are designed to detect whether API endpoints inadvertently allow unauthorized access through insufficient path or identifier validation.
Firestore-Specific Remediation in Fastapi — concrete code fixes
To mitigate Zip Slip in a Fastapi application that uses Firestore, enforce strict path normalization and confinement before any interaction with storage or document references. Always resolve uploaded paths against a base directory and reject entries that escape this boundary. Below are concrete, working examples that demonstrate secure handling.
Secure file extraction and Firestore metadata storage
Use pathlib with resolve() and validate that the resolved path remains within the allowed directory. This prevents traversal regardless of archive entry patterns.
from fastapi import Fastapi, UploadFile, HTTPException
from google.cloud import firestore
from pathlib import Path
import zipfile
import tempfile
import os
app = Fastapi()
db = firestore.Client()
BASE_DIR = Path("/safe/upload/root").resolve()
@app.post("/upload-archive/")
async def upload_archive(file: UploadFile):
if not file.filename.endswith(".zip"):
raise HTTPException(status_code=400, detail="Only ZIP files are allowed")
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir) / file.filename
tmp_path.write_bytes(await file.read())
with zipfile.ZipFile(tmp_path, "r") as zf:
for member in zf.infolist():
# Securely resolve the extraction target
member_path = Path(member.filename).resolve()
target = (Path(tmpdir) / member_path).resolve()
# Ensure the resolved path stays within the allowed directory
try:
target.relative_to(BASE_DIR)
except ValueError:
raise HTTPException(status_code=400, detail=f"Invalid path detected: {member.filename}")
zf.extract(member, tmpdir)
# Store safe metadata in Firestore using a controlled document ID
doc_ref = db.collection("uploads").document(member_path.name)
doc_ref.set({
"original_name": member.filename,
"stored_path": str(target),
"size": member.file_size
})
return {"status": "ok"}
Using Firestore security rules and reference validation
Even when paths are confined, validate document references on the server side. Avoid constructing document IDs directly from raw user input. Instead, use deterministic but safe identifiers, and enforce Firestore rules that align with your data model.
# Firestore security rules (conceptual; actual rules live in Firebase console)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /uploads/{docId} {
allow read, write: if request.auth != null && docId.matches('^[a-zA-Z0-9_-]+$');
}
}
}
Alternative: Signed URLs with controlled references
If files are stored in Cloud Storage, generate signed URLs using a path derived from a database document ID rather than from raw filenames. This ensures that references remain predictable and verifiable.
from google.cloud import storage
from uuid import uuid4
bucket = storage.Client().bucket("my-bucket")
filename = f"uploads/{uuid4().hex}.bin"
blob = bucket.blob(filename)
blob.upload_from_filename(local_path)
# Store the blob path in Firestore under a controlled document
db.collection("files").add({
"blob_path": filename,
"uploaded_at": firestore.SERVER_TIMESTAMP
})