---
title: "Confirm every upload on the server · Agent Wranglers"
url: https://agentwranglers.se/playbook/files-and-media/uploads/confirm-uploads
id: MED-43
level: Recommended
updated: 2026-10-22
build: a2a6bdfed830
tokens: 1375
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Confirm every upload on the server

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-43
- Level: Recommended
- Chapter: [Uploads](https://agentwranglers.se/playbook/files-and-media/uploads.md)
- Rule: Have the client send a SHA-256 checksum with each upload. Keep the new file `pending` until the server has checked its size, type and checksum from the stored bytes. Never trust what the client reports.
- Check: Upload a text file named `photo.jpg` with a content type of `image/jpeg`. The file never leaves `pending`, and the API answers with a problem response naming the type. Upload a file with the checksum of other bytes, and storage or the server refuses it. With the right checksum, the files table holds the same SHA-256.
- How: A dropped connection, a faulty proxy or a bug in a resumed upload can store a damaged file that looks fine until someone opens it. RFC 9530 defines the `Content-Digest` header for the bytes of one message, with `sha-256` and `sha-512` as active algorithms and MD5 and SHA-1 deprecated. Object storage takes its own checksum headers, such as `x-amz-checksum-sha256` on S3, and refuses bytes that do not match. Resumable uploads check each part and then the whole file. When the client says the upload is done, or storage sends its event, read the stored object. Compare its size with your limit. Find its type from the first bytes, and checksum it with SHA-256. Store all three in your files table, and refuse a file whose SHA-256 differs from the client's. Compare it again after every copy, as [Restore files and their records together](https://agentwranglers.se/playbook/files-and-media/storage-and-backups/restore-files-with-records.md) says. Only then mark it ready for the next step. The attacks this blocks, and how to test for them, are in [check every uploaded file](https://agentwranglers.se/playbook/application-security/requests-files-and-caches/safe-file-uploads.md). Delete pending files that were never confirmed within 24 hours.

## 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 { fileTypeFromBuffer } from 'file-type';

const ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'application/pdf']);

export async function confirmUpload(firstBytes, storedSize, maxBytes) {
  const detected = await fileTypeFromBuffer(firstBytes);
  if (storedSize > maxBytes) return { status: 'rejected', reason: 'too-large' };
  if (!detected || !ALLOWED_TYPES.has(detected.mime)) return { status: 'rejected', reason: 'type-not-allowed' };
  return { status: 'awaiting-scan', mime: detected.mime };
}
```

### Python example

```python
import magic

ALLOWED_TYPES = {"image/jpeg", "image/png", "application/pdf"}

def confirm_upload(first_bytes: bytes, stored_size: int, max_bytes: int) -> dict:
    detected = magic.from_buffer(first_bytes, mime=True)
    if stored_size > max_bytes:
        return {"status": "rejected", "reason": "too-large"}
    if detected not in ALLOWED_TYPES:
        return {"status": "rejected", "reason": "type-not-allowed"}
    return {"status": "awaiting-scan", "mime": detected}
```

## Sources

- [OWASP File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html)
- [RFC 9530 (Digest Fields)](https://www.rfc-editor.org/rfc/rfc9530)

## Related rules

- APP-24 [Check files in the browser before they upload](https://agentwranglers.se/playbook/app-ui/forms/file-upload-field.md) in Web app interface
- MED-1 [Define each kind of upload once](https://agentwranglers.se/playbook/files-and-media/uploads/upload-kinds.md) in Files and media
- MED-41 [Upload straight to storage with a signed address](https://agentwranglers.se/playbook/files-and-media/uploads/presigned-uploads.md) in Files and media
- MED-42 [Make large uploads resumable](https://agentwranglers.se/playbook/files-and-media/uploads/resumable-uploads.md) in Files and media
- MED-6 [Restore files and their records together](https://agentwranglers.se/playbook/files-and-media/storage-and-backups/restore-files-with-records.md) in Files and media
- APS-43 [Check every uploaded file](https://agentwranglers.se/playbook/application-security/requests-files-and-caches/safe-file-uploads.md) in Application security
- MED-9 [Accept the photo formats phones save](https://agentwranglers.se/playbook/files-and-media/images/phone-photo-formats.md) in Files and media

**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/confirm-uploads">Agent Wranglers files and media playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Make large uploads resumable](https://agentwranglers.se/playbook/files-and-media/uploads/resumable-uploads.md)
- Next: [Scan files for malware before anyone opens them](https://agentwranglers.se/playbook/files-and-media/uploads/scan-uploads.md)
- [Uploads](https://agentwranglers.se/playbook/files-and-media/uploads.md)
