Unggah berkas aman dalam skala besar: URL bertanda tangan dan validasi
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.

Apa yang membuat unggahan berkas sulit dalam skala besar
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.
Model ancaman sederhana untuk unggahan
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:
- Tegakkan ukuran maksimum dan kategori yang diizinkan (gambar, PDF, dll.)
- Verifikasi tipe berkas nyata di server setelah unggahan
- Pindai secara asinkron dan karantina hingga bersih
- Minta otorisasi eksplisit untuk unggah dan unduh
- Log dan beri alert pada kegagalan berulang (ukuran, tipe, auth)
Arsitektur unggahan praktis yang tetap cepat
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.
Komponen minimal yang bergerak
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.
Rencanakan status unggahan sejak awal
Perlakukan unggahan seperti mesin status sehingga pemeriksaan izin tetap benar bahkan saat retry terjadi.
Set status praktis adalah:
- requested
- uploaded
- scanned
- approved
- rejected
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.
Langkah demi langkah: unggahan signed URL tanpa bottleneck
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.
Validasi tipe dan ukuran yang bisa Anda percayai
Apa yang divalidasi, dan di mana
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.
Checksum dan nama file
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 tanpa membuat pengguna menunggu
Pemindaian malware penting, tapi tidak harus berada di jalur kritis. Terima unggahan dengan cepat, lalu perlakukan file sebagai diblokir sampai lulus pemindaian.
Pola asinkron
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?”
Apa yang terjadi saat file gagal
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.”
Pemeriksaan izin yang tetap benar di bawah beban
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.
Menjaga unggahan tetap cepat saat trafik dan ukuran file tumbuh
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.
Buat file besar terprediksi
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.
Kendalikan langkah “create upload”
Signed URL menjaga jalur data berat tetap cepat, tapi request yang membuatnya tetap menjadi titik panas. Lindungi agar tetap responsif:
- Rate-limit "create upload" per user dan per IP
- Tegakkan batas ukuran sebelum mengeluarkan signed URL
- Gunakan TTL singkat sehingga URL yang tidak dipakai cepat kedaluwarsa
- Lacak unggahan yang sedang berlangsung sehingga satu user tidak memulai ratusan sekaligus
- Gunakan idempotency keys sehingga refresh tidak membuat unggahan duplikat
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.
Contoh skenario: faktur dan kuitansi di aplikasi multi-user
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.
Kesalahan umum dan perbaikan mudah
Kebanyakan bug unggahan bukan trik rumit. Mereka adalah jalan pintas kecil yang diam-diam berubah menjadi risiko nyata saat trafik bertambah.
Lima kesalahan yang paling sering muncul
- Mengandalkan pemeriksaan sisi klien saja. Perbaikan: validasi lagi di server menggunakan byte nyata (magic bytes) dan tegakkan batas ukuran menggunakan metadata storage, bukan hanya laporan browser.
- Membuat signed URL berumur panjang. Perbaikan: buat singkat (menit), bersifat single-purpose, dan scoped ke satu object key. Rotasi kredensial dan log setiap penerbitan.
- Membiarkan file bisa diunduh sebelum pemindaian selesai. Perbaikan: unggah ke lokasi karantina, pindai asinkron, lalu promosikan atau sajikan setelah hasil bersih.
- Menggunakan nama atau path yang diberikan user sebagai storage key. Perbaikan: hasilkan object key Anda sendiri (UUID) dan simpan filename asli sebagai metadata tampilan.
- Melewatkan pemeriksaan izin saat unduh. Perbaikan: perlakukan unduh sebagai keputusan terpisah dan periksa kembali kepemilikan, keanggotaan workspace, dan aturan sharing setiap kali Anda membuat download URL.
Perbaikan mudah yang mencegah bottleneck
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.
Checklist cepat dan langkah selanjutnya
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:
- Tegakkan batas ukuran keras sejak awal (sebelum biaya storage membesar)
- Gunakan allowlist tipe berkas, divalidasi oleh konten (magic bytes), bukan hanya nama file
- Batasi akses sampai pemindaian selesai: jangan sajikan berkas ke orang lain sampai dipindai
- Minta pemeriksaan otorisasi unduh setiap kali
- Simpan log audit untuk unggahan, hasil scan, dan percobaan unduh
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.
Pertanyaan umum
Apa cara tercepat untuk menangani unggahan tanpa membebani backend saya?
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.
Apakah saya benar-benar perlu memeriksa izin lebih dari sekali?
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.
Status unggahan apa yang harus saya lacak di database?
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.
Di mana saya harus menegakkan batas ukuran maksimum: client, backend, atau storage?
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.
Bagaimana cara mencegah tipe berkas palsu seperti “invoice.pdf.exe”?
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.
Bagaimana saya bisa menambahkan pemindaian malware tanpa membuat unggahan terasa lambat?
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.
Apa yang harus saya lakukan ketika sebuah file gagal dipindai?
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.
Bagaimana sebaiknya saya menamai berkas di storage untuk menghindari penebakan dan tabrakan?
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.
Bagaimana menjaga unggahan besar tetap andal di jaringan mobile atau yang fluktuatif?
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.
Data minimum apa yang harus saya log untuk men-debug masalah unggahan nanti?
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.