~/zafer
Tüm yazılara dön
3 dk okuma

NestJS ve MinIO ile güvenli, S3 uyumlu dosya yükleme

NestJS ve MinIO ile güvenli, S3 uyumlu dosya yükleme

Görsel yükleme, küçük görünen ama güvenlik ve ölçeklenebilirlik açısından çok sayıda karar içeren bir özelliktir. Dosyayı uygulama diskine yazmak ilk aşamada kolaydır; container yeniden oluşturulduğunda, birden fazla uygulama örneği çalıştığında veya yedekleme gerektiğinde sorun çıkarır. S3 uyumlu obje depolama bu yüzden iyi bir sınır sunar.

Bu projede MinIO, obje depolama katmanı olarak kullanılıyor; NestJS ise dosyayı doğrulayan ve bucket'ı doğrudan herkese açmadan içeriği sunan katman.

Bucket'ı public yapmak zorunda değilsiniz

Yüklenen her görselin doğrudan bucket URL'sinden okunması pratik görünür. Ancak bucket'ı private tutup dosyayı API üzerinden servis etmek erişim politikasını tek noktada toplar. Uygulama MinIO'ya PutObject ile yükler; kullanıcı ise /api/storage/:folder/:filename rotasından dosyayı alır.

Bu modelde MinIO erişim anahtarları tarayıcıya gönderilmez. İleride kullanıcıya özel dosyalar, imzalı URL veya indirme izni gibi ihtiyaçlar doğarsa aynı sınırın içinde gelişebilirsiniz.

Her dosyayı kabul etmeyin

Yükleme endpoint'i için açık bir allowlist tanımlayın. Örneğin PNG, JPEG, WebP ve GIF dışında bir MIME türünü reddetmek; HTML, script veya beklenmeyen binary dosyaların görsel alanına girmesini engeller. Ayrıca dosya boyutuna sınır koyun.

const ALLOWED_MIME_TYPES = new Set([
  "image/png",
  "image/jpeg",
  "image/webp",
  "image/gif",
]);
const MAX_UPLOAD_BYTES = 10 * 1024 * 1024;

MIME doğrulaması tek başına her saldırıyı çözmez; fakat güvenli tasarımın temel katmanıdır. Uygulamanız kritik veya herkese açık yükleme alıyorsa içerik imzası doğrulama, kötü amaçlı yazılım taraması ve görsel yeniden kodlama gibi ek savunmaları değerlendirin.

Object key'i kullanıcıdan almayın

Kullanıcının verdiği dosya adı doğrudan storage key olmamalı. Aynı isimde iki dosya çakışabilir; path karakterleri güvenlik ve erişim sorunları doğurabilir. Güvenli bir klasör adı, zaman damgası ve rastgele değerle key üretmek basit bir başlangıçtır:

const key = `${folder}/${Date.now()}-${random}.webp`;

Route parametreleriyle dosya okurken her segmenti / ve \ içermeyecek şekilde doğrulayın. Bu, ../ benzeri path traversal denemelerinin uygulama katmanından geçmesini engeller.

Content-Type ve cache header'ları kontrol edin

Depodan dönen Content-Type'a körü körüne güvenmek doğru değildir. Uygulamanın izin verdiği liste dışındaki bir türü inert application/octet-stream olarak ve Content-Disposition: attachment ile servis edin. Bu, beklenmedik text/html içeriğinin tarayıcıda aktif belge gibi yorumlanma riskini azaltır.

Değişmeyen dosya anahtarları kullanıyorsanız görseller için uzun süreli cache header'ı verilebilir:

Cache-Control: public, max-age=31536000, immutable

Aynı key'in içeriğini sonradan değiştirmeyin. Yeni sürüm için yeni key üretmek, hem CDN hem tarayıcı cache'inin güvenli biçimde kullanılmasını sağlar.

Production ayarları

MinIO endpoint'i, SSL tercihi, bucket adı ve erişim anahtarları environment değişkenlerinde olmalıdır. Bu değerleri Docker image'a gömmeyin; deploy sırasında .env.production üzerinden verin. Uygulama ve storage altyapısının Docker ağında nasıl konumlandığını Docker Compose ile Next.js ve NestJS production deploy rehberinde bulabilirsiniz.

İyi dosya yükleme sistemi, yalnızca “upload başarılı” cevabı vermez: neyin yüklendiğini sınırlar, anahtarları öngörülemez kılar, bucket erişimini korur ve görselleri cache dostu biçimde sunar.