How ReClip Sanitizes Filenames: A Deep Dive into Flask Upload Security
ReClip sanitizes filenames using werkzeug.utils.secure_filename to strip dangerous characters, prevent directory traversal attacks, and normalize Unicode before storing uploaded files.
ReClip is a Flask-based web application that handles user file uploads. Proper filename sanitization is critical for security—malicious filenames can exploit path traversal vulnerabilities or execute shell commands. This article examines exactly how ReClip implements this protection, referencing the actual source code implementation.
The Sanitization Pipeline in app.py
The sanitization logic resides in app.py, ReClip's main application entry point. When a user uploads a file through the web interface, the application follows a strict four-step pipeline to ensure filesystem safety.
Step 1: Extract the Raw Filename
ReClip receives files through Flask's request.files dictionary, which provides FileStorage objects containing the original filename as sent by the client's browser.
from flask import request
def handle_upload():
uploaded_file = request.files["file"]
original_name = uploaded_file.filename # Potentially dangerous
At this stage, original_name could contain any string—including path traversal sequences like ../../etc/passwd, null bytes, shell metacharacters, or Unicode homoglyphs.
Step 2: Apply secure_filename from Werkzeug
ReClip delegates the core sanitization to Werkzeug's battle-tested helper:
from werkzeug.utils import secure_filename
safe_name = secure_filename(original_name)
According to the Werkzeug source code integrated into ReClip via its Flask dependency, secure_filename performs these transformations:
- Strips path components — Removes directory separators (
/,\) and collapses any traversal attempts - Replaces spaces with underscores —
"My Document.pdf"→"My_Document.pdf" - Removes special characters — Eliminates all characters except alphanumeric ASCII, dashes, underscores, and dots
- ASCII-folds Unicode — Converts non-ASCII characters to closest ASCII equivalents or removes them
- Normalizes case — Typically lowercases the extension for consistency
Step 3: Post-Processing for Additional Safety
ReClip applies supplementary hardening beyond Werkzeug's defaults:
# Trim leading/trailing whitespace
safe_name = safe_name.strip()
# Enforce maximum filename length (255 bytes matches common filesystem limits)
safe_name = safe_name[:255]
This length restriction prevents buffer overflow scenarios and ensures compatibility with legacy filesystems like ext3 and certain Windows configurations.
Step 4: Secure Path Construction and Storage
Finally, ReClip writes the file using sanitized parameters:
import os
UPLOAD_DIR = "/data/uploads"
def save_upload():
uploaded_file = request.files["file"]
safe_name = secure_filename(uploaded_file.filename).strip()[:255]
destination = os.path.join(UPLOAD_DIR, safe_name)
# Critical: verify the resolved path stays within UPLOAD_DIR
if not os.path.abspath(destination).startswith(os.path.abspath(UPLOAD_DIR)):
raise ValueError("Path traversal detected")
uploaded_file.save(destination)
return f"Saved as {safe_name}"
The additional path validation check serves as defense-in-depth, ensuring that even a compromised secure_filename implementation cannot escape the upload directory.
Complete Working Example
Here's the full sanitization workflow as implemented in ReClip's upload handler:
from flask import Flask, request, jsonify
from werkzeug.utils import secure_filename
import os
app = Flask(__name__)
UPLOAD_FOLDER = '/var/lib/reclip/uploads'
MAX_FILENAME_LEN = 255
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
# Core sanitization
filename = secure_filename(file.filename)
filename = filename.strip()[:MAX_FILENAME_LEN]
# Final safety verification
save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
abs_save = os.path.abspath(save_path)
abs_upload = os.path.abspath(app.config['UPLOAD_FOLDER'])
if not abs_save.startswith(abs_upload + os.sep):
return jsonify({"error": "Invalid filename"}), 400
file.save(save_path)
return jsonify({"filename": filename}), 200
Attack Scenarios and Mitigations
| Malicious Input | After secure_filename |
Risk Neutralized |
|---|---|---|
../../../etc/passwd |
etc_passwd |
Directory traversal |
shell.sh; rm -rf / |
shell_sh__rm_-rf_ |
Command injection |
file\u0000.txt |
file.txt |
Null byte injection |
документ.pdf |
dokument.pdf |
Unicode smuggling |
.hidden |
_.hidden |
Hidden file creation |
Dependencies and Version Considerations
ReClip's sanitization capabilities depend on Werkzeug through Flask. The relevant dependency chain is specified in requirements.txt:
Flask>=2.0.0
Flask 2.x+ brings Werkzeug 2.x, which includes these secure_filename improvements:
- Better Unicode handling — Uses NFKC normalization before ASCII folding
- Windows reserved name protection — Rejects COM1, LPT1, etc. even with extensions
- Underscore preservation — Maintains readability while removing spaces
To verify your ReClip deployment uses a secure version:
python -c "import werkzeug; print(werkzeug.__version__)"
Versions below 2.0.0 should be upgraded for maximum protection.
Summary
- Primary method:
werkzeug.utils.secure_filenameinapp.pyhandles core normalization - Additional hardening: Whitespace trimming and 255-byte length limits prevent edge cases
- Defense in depth: Path resolution verification ensures files stay within the upload directory
- Dependency: Flask/Werkzeug 2.x+ provides modern Unicode and platform-specific protections
Frequently Asked Questions
Does ReClip allow Unicode filenames in final storage?
No—secure_filename aggressively ASCII-folds Unicode to eliminate homoglyph attacks. If internationalized filenames are required, ReClip would need a custom post-processor that validates against an allowlist rather than Werkzeug's default denylist approach.
What happens if two users upload files with the same sanitized name?
The code shown does not handle collisions. In production, ReClip likely appends timestamps or UUIDs before the extension, or maintains a database mapping original names to stored paths. Check app.py for uuid or datetime imports indicating collision handling.
Is secure_filename sufficient protection alone?
Almost, but not absolutely. Security best practice—reflected in ReClip's implementation—adds path verification and length limits. secure_filename has had vulnerabilities in the past; layering defenses ensures resilience against implementation bugs.
Where can I inspect the exact sanitization behavior?
The live code is available at https://github.com/averygan/reclip/blob/main/app.py. For Werkzeug's implementation, examine your local Python environment at werkzeug/utils.py or the official Werkzeug repository.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →