6 dk

Güvenli dosya yüklemeleri: izinler, sınırlar, imzalı URL’ler, taramalar

Web uygulamalarında güvenli dosya yüklemeleri için sıkı izinler, boyut sınırları, imzalı URL’ler ve temel kötü amaçlı yazılım tarama desenleri gereklidir.

Güvenli dosya yüklemeleri: izinler, sınırlar, imzalı URL’ler, taramalar

Neden dosya yüklemeleri risklidir (basit dille)

Dosya yüklemeler masum görünebilir: bir profil fotoğrafı, bir PDF, bir tablo. Ancak sıklıkla ilk güvenlik olayı olurlar çünkü yabancıların sisteminize gizemli bir kutu göndermesine izin verirler. Kabul ederseniz, depolarsanız ve başkalarına gösterirseniz, uygulamanıza yeni bir saldırı yolu açmış olursunuz.

Risk sadece “birisi virüs yükledi” değil. Kötü bir yükleme özel dosyaları sızdırabilir, depolama faturanızı uçurabilir veya kullanıcıları erişim vermeye ikna edebilir. “invoice.pdf” adlı bir dosya aslında PDF olmayabilir. Gerçek PDF ve resimler bile, uygulamanız meta verilere güveniyorsa, önizlemeyi otomatik oluşturuyorsa veya yanlış kurallarla sunuyorsa sorun çıkarabilir.

Gerçek hatalar genellikle şöyle görünür:

  • Birisi bir dosya URL’sini tahmin eder ve başka bir kullanıcının belgesini indirir.
  • Yüklenen bir HTML dosyası web sayfası gibi sunulur ve giriş çalma amaçlı bir pencere gösterir.
  • Bir saldırgan tekrar tekrar devasa dosyalar yükleyerek uygulamanızı yavaşlatır veya çökertir.
  • “Güvenli” bir dosya türü sahtelenir ve personel tarafından iç ağda açılır.

Bir detay birçok olayı tetikler: dosyaları depolamak, dosyaları sunmakla aynı şey değildir. Depolama baytları tuttuğunuz yerdir. Sunma, bu baytların tarayıcılara ve uygulamalara nasıl teslim edildiğidir. İşler ters gittiğinde, uygulama kullanıcı yüklemelerini ana siteyle aynı güven düzeyi ve kurallarla sunar; bu durumda tarayıcı yüklemeyi "güvenilir" olarak kabul eder.

Küçük veya büyüyen bir uygulama için “yeterince güvenli” genellikle dört soruyu elinizde net yanıtla cevaplayabilmeniz demektir: kim yükleyebilir, ne kabul ediyorsunuz, ne kadar büyük/ne sıklıkta ve kim daha sonra okuyabilir. Hızla inşa ediyorsanız bile (üretilmiş kod veya sohbet tabanlı platformla), bu koruyucular önem taşır.

Yüklemeler için basit bir tehdit modeli

Her yüklemeyi güvensiz girdi olarak ele alın. Yüklemeleri güvenli tutmanın pratik yolu, kimin bunları kötüye kullanabileceğini ve onlar için “başarı”nın ne olduğunu görselleştirmektir.

Çoğu saldırgan ya zayıf yükleme forlarını tarayan botlardır ya da ücretsiz depolama, veri kazıma veya servis trolleme amacıyla sınırları zorlayan gerçek kullanıcılardır. Bazen de rakipler sızıntı veya kesintileri deniyor olabilir.

Ne istiyorlar? Genellikle şu sonuçlardan biri:

  • Sunucularınızda kod çalıştırmak için yürütülebilir bir şey yüklemek.
  • İndirme URL’lerini tahmin ederek, yeniden kullanarak veya paylaşarak özel dosyaları çalmak.
  • Yüklemelerle kullanılabilirliği bozmak veya pahalı işlem yükü yaratmak.
  • Depolama büyümesi veya bant genişliği ağırlıklı indirmelerle faturayı şişirmek.

Sonra zayıf noktaları haritalandırın. Yükleme uç noktası ön kapıdır (aşırı büyük dosyalar, tuhaf formatlar, yüksek istek oranları). Depolama arka odadır (açık bucket’lar, yanlış izinler, paylaşılan klasörler). İndirme URL’leri ise çıkıştır (öngörülebilir, uzun ömürlü veya kullanıcıyla bağlanmamış).

Örnek: bir “özgeçmiş yükleme” özelliği. Bir bot binlerce büyük PDF yükleyip maliyeti artırırken, kötü niyetli bir kullanıcı bir HTML dosyası yükler ve bunu “belge” olarak paylaşarak başkalarını kandırır.

Kontroller eklemeden önce, uygulamanız için en önemli olanı belirleyin: gizlilik (kim okuyabilir), kullanılabilirlik (sunmaya devam edebilir misiniz), maliyet (depolama ve bant genişliği) ve uyumluluk (verinin nerede saklandığı ve ne kadar süre tutulduğu). Bu öncelik listesi kararları tutarlı kılar.

Gerçekten işe yarayan izinler ve erişim kontrolü

Çoğu yükleme olayı karmaşık hack’ler değil. Bunlar basit “başkasının dosyasını görebiliyorum” hatalarıdır. İzinleri yüklemelerin bir parçası olarak ele alın, sonradan eklenen bir özellik gibi değil.

Bir kuralla başlayın: varsayılan reddet. Her yüklenen nesnenin özel olduğunu, açıkça izin verene kadar kabul edin. “Varsayılan olarak özel” fatura, sağlık dosyaları, hesap belgeleri ve kullanıcıya bağlı her şey için güçlü bir temel sağlar. Dosyaları yalnızca kullanıcı açıkça beklediğinde genel yapın (ör. herkese açık avatar) ve yine de zaman sınırlı erişimi düşünün.

Gerçek işleri yansıtan roller

Rolleri basit ve ayrı tutun. Yaygın bir ayrım:

  • Yükleyen: kendi hesabı için yükleme oluşturabilir
  • Görüntüleyici: izinli olduğu dosyaları indirebilir
  • Destek: yalnızca denetlenebilir, geçici bir yetkiyle dosyalara erişebilir
  • Yönetici: politikayı yönetir, ama otomatik olarak her şeyi okumamalı

/user-uploads/ gibi klasör düzeyinde kurallara güvenmeyin. Okuma zamanında her dosya için sahipliği veya tenant erişimini kontrol edin. Bu, birisi takım değiştirip ayrıldığında ya da dosya yeniden atandığında sizi korur.

İyi bir destek deseni dar ve geçicidir: belirli bir dosyaya erişim verin, kaydedin ve otomatik olarak süresi dolsun.

Güvenilirliğe dayanmadan dosya türünü doğrulama

Çoğu yükleme saldırısı basit bir numara ile başlar: dosya isme veya tarayıcı başlığına bakınca güvenli gibi görünür ama aslında farklıdır. İstemcinin gönderdiği her şeyi güvensiz kabul edin.

Bir allowlist ile başlayın: kabul ettiğiniz tam formatları belirleyin (örneğin .jpg, .png, .pdf) ve diğerlerini reddedin. “Herhangi bir resim” veya “herhangi bir belge” demekten kaçının, gerçekten ihtiyaç yoksa.

Dosya uzantısına veya istemciden gelen Content-Type başlığına güvenmeyin. İkisi de kolayca taklit edilebilir. invoice.pdf adındaki bir dosya yürütülebilir olabilir ve Content-Type: image/png yalancı olabilir.

Daha güçlü yöntem dosyanın ilk baytlarını incelemektir, buna genellikle “magic bytes” veya dosya imzası denir. Birçok formatın tutarlı başlıkları vardır (PNG ve JPEG gibi). Başlık izin verilenle eşleşmiyorsa reddedin.

Pratik bir doğrulama ayarı:

  • Sunucu tarafında kabul edilen uzantıların allowlist’i
  • İstemci başlığını değil, sunucuda MIME türü tespiti
  • Desteklenen formatlar için magic bytes kontrolü
  • Yeni rastgele bir depolama adı üretme ve orijinal adı metadata olarak saklama
  • HTML, SVG ve betik benzeri içerikleri gerekmedikçe engelleme

Yeniden adlandırma düşündüğünüzden daha önemlidir. Kullanıcı sağladığı adları doğrudan saklarsanız yol hileleri, garip karakterler ve kazara üzerine yazma riskini davet edersiniz. Depolama için üretilmiş bir kimlik kullanın ve orijinal dosya adını yalnızca görüntüleme için saklayın.

Profil fotoğrafları için yalnızca JPEG ve PNG kabul edin, başlıkları doğrulayın ve mümkünse meta verileri temizleyin. Belgeler için yalnızca PDF’ye izin vermeyi düşünün ve aktif içerik içerenleri reddedin. SVG veya HTML’e ihtiyaç duyarsanız, bunları potansiyel olarak yürütülebilir kabul edip izole edin.

Boyut sınırları, oran sınırlamaları ve DoS temelleri

Çoğu yükleme kesintisi “havalı hacker” taktiklerinden değil. Çok büyük dosyalar, çok fazla istek veya sunucuları meşgul eden yavaş bağlantılardır. Her baytı bir maliyet olarak değerlendirin.

Gerçekten işe yarayan boyut sınırları koyun

Özellik başına maksimum boyut seçin, tek bir genel sayı seçmeyin. Bir avatar vergi belgesi veya kısa video ile aynı limiti gerektirmez. Normal görünen en küçük limiti belirleyin, sonra gerçekten ihtiyaç duyduğunuzda ayrı bir “büyük yükleme” akışı ekleyin.

Sınırları birden fazla yerde uygulayın çünkü istemciler yalan söyleyebilir: uygulama mantığı, web sunucusu veya ters proxy, yükleme zaman aşımı ve beyan edilen boyut çok büyükse tam gövdeyi okumadan erken reddetme gibi.

Somut örnek: avatarlar 2 MB ile sınırlandırılsın, PDF’ler 20 MB ile sınırlandırılsın, daha büyük olanlar farklı bir yol gerektirsin (ör. imzalı URL ile doğrudan obje depolamaya).

Oran sınırlamaları ve kötüye kullanım kontrolleri

Küçük dosyalar bile bir döngüde birisi tarafından tekrar tekrar yüklenirse DoS olabilir. Yükleme uç noktalarına kullanıcı ve IP başına oran sınırlamaları ekleyin. Anonim trafik için giriş yapmış kullanıcılardan daha sıkı sınırlar düşünün.

Sürülebilir yüklemeler zayıf ağdaki gerçek kullanıcılara yardımcı olur, ancak oturum jetonu sıkı olmalıdır: kısa süreli, kullanıcıya bağlı ve belirli bir dosya boyutu ve hedefe bağlanmış. Aksi halde “resume” uç noktaları depolamaya ücretsiz bir hat olabilir.

Bir yüklemeyi engellediğinizde kullanıcıya açık hatalar döndürün (dosya çok büyük, çok fazla istek) ama iç ayrıntıları sızdırmayın (stack trace, bucket adları, satıcı detayları).

Güvenli depolama ve sunum tercihleri

Build for web and mobile
Create React, Go, and Flutter apps that share one upload policy.

Güvenli yüklemeler sadece ne kabul ettiğinizle ilgili değildir. Dosyanın nereye gittiği ve daha sonra nasıl geri verildiği de önemlidir.

Yükleme baytlarını ana veritabanınızın dışına çıkarın. Çoğu uygulama veritabanında sadece metadata (sahip kullanıcı ID’si, orijinal dosya adı, tespit edilen tür, boyut, checksum, depolama anahtarı, oluşturulma zamanı) tutar. Baytları büyük bloblar için tasarlanmış obje depolamada veya dosya servisinde saklayın.

Public ve private dosyaları depolama düzeyinde ayırın. Farklı kurallara sahip farklı bucket veya konteynerler kullanın. Genel dosyalar (ör. herkese açık avatarlar) giriş yapmadan okunabilir olabilir. Özel dosyalar (sözleşmeler, faturalar, tıbbi belgeler) asla herkese açık olmamalı, URL tahmin edilse bile okunamaz olmalıdır.

Mümkünse kullanıcı dosyalarını uygulamanızın ana domaininden sunmaktan kaçının. Riskli bir dosya (HTML, script içeren SVG veya tarayıcı MIME sniffing tuhaflıkları) atlanırsa, ana domain üzerinde barındırmak hesap ele geçirmeye yol açabilir. Ayrı bir indirme domaini (veya depolama domaini) patlama alanını sınırlar.

İndirmelerde güvenli başlıkları zorlayın. Kullanıcının iddia ettiğine değil, izin verdiğiniz şeylere göre öngörülebilir bir Content-Type ayarlayın. Tarayıcı tarafından yorumlanabilecek her şey için indirme olarak gönderin.

Sürprizleri önleyen birkaç varsayılan:

  • Belgeler için Content-Disposition: attachment kullanın.
  • Güvenli bir Content-Type kullanın (veya application/octet-stream).
  • Opaque nesne anahtarlarıyla saklayın ve sunun (kullanıcı dosya adları değil).
  • Özel dosya indirmelerini kaydedin (loglayın).

Tutma (retention) da güvenliktir. Terk edilmiş yüklemeleri silin, değiştirildikten sonra eski sürümleri kaldırın ve geçici dosyalar için zaman sınırları belirleyin. Az veri saklamak, sızabilecek veri miktarını azaltır.

İmzalı URL’ler: ne zaman kullanmalı ve nasıl sıkı tutmalı

İmzalı URL’ler (pre-signed URLs) kullanıcılara depolama bucket’ını herkese açık yapmadan veya her baytı API’nizden geçirmek zorunda kalmadan yükleme/indirme izni vermenin yaygın yoludur. URL geçici izin taşır ve sonra süresi dolar.

İki yaygın akış:

  • Doğrudan-depolamaya yükleme: uygulamanız kısa ömürlü imzalı bir URL verir ve tarayıcı doğrudan obje depolamaya yükler.
  • Sunucu-üzerinden-yükleme: dosya önce API’nize gelir, sonra sunucunuz depolar.

Doğrudan-depolamaya yükleme API yükünü azaltır, ancak depolama kuralları ve URL kısıtlamaları daha önemli hale gelir.

İmzalı URL’leri sıkı tutma

İmzalı URL’yi bir tek kullanımlık anahtar gibi ele alın. Spesifik ve kısa ömürlü yapın.

  • Yazma URL’lerinin süresini kısa tutun (genellikle 1–5 dakika).
  • Okuma URL’lerini dakikalar düzeyinde tutun, günler değil.
  • URL’yi beklenen nesne anahtarına bağlayın (bir nesne, bir klasör değil).
  • Desteklenen yerlerde ek kısıtlamalar ekleyin: beklenen içerik türü, maksimum boyut, checksum.
  • Yalnızca izin kontrollerinden sonra URL verin.
  • URL’yi kim istediğini ve neden istediğini kaydedin (kullanıcı ID, nesne anahtarı, amaç, IP/user agent).

Pratik bir desen: önce bir yükleme kaydı oluşturun (durum: pending), sonra imzalı URL’yi verin. Yüklemeden sonra nesnenin varlığını ve beklenen boyut/tür ile eşleştiğini doğrulamadan durumu hazır yapmayın.

Adım adım: uygulayabileceğiniz güvenli bir yükleme akışı

Keep data where it belongs
Choose where your app runs to match privacy and cross-border requirements.

Güvenli bir yükleme akışı esasen net kurallar ve net durumlar içerir. Her yüklemeyi kontrol edilene kadar güvensiz kabul edin.

Her özelliğin neleri kabul ettiğini yazın. Bir profil fotoğrafı ile vergi belgesi aynı dosya türleri, boyut sınırları veya görünürlük paylaşmamalıdır.

Pratik bir akış (gerçek durumlarla)

  1. İzin verilen türleri ve özellik başına boyut limitini tanımlayın (ör. fotoğraflar 5 MB’e kadar; PDF’ler 20 MB’e kadar). Aynı kuralları backend’de uygulayın.

  2. Baytlar gelmeden önce bir “yükleme kaydı” oluşturun. Saklayın: sahibi (kullanıcı veya organizasyon), amaç (avatar, fatura, eklenti), orijinal dosya adı, beklenen maksimum boyut ve pending gibi bir durum.

  3. Özel bir konuma yükleyin. İstemcinin son yolu seçmesine izin vermeyin.

  4. Sunucu tarafında tekrar doğrulayın: boyut, magic bytes/tür, allowlist. Geçerse durumu uploaded yapın.

  5. Kötü amaçlı yazılım taraması yapın ve durumu clean veya quarantined olarak güncelleyin. Tarama asenkron ise erişimi beklerken kilitleyin.

  6. Durum clean olduğunda indirme, önizleme veya işleme izin verin.

Küçük örnek: profil fotoğrafı için kullanıcıya bağlı bir avatar amacıyla bir kayıt oluşturun, özel olarak saklayın, gerçekten JPEG/PNG olduğunu (sadece isimle değil) doğrulayın, tarayın ve sonra bir önizleme URL’si oluşturun.

Temel kötü amaçlı yazılım tarama desenleri (abartmadan)

Kötü amaçlı yazılım taraması bir güvenlik ağıdır, garanti değil. Bilinen kötü dosyaları ve bariz numaraları yakalar ama her şeyi tespit edemez. Amaç basit: riski azaltmak ve bilinmeyen dosyaları varsayılan olarak zararsız hale getirmektir.

Güvenilir bir desen önce karantinadır. Yeni her yüklemeyi özel, halka açık olmayan bir konuma kaydedin ve onu bekleyen durumda işaretleyin. Kontroller geçildikten sonra “clean” konumuna taşıyın veya kullanılabilir olarak işaretleyin.

Senkron taramalar yalnızca küçük dosyalar ve düşük trafik için uygundur çünkü kullanıcı beklemek zorunda kalır. Çoğu uygulama asenkron tarar: yüklemeyi kabul eder, “işleniyor” durumunu döndürür, arka planda tarama yapar.

“Temel tarama” genellikle neleri içerir

Temel tarama tipik olarak bir antivirüs motoru (veya hizmet) artı birkaç koruma katmanıdır: AV taraması, dosya türü kontrolleri (magic bytes), arşiv sınırları (zip bombaları, iç içe geçmiş zip’ler, büyük açılmamış boyutlar) ve ihtiyaç duyulmayan formatları engelleme.

Tarayıcı başarısız olursa, zaman aşımına uğrarsa veya “bilinmiyor” dönerse dosyayı şüpheli kabul edin. Karantinada tutun ve indirme bağlantısı vermeyin. Burada ekipler yanar: “tarama başarısız” asla “yine de yayınla” olmamalıdır.

Bir dosyayı engellediğinizde mesajı nötr tutun: “Bu dosyayı kabul edemedik. Farklı bir dosya deneyin veya destek ile iletişime geçin.” Malware tespit ettiğinizi iddia etmeyin, emin değilseniz.

Örnek: tipik bir uygulamada profil fotoğrafı ve belge yükleme

Profil fotoğrafı (herkese açık gösterilen) ve PDF makbuz (özel, faturalama veya destek için) gibi iki özellik düşünün. Her ikisi de yükleme problemi ama aynı kuralları paylaşmamalı.

Profil fotoğrafı için katı olun: yalnızca JPEG/PNG izin verin, boyutu sınırlandırın (ör. 2–5 MB), sunucu tarafında yeniden kodlayın ki kullanıcı orijinal baytları doğrudan sunulmasın. Kontrollerden sonra kamuya açık depolamaya koyun.

PDF makbuzu için daha büyük boyuta izin verin (ör. 20 MB’e kadar), varsayılan olarak özel tutun ve ana uygulama domaininden inline olarak renderlamaktan kaçının.

Basit bir durum modeli kullanıcıyı içeriği açmadan bilgilendirir:

  • pending: kullanıcı dosyayı seçti, yükleme başlamadı
  • uploaded: depolama baytları aldı
  • scanning: arka plan işi dosyayı kontrol ediyor
  • clean (veya rejected): dosya kullanılabilir (veya engellendi)

İmzalı URL’ler burada iyi uyum sağlar: yazma için kısa ömürlü imzalı URL kullanın (yazma sadece bir nesne anahtarına), okuma için ayrı kısa ömürlü imzalı URL verin ve yalnızca durum clean olduğunda verin.

Araştırma için ihtiyaç duyduğunuz bilgileri loglayın, dosyanın kendisini değil: kullanıcı ID, dosya ID, tür tahmini, boyut, depolama anahtarı, zaman damgaları, tarama sonucu, istek ID’leri. Ham içerik veya belgelerdeki hassas verileri loglamaktan kaçının.

Yaygın hatalar ve kolay tuzaklar

Ship safer upload flows
Create upload endpoints with private defaults, type checks, and clean status states.

Çoğu yükleme hatası küçük bir “geçici” kısayolun kalıcı hale gelmesiyle oluşur. Her dosyanın güvensiz olduğunu, her URL’nin paylaşılacağını ve her “sonra düzeltilir” ayarının unutulacağını varsayın.

Tekrarlayan tuzaklar:

  • Yalnızca istemci tarafı kontrollerine güvenmek. Tarayıcılar saniyeler içinde atlatılabilir.
  • Kullanıcıların yolları, dosya adlarını veya obje anahtarlarını etkilemesine izin vermek.
  • Yüklemeleri “bir an için” genel yapmak.
  • Çok uzun süre yaşayan veya birden fazla kullanıcı için geçerli imzalı URL’ler kullanmak.
  • Dosyaları yanlış Content-Type ile sunmak, tarayıcının riskli içeriği yorumlamasına izin vermek.

Monitoring, ekiplerin atladığı tek şeydir ta ki depolama faturası patlayana kadar. Yükleme hacmini, ortalama boyutu, en çok yükleyenleri ve hata oranlarını takip edin. Bir ele geçirilmiş hesap gece boyunca sessizce binlerce büyük dosya yükleyebilir.

Örnek: bir takım avatarları kullanıcı tarafından verilen dosya adlarıyla “avatar.png” gibi paylaşılan bir klasörde saklar. Bir kullanıcı başkalarının resimlerini ezebilir. Düzeltme sıkıcı ama etkili: obje anahtarlarını sunucu tarafında üretin, yüklemeleri varsayılan olarak özel tutun ve yeniden boyutlandırılmış resmi kontrollü bir yanıtla sunun.

Hızlı kontrol listesi ve sonraki adımlar

Bunu göndermeden önce son kontrol olarak kullanın. Her öğeyi bir sürüm engelleyici olarak ele alın; çünkü çoğu olay bir eksik koruyucudan gelir.

Hızlı kontrol listesi

  • Sunucuda allowlist ile gerçek içerik kontrolleri (sadece dosya adı değil) ve dosya başına sert maksimum boyut doğrulayın.
  • Yüklemeleri varsayılan olarak özel saklayın ve bir dosya okunduğunda/indirildiğinde/önizlendiğinde izinleri her seferinde kontrol edin.
  • İmzalı URL yüklemeleri kullanıyorsanız, bunları kısa ömürlü, tek nesne anahtarına scoped ve kötüye kullanımı izleyebilmek için düzenli olarak loglayın.
  • Önce karantina, sonra tarama: dosya temizlenene kadar önizleme veya indirme oluşturmayın.
  • Güvenli indirme davranışını zorlayın: öngörülebilir Content-Type, güvenli dosya adları ve belgeler için attachment.

Fayda sağlayan sonraki adımlar

Kurallarınızı açık ve sade bir dille yazın: izin verilen türler, maksimum boyutlar, kim neye erişebilir, imzalı URL’lerin ne kadar yaşadığı ve “tarama geçti”nin ne anlama geldiği. Bu, ürün, mühendislik ve destek arasında paylaşılan bir sözleşme olur.

Aşırı büyük dosyalar, yeniden adlandırılmış yürütülebilirler, yetkisiz okumalar, süresi dolmuş imzalı URL’ler ve “tarama bekleniyor” indirmelerini yakalayacak birkaç test ekleyin. Bu testler bir olayla karşılaştırıldığında ucuzdur.

Hızla inşa edip yineleyen ekipler için, değişiklikleri planlayıp güvenle geri alabileceğiniz bir iş akışı kullanmak yardımcı olur. Ekipler Koder.ai (koder.ai) kullanırken genellikle planlama modu ve snapshot/rollback ile yükleme kurallarını sıkılaştırırken bu tür iş akışlarına dayanır, ancak temel gereksinim değişmez: politika backend tarafından uygulanır, UI değil.

SSS

What’s the minimum I should do to make file uploads “safe enough”?“

Start with private by default and treat every upload as untrusted input. Enforce four basics server-side:

  • Who can upload
  • What file types you accept (allowlist)
  • How big/how often (size + rate limits)
  • Who can read it later (per-file permission checks)

If you can answer those clearly, you’re already ahead of most incidents.

Why are file uploads a common first security incident?

Because users can upload a “mystery box” that your app stores and might later serve back to other users. That can lead to:

  • Unauthorized access to private documents
  • Phishing or account takeover if a file is served as trusted web content
  • Outages and high bills from upload floods or huge files

It’s rarely just “someone uploaded a virus.”

What’s the difference between storing files and serving files, and why does it matter?

Storing is keeping bytes somewhere. Serving is delivering those bytes to browsers and apps.

The danger is when your app serves user uploads with the same trust and rules as your main site. If a risky file is treated like a normal page, the browser may execute it (or users may trust it too much).

A safer default is: store privately, then serve through controlled download responses with safe headers.

How do I stop users from downloading someone else’s uploaded file?

Use default deny and check access every time a file is downloaded or previewed.

Practical rules:

  • Every file record should have an owner (user/org) and purpose (avatar, invoice, etc.)
  • On read/download, verify the requester is allowed for that specific file
  • Avoid “folder-based” security like “anything under /uploads/ is fine”
  • Keep support access temporary and logged (grant access to one file, expire it automatically)

Most real bugs are simple “I can see another user’s file” mistakes.

How do I validate file type without trusting the filename or Content-Type?

Don’t trust the filename extension or the browser’s Content-Type. Validate on the server:

  • Use an allowlist of formats per feature (e.g., JPEG/PNG for avatars, PDF for receipts)
  • Detect type server-side and check magic bytes (file signatures)
  • Rename files for storage using a random ID; keep the original name only as metadata
  • Block risky formats you don’t need (especially HTML, SVG, script-like content)

If the bytes don’t match an allowed format, reject the upload.

What limits should I set to prevent upload-based DoS attacks?

Because outages often come from boring abuse: too many uploads, huge files, or slow connections tying up server resources.

Defaults that work well:

  • Set per-feature max sizes (avatars small, documents bigger)
  • Enforce limits at multiple layers (app + reverse proxy + timeouts)
  • Add rate limits per user and per IP, with stricter limits for anonymous traffic

Treat every byte as cost and every request as potential abuse.

Should I use signed URLs for uploads, and what’s the safest default?

Yes, but do it carefully. Signed URLs let the browser upload/download directly from object storage without making the bucket public.

Good defaults:

  • Keep write URLs short-lived (often 1–5 minutes)
  • Scope each URL to one object key, not a folder
  • Issue URLs only after permission checks
  • Log who requested the URL and for what file

Direct-to-storage reduces API load, but it makes scoping and expiration non-negotiable.

What’s a safe step-by-step upload flow I can implement?

The safest pattern is:

  1. Create an upload record with status pending
  2. Upload bytes into a private location
  3. Validate size + type (magic bytes) server-side
  4. Scan (usually async) and move status to clean or quarantined
  5. Only allow download/preview when status is clean

This prevents “scan failed” or “still processing” files from being shared accidentally.

Do I really need malware scanning, and what does “basic scanning” look like?

Scanning helps, but it’s not a guarantee. Use it as a safety net, not your only control.

Practical approach:

  • Quarantine first: don’t expose links until scanning completes
  • Scan asynchronously for scale; show “processing” to the user
  • If scanning fails or times out, treat the file as suspicious and keep it blocked
  • Add guardrails for archives (zip bombs, huge uncompressed sizes) if you allow them

The key is policy: “not scanned” should never mean “available.”

How should I deliver uploaded files safely (headers, domains, downloads)?

Serve files in a way that prevents browsers from interpreting them as web pages.

Good defaults:

  • Set Content-Disposition: attachment for documents
  • Use a safe, server-chosen Content-Type (or application/octet-stream)
  • Use opaque storage keys (not user filenames) in URLs
  • Prefer a separate download domain for user content when possible

This reduces the risk that an uploaded file turns into a phishing page or script execution.

Related posts