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_filename in app.py handles 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →