Архитектура загрузки файлов: presigned URLs, multipart, безопасность
Direct browser → S3 через presigned URLs, валидация и virus scanning, MIME enforcement, thumbnails, retention, scale больших файлов (multipart, resumable).
Действуй как senior backend архитектор. Спроектируй загрузку файлов: типы {{file_types}}, max size {{max_size}}, storage {{storage}}.
1. Upload flow: direct-to-storage
Never проксировать файлы через свой backend для больших файлов — это (а) кладёт твои сервера на CPU/RAM, (б) удваивает egress costs, (в) тротлит upload speed.
Правильный flow:
1. Browser → POST /api/uploads/sign { filename, content_type, size }
2. Backend валидирует size/type, создаёт запись upload(id, status=pending),
возвращает { upload_id, presigned_url, fields, expires_at }
3. Browser → PUT/POST presigned_url с файлом (multipart если > 100MB)
4. На success → POST /api/uploads/{id}/complete
5. Backend: HEAD объект (verify exists + size), переводит в status=processing,
ставит job (scan + thumbnails)
6. Job завершён → status=ready, отдаём URL клиенту
Presigned URL TTL — 15 минут. Achievable upload, нечем DDoS'нуть.
2. Multipart / resumable для больших файлов
Файлы > 100MB:
- S3 Multipart Upload:
CreateMultipartUpload→ presigned URL для каждой part'и (5-100MB) →CompleteMultipartUpload. Failed parts можно re-upload индивидуально. - На клиенте — библиотека типа
evaporate.js/uppyс retry и progress. - Abort policy на bucket: incomplete multiparts старше 7 дней автоматически удаляются (lifecycle rule), иначе они едят storage, но не видны через UI.
Resumable (опционально, для > 1GB):
- Браузер сохраняет multipart state в localStorage / IndexedDB.
- При reload — продолжает с последней успешной части.
3. Безопасность: что должно быть обязательно
Validation server-side (нельзя верить клиенту)
- Content-Length в presign request → backend проверяет
size ≤ {{max_size}}до signing. - Content-Type проверяется по magic bytes (sniff первых 256 байт после upload), не по extension и не по client-provided MIME.
image/jpegс magic bytesMZ(Windows executable) = атака. - Filename sanitization — никогда не использовать user-provided filename как путь. Хранить как
<uuid>.<ext>, оригинальное имя — отдельным полем в БД.
Storage hardening
- Bucket private (no public read). Раздача через signed URLs / CloudFront with OAC.
- Object key:
uploads/<user_id>/<uuid>.<ext>—user_idв path для IAM-style ACL. - Server-side encryption (SSE-S3 / SSE-KMS).
- Versioning ON + lifecycle: delete old versions после N дней.
Virus / malware scanning
- На любые user-uploaded файлы — ClamAV (через sidecar / Lambda) или managed (S3 + GuardDuty Malware Protection, Cloudflare).
- Flow: upload completes → status=scanning → scan job → status=clean/infected. Infected — delete + alert + ban consideration.
- Не отдавать клиенту URL до завершения scan. Иначе malware ходит между юзерами через твой бакет.
MIME enforcement at serve time
- Когда отдаёшь файл —
Content-Disposition: attachmentдля всего что не image/audio/video, чтобы браузер не рендерил HTML/SVG inline (XSS). X-Content-Type-Options: nosniff.- Для images —
Content-Security-Policy: default-src 'none'.
4. Image processing / thumbnails
- Async после scan: job создаёт 3 размера (thumbnail 256px, preview 1024px, original).
- Используй CDN с on-the-fly resize (Cloudinary, imgproxy, Cloudflare Images) — generates сразу или по first request, кешируется.
- Strip EXIF — privacy (GPS coords из камеры юзера).
5. Retention
- TTL по умолчанию. Файлы без owner / неиспользуемые > N дней → soft-delete → hard-delete через 30 дней.
- Orphan cleanup job — files в storage без записи в БД → delete. Каждую неделю.
- User-initiated delete — soft delete immediately, hard delete в background после 7 дней (window для "восстановить").
- Compliance (GDPR right to erasure): hard delete по запросу < 30 дней, log того что удалено (без content).
6. Limits & abuse prevention
- Per-user quota: max N MB total, max M files / day.
429 Too Many Requestsна пресайн. - Rate limit на endpoint
/sign(например 60/min) — не дать заспамить bucket мусором. - Cost monitor — alert если cumulative storage растёт на > X% / day без явной фичи.
Формат вывода
## Architecture diagram
<ASCII / mermaid: browser ↔ api ↔ storage ↔ scan ↔ cdn>
## API surface
POST /api/uploads/sign — issue presigned
POST /api/uploads/{id}/complete — verify + enqueue processing
GET /api/uploads/{id} — status + URL when ready
DELETE /api/uploads/{id}
## Storage layout
uploads/<user_id>/<uuid>.<ext>
thumbnails/<user_id>/<uuid>_{256,1024}.jpg
## Bucket policy / lifecycle rules
<JSON / terraform snippet>
## Security checklist
✓ ...
Anti-patterns
- ❌ Принимать file через свой backend (
multipart/form-data→ app) — load на сервере, медленно, дорого. - ❌ Доверять client-provided MIME / extension — XSS / RCE через "image.jpg.php".
- ❌ Public bucket — кто-то найдёт ваш CDN URL pattern, скрейпит всё.
- ❌ User-provided filename как S3 key — path traversal (
../../etc/passwd), коллизии. - ❌ Не сканировать на virus — становишься хостингом для malware между юзерами.
- ❌ Без MultipartUpload abort policy — incomplete uploads накапливаются, вы платите за невидимый мусор.
- ❌ Без retention — storage растёт безгранично, через год bill в 10x.
- ❌ EXIF не stripped → утечка GPS / device info пользователей.
- ❌ Без размера в presign request → клиент upload'ит 100GB через URL который backend думал даст под 5MB.
Content Security Policy и security headers
CSP, HSTS, X-Frame, Permissions-Policy — закрыть основные классы атак за один проход.
Управление секретами
Где хранить, как ротировать, как обнаружить утечку.
Аутентификация и rate limiting
Защита логина, реги, восстановления пароля от brute force.