# How ReClip Sanitizes Filenames: A Deep Dive into Flask Upload Security

> ReClip sanitizes filenames with secure_filename to prevent attacks and normalize Unicode. Learn how ReClip protects your Flask uploads.

- Repository: [Avery Gan/reclip](https://github.com/averygan/reclip)
- Tags: deep-dive
- Published: 2026-09-03

---

**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`](https://github.com/averygan/reclip/blob/main/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.

```python
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**:

```python
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:

```python

# 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:

```python
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:

```python
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`](https://github.com/averygan/reclip/blob/main/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`](https://github.com/averygan/reclip/blob/main/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:

```bash
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`](https://github.com/averygan/reclip/blob/main/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`](https://github.com/averygan/reclip/blob/main/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`](https://github.com/averygan/reclip/blob/main/app.py). For Werkzeug's implementation, examine your local Python environment at [`werkzeug/utils.py`](https://github.com/averygan/reclip/blob/main/werkzeug/utils.py) or the [official Werkzeug repository](https://github.com/pallets/werkzeug).