Amankan unggahan berkas dalam skala besar dengan signed URL, pemeriksaan tipe dan ukuran yang ketat, pemindaian malware asinkron, dan aturan izin yang tetap cepat saat lalu lintas tumbuh.

Unggahan berkas terlihat sederhana sampai pengguna nyata datang. Satu orang mengunggah foto profil. Lalu sepuluh ribu orang mengunggah PDF, video, dan spreadsheet secara bersamaan. Tiba-tiba aplikasi terasa lambat, biaya storage melonjak, dan tiket dukungan menumpuk.
Mode kegagalan umum bisa diprediksi. Halaman unggah macet atau timeout ketika server Anda mencoba menangani seluruh file alih-alih membiarkan object storage melakukan pekerjaan berat. Izin bergeser, sehingga seseorang menebak URL berkas dan melihat sesuatu yang seharusnya tidak mereka lihat. Berkas yang tampak “tidak berbahaya” datang dengan malware, atau dengan format rumit yang membuat alat downstream crash. Dan log tidak lengkap, jadi Anda tidak bisa menjawab pertanyaan dasar seperti siapa yang mengunggah apa dan kapan.
Yang Anda butuhkan sebenarnya membosankan tapi dapat diandalkan: unggahan cepat, aturan yang jelas (tipe dan ukuran yang diizinkan), dan jejak audit yang membuat insiden mudah diselidiki.
Perdagangan paling sulit adalah kecepatan vs keamanan. Jika Anda menjalankan setiap pemeriksaan sebelum pengguna selesai, mereka menunggu dan mencoba ulang, yang meningkatkan beban. Jika Anda menunda pemeriksaan terlalu lama, berkas yang tidak aman atau tidak berizin bisa tersebar sebelum Anda menangkapnya. Pendekatan praktis adalah memisahkan unggahan dari pemeriksaan, dan menjaga setiap langkah cepat dan terukur.
Juga tentukan secara spesifik apa yang Anda maksud dengan “skala.” Tulis angkanya: berkas per hari, puncak unggahan per menit, ukuran file maksimal, dan di mana pengguna Anda berada. Wilayah (region) berpengaruh untuk latensi dan aturan privasi.
Jika Anda membangun aplikasi di platform seperti Koder.ai, membantu untuk memutuskan batasan ini sejak awal, karena mereka membentuk cara Anda merancang izin, storage, dan alur kerja pemindaian latar.
Sebelum memilih alat, jelasakan apa yang bisa salah. Model ancaman tidak perlu menjadi dokumen besar. Cukup pemahaman singkat bersama tentang apa yang harus dicegah, apa yang bisa dideteksi belakangan, dan tradeoff yang Anda terima.
Penyerang biasanya mencoba menyelinap melalui beberapa titik yang bisa diprediksi: client (mengubah metadata atau memalsukan MIME type), batas jaringan (replay dan penyalahgunaan rate-limit), storage (menebak nama objek, menimpa), dan unduh/pratinjau (men-trigger rendering berisiko atau mencuri berkas lewat akses bersama).
Dari situ, peta ancaman ke kontrol sederhana:
Berkas berukuran berlebih adalah penyalahgunaan termudah. Mereka bisa membengkakkan biaya dan memperlambat pengguna nyata. Hentikan lebih awal dengan batas byte keras dan penolakan cepat.
Tipe berkas palsu berikutnya. Berkas bernama invoice.pdf mungkin sesuatu yang lain. Jangan percaya ekstensi atau pemeriksaan UI. Verifikasi berdasarkan byte nyata setelah unggahan.
Malware berbeda. Biasanya Anda tidak bisa memindai semuanya sebelum unggahan selesai tanpa membuat pengalaman menyakitkan. Pola umum adalah mendeteksi secara asinkron, mengarantina item mencurigakan, dan memblokir akses sampai pemindaian lulus.
Akses tidak berizin seringkali yang paling merusak. Perlakukan setiap unggahan dan setiap unduhan sebagai keputusan izin. Seorang pengguna seharusnya hanya boleh mengunggah ke lokasi yang mereka miliki (atau diizinkan untuk menulis), dan hanya mengunduh berkas yang mereka boleh lihat.
Untuk banyak aplikasi, kebijakan v1 yang solid adalah:
Cara tercepat menangani unggahan adalah menjauhkan server aplikasi dari “bisnis byte.” Alih-alih mengirim setiap file melalui backend Anda, biarkan client mengunggah langsung ke object storage menggunakan signed URL berumur pendek. Backend Anda tetap fokus pada keputusan dan catatan, bukan mendorong gigabyte.
Pembagian tugas sederhana: backend menjawab “siapa yang bisa mengunggah apa, dan ke mana,” sementara storage menerima data file. Ini menghilangkan bottleneck umum: server aplikasi melakukan kerja ganda (auth plus mem-proxy file) dan kehabisan CPU, memori, atau jaringan saat beban tinggi.
Simpan catatan unggahan kecil di database Anda (misalnya PostgreSQL) sehingga setiap file punya pemilik yang jelas dan siklus hidup yang jelas. Buat catatan ini sebelum unggahan dimulai, lalu perbarui saat peristiwa terjadi.
Bidang yang biasanya berguna meliputi identifier owner dan tenant/workspace, kunci objek storage, status, ukuran dan MIME type yang diklaim, serta checksum yang bisa Anda verifikasi.
Perlakukan unggahan seperti mesin status sehingga pemeriksaan izin tetap benar bahkan saat retry terjadi.
Set status praktis adalah:
Hanya izinkan client memakai signed URL setelah backend membuat catatan requested. Setelah storage mengonfirmasi unggahan, pindahkan ke uploaded, mulai pemindaian malware di latar, dan hanya ekspos berkas setelah approved.
Mulai ketika pengguna menekan Upload. Aplikasi Anda memanggil backend untuk memulai unggahan dengan detail dasar seperti nama file, ukuran file, dan penggunaan yang dimaksud (avatar, invoice, attachment). Backend memeriksa izin untuk target spesifik itu, membuat catatan unggahan, dan mengembalikan signed URL berumur singkat.
Signed URL harus dibatasi sempit. Idealnya hanya mengizinkan satu unggahan ke satu object key yang persis, dengan kedaluwarsa singkat dan kondisi jelas (batas ukuran, tipe konten yang diizinkan, checksum opsional).
Browser mengunggah langsung ke storage menggunakan URL tersebut. Saat selesai, browser memanggil backend lagi untuk finalisasi. Saat finalize, periksa kembali izin (pengguna bisa kehilangan akses), dan verifikasi apa yang benar-benar mendarat di storage: ukuran, tipe konten yang terdeteksi, dan checksum jika Anda menggunakannya. Buat finalize idempotent sehingga retry tidak membuat duplikasi.
Lalu tandai catatan sebagai uploaded dan picu pemindaian di latar (queue/job). UI bisa menampilkan “Processing” sementara pemindaian berjalan.
Mengandalkan ekstensi adalah bagaimana invoice.pdf.exe berakhir di bucket Anda. Perlakukan validasi sebagai rangkaian pemeriksaan yang dapat diulang dan terjadi di lebih dari satu tempat.
Mulai dengan batas ukuran. Masukkan ukuran maksimum ke kebijakan signed URL (atau kondisi pre-signed POST) sehingga storage bisa menolak unggahan berlebih lebih awal. Terapkan batas yang sama lagi ketika backend Anda mencatat metadata, karena client masih bisa mencoba melewati UI.
Pemeriksaan tipe harus berbasis konten, bukan nama file. Inspeksi byte pertama file (magic bytes) untuk memastikan cocok dengan yang Anda harapkan. PDF asli dimulai dengan %PDF, dan file PNG diawali dengan tanda tangan tetap. Jika konten tidak cocok dengan allowlist Anda, tolak meski ekstensi terlihat benar.
Jaga allowlist spesifik untuk tiap fitur. Unggahan avatar mungkin hanya mengizinkan JPEG dan PNG. Fitur dokumen mungkin mengizinkan PDF dan DOCX. Ini mengurangi risiko dan membuat aturan lebih mudah dijelaskan.
Jangan pernah percaya nama file asli sebagai storage key. Normalisasi untuk tampilan (hapus karakter aneh, potong panjang), tapi simpan object key aman sendiri, seperti UUID ditambah ekstensi yang Anda tetapkan setelah deteksi tipe.
Simpan checksum (misalnya SHA-256) di database Anda dan bandingkan nanti saat pemrosesan atau pemindaian. Ini membantu menangkap kerusakan, unggahan parsial, atau manipulasi, terutama saat unggahan dicoba ulang di bawah beban.
Pemindaian malware penting, tapi tidak harus berada di jalur kritis. Terima unggahan dengan cepat, lalu perlakukan file sebagai diblokir sampai lulus pemindaian.
Buat catatan unggahan dengan status seperti pending_scan. UI bisa menampilkan berkas, tetapi tidak boleh digunakan dulu.
Pemindaian biasanya dipicu oleh event storage saat objek dibuat, dengan menerbitkan job ke queue segera setelah selesai unggahan, atau melakukan keduanya (queue plus event storage sebagai cadangan).
Worker pemindaian mengunduh atau men-stream objek, menjalankan scanner, lalu menulis hasilnya kembali ke database Anda. Simpan yang esensial: status scan, versi scanner, timestamp, dan siapa yang meminta unggahan. Jejak audit itu membuat dukungan jauh lebih mudah ketika seseorang bertanya, “Mengapa berkas saya diblokir?”
Jangan biarkan file gagal bercampur dengan yang bersih. Pilih satu kebijakan dan terapkan konsisten: karantina dan cabut akses, atau hapus jika Anda tidak memerlukannya untuk investigasi.
Apa pun pilihan Anda, sampaikan pesan ke pengguna dengan tenang dan spesifik. Beri tahu apa yang terjadi dan langkah selanjutnya (unggah ulang, hubungi dukungan). Beri tahu tim Anda jika banyak kegagalan terjadi dalam waktu singkat.
Yang paling penting, tetapkan aturan ketat untuk unduhan dan pratinjau: hanya file yang ditandai approved yang boleh disajikan. Yang lain mengembalikan respons aman seperti “File sedang diperiksa.”
Unggahan cepat bagus, tapi jika orang yang salah bisa melampirkan file ke workspace yang salah, Anda punya masalah lebih besar daripada request lambat. Aturan paling sederhana juga paling kuat: setiap catatan berkas milik tepat satu tenant (workspace/org/project) dan punya pemilik atau pembuat yang jelas.
Lakukan pemeriksaan izin dua kali: saat Anda menerbitkan signed upload URL, dan lagi saat seseorang mencoba mengunduh atau melihat berkas. Pemeriksaan pertama menghentikan unggahan tidak berizin. Pemeriksaan kedua melindungi Anda jika akses dicabut, URL bocor, atau peran pengguna berubah setelah unggahan.
Least privilege menjaga prediktabilitas keamanan dan performa. Alih-alih satu izin luas “files”, pisahkan peran seperti “can upload,” “can view,” dan “can manage (delete/share).” Banyak permintaan kemudian menjadi lookup cepat (user, tenant, action) alih-alih logika kustom yang mahal.
Untuk mencegah penebakan ID, hindari ID berurutan di URL dan API. Gunakan identifier buram dan jaga kunci storage tak bisa ditebak. Signed URL adalah transport, bukan sistem izin Anda.
Berkas bersama seringkali membuat sistem menjadi lambat dan berantakan. Perlakukan sharing sebagai data eksplisit, bukan akses implisit. Pendekatan sederhana adalah catatan sharing terpisah yang memberi user atau grup izin ke satu file, dengan opsi kadaluarsa.
Saat orang berbicara tentang menskalakan unggahan aman, mereka sering fokus pada pemeriksaan keamanan dan lupa dasar: memindahkan byte adalah bagian yang lambat. Tujuannya adalah menjaga trafik file besar tetap jauh dari server aplikasi Anda, kendalikan retry, dan hindari mengubah pemeriksaan keamanan menjadi antrean tanpa batas.
Untuk file besar, gunakan unggahan multipart atau chunked sehingga koneksi goyah tidak memaksa pengguna mulai dari nol. Chunk juga membantu Anda menegakkan batas yang lebih jelas: total maksimum, ukuran chunk maksimum, dan waktu unggah maksimum.
Atur timeout dan retry di sisi client dengan sengaja. Beberapa retry membantu pengguna nyata; retry tanpa batas bisa membengkakkan biaya, terutama di jaringan seluler. Targetkan timeout per-chunk singkat, batas retry kecil, dan deadline keras untuk keseluruhan unggahan.
Signed URL menjaga jalur data berat tetap cepat, tapi request yang membuatnya tetap menjadi titik panas. Lindungi agar tetap responsif:
Latensi juga bergantung pada geografi. Tempatkan app, storage, dan worker pemindaian di region yang sama bila memungkinkan. Jika Anda membutuhkan hosting berbasis negara untuk kepatuhan, rencanakan routing sejak awal agar unggahan tidak bolak-balik antar benua. Platform yang berjalan di AWS secara global (seperti Koder.ai) dapat menempatkan beban kerja lebih dekat ke pengguna saat residensi data penting.
Akhirnya, rencanakan unduhan, bukan hanya unggahan. Sajikan berkas dengan signed download URL dan tetapkan aturan cache berdasarkan tipe file dan level privasi. Aset publik bisa di-cache lebih lama; bukti privat sebaiknya berumur pendek dan selalu diperiksa izin.
Bayangkan aplikasi usaha kecil di mana karyawan mengunggah faktur dan foto kuitansi, dan manajer menyetujuinya untuk reimburstment. Di sinilah desain unggahan berhenti menjadi akademis: Anda punya banyak pengguna, gambar besar, dan uang nyata terlibat.
Alur yang baik menggunakan status yang jelas sehingga semua orang tahu apa yang terjadi dan Anda bisa mengotomatisasi bagian membosankan: file mendarat di object storage dan Anda menyimpan catatan terkait user/workspace/expense; pekerjaan latar memindai file dan mengekstrak metadata dasar (seperti MIME type nyata); lalu item disetujui dan dapat dipakai di laporan, atau ditolak dan diblokir.
Pengguna butuh umpan balik cepat dan spesifik. Jika file terlalu besar, tunjukkan batas dan ukuran saat ini (misal: “File 18 MB. Maks 10 MB.”). Jika tipe salah, beri tahu apa yang diizinkan (“Unggah PDF, JPG, atau PNG”). Jika pemindaian gagal, tetap tenang dan beri tindakan (“File ini mungkin tidak aman. Silakan unggah salinan baru.”).
Tim dukungan butuh jejak yang membantu mereka debug tanpa membuka file: upload ID, user ID, workspace ID, timestamp untuk created/uploaded/scan started/scan finished, kode hasil (terlalu besar, tipe tidak cocok, scan gagal, izin ditolak), plus storage key dan checksum.
Unggahan ulang dan penggantian umum terjadi. Perlakukan itu sebagai unggahan baru, kaitkan ke expense yang sama sebagai versi baru, simpan riwayat (siapa mengganti dan kapan), dan tandai hanya versi terbaru sebagai aktif. Jika Anda membangun aplikasi ini di Koder.ai, ini peta yang rapi ke tabel uploads plus table expense_attachments dengan field versi.
Kebanyakan bug unggahan bukan trik rumit. Mereka adalah jalan pintas kecil yang diam-diam berubah menjadi risiko nyata saat trafik bertambah.
Lebih banyak pemeriksaan tidak harus membuat unggahan lambat. Pisahkan jalur cepat dari jalur berat.
Lakukan pemeriksaan cepat secara sinkron (auth, ukuran, tipe yang diizinkan, rate limit), lalu serahkan pemindaian dan inspeksi mendalam ke worker latar. Pengguna bisa terus bekerja sementara file berpindah dari “uploaded” ke “ready.” Jika Anda membangun dengan builder berbasis chat seperti Koder.ai, jaga mindset yang sama: buat endpoint unggah kecil dan ketat, dan dorong pemindaian serta pemrosesan pasca ke job.
Sebelum Anda meluncurkan unggahan, definisikan apa yang dimaksud dengan “cukup aman untuk v1”. Tim biasanya tersesat karena mencampur aturan ketat (yang memblokir pengguna nyata) dengan aturan yang hilang (yang mengundang penyalahgunaan). Mulai kecil, tapi pastikan setiap unggahan punya jalur jelas dari “diterima” ke “diizinkan untuk diunduh.”
Checklist pra-launch singkat:
Jika Anda butuh kebijakan minimum yang layak, buat sederhana: batas ukuran, allowlist tipe yang sempit, unggahan signed URL, dan “karantina sampai scan lulus.” Tambahkan fitur lebih lanjut nanti (pratinjau, lebih banyak tipe, reprocessing latar) setelah jalur inti stabil.
Monitoring adalah yang menjaga “cepat” tidak berubah jadi “entah kenapa lambat” saat Anda tumbuh. Lacak rasio kegagalan unggahan (client vs server/storage), rasio kegagalan scan dan latensi scan, waktu unggah rata-rata per bucket ukuran file, penolakan otorisasi saat unduh, dan pola egress storage.
Jalankan tes beban kecil dengan ukuran file realistis dan jaringan dunia nyata (data mobile berperilaku berbeda dari Wi‑Fi kantor). Perbaiki timeout dan retry sebelum peluncuran.
Jika Anda mengimplementasikan ini di Koder.ai (koder.ai), Planning Mode adalah tempat praktis untuk memetakan status unggahan dan endpoint terlebih dahulu, lalu menghasilkan backend dan UI di sekitar alur itu. Snapshot dan rollback juga membantu saat Anda menyetel batas atau menyesuaikan aturan scan.
Gunakan unggahan langsung ke object storage dengan signed URL berumur singkat sehingga server aplikasi Anda tidak perlu men-stream byte berkas. Biarkan backend fokus pada keputusan otorisasi dan pencatatan status unggahan, bukan memindahkan gigabyte.
Periksa dua kali: saat Anda membuat unggahan dan menerbitkan signed URL, lalu lagi saat Anda memfinalisasi dan saat menyajikan unduhan. Signed URL hanyalah mekanisme transport; aplikasi Anda tetap membutuhkan pemeriksaan izin yang terkait dengan catatan berkas dan tenant/workspace.
Perlakukan sebagai mesin status sehingga retry dan kegagalan parsial tidak menciptakan celah keamanan. Alur umum: requested, uploaded, scanned, approved, rejected — dan hanya izinkan unduhan ketika statusnya approved.
Masukkan batas byte keras ke kebijakan signed URL (atau kondisi pre-signed POST) sehingga storage menolak file berukuran berlebih lebih awal. Terapkan batas yang sama lagi saat finalize menggunakan metadata yang dilaporkan storage agar client tidak bisa melewatinya.
Jangan percaya ekstensi nama file atau MIME type dari browser. Deteksi tipe dari byte aktual file setelah unggah (misalnya magic bytes) dan cocokkan dengan allowlist yang ketat untuk fitur tersebut.
Jangan memblokir pengguna sambil menunggu pemindaian. Terima unggahan dengan cepat, karantina, jalankan pemindaian di latar, dan hanya izinkan unduhan/pratinjau setelah hasil pemindaian bersih tercatat.
Pilih kebijakan yang konsisten: karantina dan cabut akses, atau hapus jika Anda tidak memerlukan berkas untuk investigasi. Beri pengguna pesan yang tenang dan spesifik, dan simpan data audit sehingga dukungan bisa menjelaskan apa yang terjadi tanpa membuka berkas.
Jangan gunakan nama file atau path yang diberikan pengguna sebagai storage key. Hasilkan object key yang tidak dapat ditebak (misalnya UUID) dan simpan nama asli hanya sebagai metadata tampil setelah dinormalisasi.
Gunakan unggahan multipart atau chunked sehingga koneksi yang tidak stabil tidak memaksa pengguna mulai dari nol. Batasi retry, atur timeout dengan sengaja, dan tetapkan batas akhir untuk keseluruhan unggahan agar satu klien tidak mengikat sumber daya tanpa batas.
Gunakan catatan unggahan kecil berisi owner, tenant/workspace, object key, status, timestamp, tipe yang terdeteksi, ukuran, dan checksum jika Anda menggunakannya. Jika Anda membangun di Koder.ai, ini cocok dengan backend Go, tabel PostgreSQL untuk unggahan, dan pekerjaan latar untuk pemindaian sambil menjaga UI responsif.