---
title: "Store files under keys you make and clean · Agent Wranglers"
url: https://agentwranglers.se/playbook/files-and-media/uploads/safe-file-names
id: MED-40
level: Recommended
updated: 2026-10-22
build: a2a6bdfed830
tokens: 1093
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Store files under keys you make and clean the names you show

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Uploads](https://agentwranglers.se/playbook/files-and-media/uploads.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-22.

- ID: MED-40
- Level: Recommended
- Chapter: [Uploads](https://agentwranglers.se/playbook/files-and-media/uploads.md)
- Rule: Never use an uploaded file name in a storage key or a file path. Store each file under a random key, and keep the original name only as a cleaned label of at most 255 bytes.
- Check: Upload files named `../../a.txt`, `a\nb.pdf` and a 300-byte name in Swedish. Each is stored under a random key, and no file appears outside the upload prefix. The download answers with a `Content-Disposition` header holding a `filename*` with no line break and at most 255 bytes.
- How: A name such as `../../config.json`, `invoice.pdf.exe` or one holding a line break can escape a folder, trick a reader or break a header. Make the key from a random ID, such as `uploads/{owner}/{uuid}`. Clean the label before you save it. Normalise it to Unicode NFC, drop control characters and path separators, and cut it at 255 bytes on a character boundary. Send it back only in `Content-Disposition`. Give an ASCII `filename` and the full name in `filename*` with UTF-8, as RFC 6266 and RFC 8187 define. The attack test is [Keep file paths inside their folder](https://agentwranglers.se/playbook/application-security/requests-files-and-caches/block-path-traversal.md).

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
import { randomUUID } from 'node:crypto';

const MAX_NAME_BYTES = 255;
const UNSAFE_CHARACTERS = /[\u0000-\u001f\u007f/\\]/g;

export function cleanFileName(name) {
  let clean = name.normalize('NFC').replace(UNSAFE_CHARACTERS, '_').trim() || 'file';
  while (Buffer.byteLength(clean) > MAX_NAME_BYTES) clean = [...clean].slice(0, -1).join('');
  return clean;
}

const encodeRfc8187 = text =>
  encodeURIComponent(text).replace(/['()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);

export const storageKey = ownerId => `uploads/${ownerId}/${randomUUID()}`;

export const contentDisposition = name =>
  `attachment; filename="${name.replace(/[^\x20-\x7e]|"/g, '_')}"; filename*=UTF-8''${encodeRfc8187(name)}`;
```

### Python example

```python
import re
import unicodedata
import uuid
from urllib.parse import quote

MAX_NAME_BYTES = 255
UNSAFE_CHARACTERS = re.compile(r"[\x00-\x1f\x7f/\\]")

def clean_file_name(name: str) -> str:
    clean = UNSAFE_CHARACTERS.sub("_", unicodedata.normalize("NFC", name)).strip() or "file"
    return clean.encode()[:MAX_NAME_BYTES].decode(errors="ignore")

def storage_key(owner_id: str) -> str:
    return f"uploads/{owner_id}/{uuid.uuid4()}"

def content_disposition(name: str) -> str:
    ascii_name = re.sub(r'[^\x20-\x7e]|"', "_", name)
    return f"attachment; filename=\"{ascii_name}\"; filename*=UTF-8''{quote(name)}"
```

## Sources

- [RFC 6266 Use of the Content-Disposition Header Field in HTTP](https://www.rfc-editor.org/rfc/rfc6266)
- [RFC 8187 Indicating Character Encoding and Language for HTTP Header Field Parameters](https://www.rfc-editor.org/rfc/rfc8187)
- [CWE-22 Path Traversal](https://cwe.mitre.org/data/definitions/22.html)

## Related rules

- APS-44 [Keep file paths inside their folder](https://agentwranglers.se/playbook/application-security/requests-files-and-caches/block-path-traversal.md) in Application security

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/files-and-media/uploads/safe-file-names">Agent Wranglers files and media playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Turn SVG uploads into plain images and refuse HTML](https://agentwranglers.se/playbook/files-and-media/uploads/svg-and-html-uploads.md)
- [Uploads](https://agentwranglers.se/playbook/files-and-media/uploads.md)
