Pada layanan generasi terrain berbasis job queue, masalah keamanan utamanya bukan hanya siapa yang bisa mengakses API, tetapi juga apa yang bisa mereka kirim, berapa sering mereka bisa memakainya, dan seberapa mahal pekerjaan yang mereka picu. Jika endpoint submit, render, atau export tidak dibatasi dengan benar, satu pengguna atau satu API key yang bocor bisa menghabiskan kapasitas GPU, memenuhi queue, menulis file berbahaya, atau mengekspor aset yang seharusnya tidak boleh diakses.

Artikel ini membahas hardening API terrain job untuk backend yang mirip layanan generasi terrain/open-world berbasis worker, terinspirasi konteks pipeline seperti InfiniteDiffusion tetapi fokus penuh pada keamanan aplikasi. Kita akan membahas desain auth, pilihan session vs token stateless, penyimpanan secret, validasi parameter generation, upload asset/heightmap yang aman, rate limit per user dan per API key, pencegahan abuse biaya, audit log, dan checklist implementasi.

Arsitektur minimum yang perlu diamankan

Sebelum masuk ke implementasi, definisikan permukaan serangan terlebih dahulu. Backend semacam ini biasanya memiliki komponen berikut:

  • API gateway / backend app: menerima request submit, status, cancel, render, export, upload.
  • Database: menyimpan user, API key, job, quota, audit log, metadata file.
  • Queue / broker: mendistribusikan job ke worker.
  • Worker CPU/GPU: menjalankan inferensi/generasi, konversi, render, export.
  • Object storage / file storage: menyimpan upload, hasil intermediate, hasil export.
  • Dashboard internal: untuk operator, support, atau tim konten.

Permukaan serangan utama biasanya ada di sini:

  • Endpoint yang dapat memicu job mahal.
  • Upload file yang dipakai worker.
  • Export file yang berpotensi membuka akses lintas tenant.
  • Token/API key yang bocor.
  • Queue starvation akibat spam atau payload berat.
  • Parameter generation yang tidak dibatasi.

Desain auth untuk endpoint submit, render, dan export

Pisahkan identitas, otorisasi, dan biaya

Untuk endpoint seperti POST /jobs/submit, POST /jobs/{id}/render, dan POST /jobs/{id}/export, jangan cukup berhenti pada verifikasi token. Backend perlu menjawab tiga pertanyaan:

  1. Siapa pemanggilnya? user, service account, atau dashboard internal.
  2. Apa yang boleh dia lakukan? submit job, lihat job sendiri, render ulang, export format tertentu.
  3. Berapa besar biaya yang boleh dia picu? batas rate, quota harian, dan kompleksitas job.

Ini penting karena banyak sistem aman di level auth, tetapi tetap boros dan mudah disalahgunakan karena authorization dan cost control longgar.

Endpoint publik vs internal

Praktik yang aman adalah memisahkan jalur penggunaan:

  • API eksternal untuk klien atau integrator: autentikasi dengan API key atau bearer token, rate limit ketat, izin sempit.
  • Dashboard internal untuk operator: login berbasis session, MFA jika memungkinkan, audit log lengkap.
  • Service-to-service untuk worker atau orchestrator: gunakan credential terpisah, bukan API key user.

Jangan gunakan token yang sama untuk semua aktor. Token worker yang hanya boleh mengklaim job tidak seharusnya bisa membuat job baru atas nama user.

Authorization berbasis resource ownership

Untuk job terrain, otorisasi minimal harus mencakup:

  • User hanya bisa membaca status job miliknya sendiri.
  • User hanya bisa meng-render atau meng-export job yang ia buat atau yang dibagikan kepadanya.
  • API key dibatasi ke tenant/project tertentu.
  • Dashboard internal bisa melakukan override, tetapi semua tindakan harus tercatat.

Kesalahan umum adalah hanya memeriksa bahwa token valid, lalu menerima job_id dari request tanpa mengecek kepemilikannya. Ini membuka akses IDOR (Insecure Direct Object Reference).

Session vs stateless token untuk dashboard internal

Kapan memakai session

Untuk dashboard internal yang diakses staf melalui browser, session-based auth biasanya lebih aman dan lebih mudah dikelola dibanding token stateless:

  • Lebih mudah dicabut secara server-side.
  • Cocok untuk browser dan proteksi CSRF.
  • Dapat dikaitkan ke device, IP, atau waktu idle.
  • Lebih baik untuk tindakan administratif berisiko tinggi.

Jika dashboard hanya dipakai internal, session dengan cookie HttpOnly, Secure, dan pembatasan origin umumnya pilihan yang baik.

Kapan memakai token stateless

Stateless token lebih cocok untuk API eksternal dan integrasi machine-to-machine karena:

  • Tidak bergantung pada session store.
  • Mudah dipakai oleh CLI, backend lain, atau automation.
  • Bisa memuat claim seperti tenant, scope, dan expiry.

Namun token stateless punya trade-off:

  • Pencabutan lebih sulit jika token berdurasi panjang.
  • Kesalahan pengaturan expiry bisa memperbesar risiko penyalahgunaan.
  • Jika token bocor, pelaku bisa memakainya sampai token kadaluarsa atau diblok via denylist.

Rekomendasi praktis

Untuk sistem terrain job:

  • Dashboard internal: gunakan session-based auth.
  • API eksternal: gunakan bearer token singkat atau API key dengan scope terbatas.
  • Worker internal: gunakan credential service account terpisah dan rotasi berkala.

Jika tim Anda kecil, hindari mencampur dashboard browser dan API publik dalam model auth yang sama. Pemisahan ini menyederhanakan hardening dan audit.

Desain token, API key, dan penyimpanan secret

Prinsip dasar secret management

Secret yang umum di sistem ini meliputi:

  • API key pengguna atau integrator.
  • Signing key untuk token.
  • Credential database, queue, object storage.
  • Credential worker GPU atau service account internal.

Prinsip yang sebaiknya diikuti:

  • Jangan hardcode secret di source code.
  • Jangan simpan secret penuh di log.
  • Gunakan secret manager atau environment yang terkelola dengan kontrol akses ketat.
  • Rotasi secret yang dipakai layanan machine-to-machine secara berkala.
  • Hash API key di database jika memungkinkan, bukan simpan plaintext.

Menyimpan API key dengan aman

Praktik umum yang baik:

  • Tampilkan API key penuh hanya saat dibuat.
  • Simpan hash dari API key di database.
  • Simpan prefix singkat untuk identifikasi di UI dan log, misalnya beberapa karakter awal.
  • Dukung revocation dan rolling key tanpa downtime.

Dengan pendekatan ini, kebocoran database tidak otomatis membocorkan API key aktif dalam bentuk yang siap pakai.

Scope token yang realistis

Jangan berikan izin serba bisa. Contoh scope yang lebih aman:

  • job:submit
  • job:read
  • job:cancel
  • render:create
  • export:create
  • upload:create

Jika perlu, tambahkan pembatasan level proyek atau tenant. Misalnya API key hanya boleh submit job untuk project tertentu dan tidak boleh export.

Validasi parameter generation untuk menekan abuse

Di layanan generasi terrain, parameter request hampir selalu bisa disalahgunakan untuk meningkatkan biaya komputasi. Validasi tidak boleh hanya memeriksa tipe data, tetapi juga batas operasional.

Parameter yang wajib dibatasi

Contoh parameter yang perlu validasi ketat:

  • Resolusi output atau ukuran tile.
  • Jumlah step/iterasi.
  • Jumlah seed atau variasi per request.
  • Jumlah chunk area atau ukuran world.
  • Format export dan tingkat detail.
  • Referensi asset atau heightmap input.

Gunakan allowlist dan rentang numerik yang eksplisit. Hindari pola "backend menerima apa saja lalu worker mencoba menjalankan".

Validasi semantik, bukan hanya skema

Request bisa lolos validasi JSON schema tetapi tetap berbahaya. Misalnya:

  • Resolusi valid secara format, tetapi terlalu tinggi untuk paket user.
  • Jumlah variasi valid, tetapi menghasilkan 100 sub-job.
  • Ekspor valid, tetapi mencoba format yang memicu pipeline mahal.

Karena itu, lakukan dua lapis validasi:

  1. Schema validation: tipe, field wajib, enum, rentang dasar.
  2. Policy validation: cek terhadap plan user, quota, dan estimasi biaya.

Contoh pseudo-code validasi submit job

function submitJob(request, actor) {
  requireAuth(actor)
  requireScope(actor, "job:submit")

  payload = validateSchema(request.body, {
    prompt: "string?",
    seed: "integer?",
    output_resolution: "integer",
    tile_count: "integer",
    step_count: "integer",
    export_format: "enum[obj, gltf, heightmap, png]",
    source_upload_id: "string?"
  })

  plan = loadPlan(actor.account_id)

  if (payload.output_resolution > plan.max_resolution) deny(422, "resolution too high")
  if (payload.tile_count > plan.max_tiles_per_job) deny(422, "tile count too high")
  if (payload.step_count > plan.max_steps) deny(422, "step count too high")

  estimatedCost = estimateJobCost(payload)
  if (estimatedCost > plan.max_cost_per_job) deny(422, "job cost too high")

  if (!quotaAvailable(actor, estimatedCost)) deny(429, "quota exceeded")

  if (payload.source_upload_id) {
    upload = findUploadOwnedByActor(payload.source_upload_id, actor)
    if (!upload) deny(403, "upload not accessible")
    if (upload.status != "scanned") deny(422, "upload not ready")
  }

  job = createJob({
    actor_id: actor.id,
    account_id: actor.account_id,
    status: "queued",
    payload: sanitizePayload(payload),
    estimated_cost: estimatedCost
  })

  enqueue(job.id)
  writeAuditLog(actor, "job.submit", job.id)
  return accepted(job)
}

Poin penting pada contoh di atas: validasi dilakukan sebelum enqueue, dan sistem sudah memperhitungkan biaya estimasi sejak awal.

Keamanan upload asset dan heightmap

Anggap semua upload tidak tepercaya

Heightmap, texture, mesh, atau file pendukung lain tidak boleh langsung dipercaya meskipun datang dari user sah. File bisa menyebabkan masalah seperti:

  • Ukuran file sangat besar dan menghabiskan storage.
  • File terkompresi yang meledak saat diekstrak.
  • MIME type palsu.
  • Format gambar atau model yang memicu parser crash.
  • Nama file dengan path traversal.
  • Konten yang membuat worker menulis ke lokasi tak terduga.

Praktik upload yang aman

  • Gunakan nama objek hasil generate server-side, jangan pakai nama file asli sebagai path penyimpanan.
  • Validasi ukuran maksimum file sebelum dan sesudah upload selesai.
  • Periksa tipe file berdasarkan inspeksi konten, bukan hanya extension atau header klien.
  • Simpan upload di bucket atau direktori non-eksekutabel.
  • Jika ada proses ekstraksi, batasi ukuran total, jumlah file, dan kedalaman path.
  • Lakukan scanning/validation pipeline sebelum file tersedia untuk worker.
  • Jangan biarkan worker membaca path arbitrer dari request.

Upload flow yang lebih aman

  1. Klien meminta upload session ke backend.
  2. Backend membuat metadata upload: owner, ukuran maksimum, tipe yang diizinkan, status pending.
  3. Klien mengunggah file ke lokasi sementara atau via URL terotorisasi singkat.
  4. Backend/processor memverifikasi ukuran, hash, tipe konten, dan hasil scanning.
  5. Jika lolos, status menjadi scanned atau ready.
  6. Baru setelah itu file boleh direferensikan pada endpoint submit/render.

Pola ini mencegah job langsung memakai file mentah yang belum diverifikasi.

Contoh pseudo-code validasi upload

function finalizeUpload(uploadId, actor) {
  upload = findUploadOwnedByActor(uploadId, actor)
  if (!upload) deny(404)

  meta = inspectStoredObject(upload.storage_key)

  if (meta.size_bytes > upload.max_size_bytes) {
    markUploadRejected(upload, "size limit exceeded")
    deny(422, "file too large")
  }

  detectedType = detectFileType(meta)
  if (!isAllowedType(detectedType, upload.allowed_types)) {
    markUploadRejected(upload, "invalid file type")
    deny(422, "invalid file type")
  }

  if (!scanResultClean(upload.storage_key)) {
    markUploadRejected(upload, "scan failed")
    deny(422, "unsafe upload")
  }

  markUploadReady(upload, detectedType)
  writeAuditLog(actor, "upload.finalize", upload.id)
  return ok(upload)
}

Rate limit per user dan per API key

Kenapa perlu lebih dari satu jenis limit

Untuk hardening API terrain job, satu rate limit global tidak cukup. Anda perlu kombinasi pembatasan berdasarkan:

  • User: mencegah satu akun membanjiri sistem.
  • API key: membatasi integrasi yang spesifik atau key yang bocor.
  • IP: berguna untuk endpoint sensitif seperti login atau upload initiation.
  • Tenant/account: mencegah beberapa key dalam satu akun menghabiskan semua kapasitas.
  • Concurrency: membatasi jumlah job aktif/queued per aktor.

Bedakan request rate dan cost rate

Kesalahan umum adalah hanya menghitung jumlah request. Pada sistem berbasis GPU, satu request bisa jauh lebih mahal daripada request lain. Karena itu, gunakan dua lapis:

  • Request rate limit: misalnya submit maksimum sejumlah request per jendela waktu.
  • Cost-based limit: total estimasi biaya atau total kapasitas komputasi per jendela waktu.

Dengan begitu, user tidak bisa mengakali sistem hanya dengan mengirim sedikit request tetapi semuanya sangat berat.

Contoh model limit yang berguna

  • Batas submit per menit per API key.
  • Batas job queued sekaligus per user.
  • Batas total estimasi GPU cost per jam per account.
  • Batas export besar per hari.
  • Batas upload total byte per hari.

Contoh pseudo-code rate limiting

function enforceLimits(actor, action, estimatedCost) {
  key1 = "rl:user:" + actor.user_id + ":" + action
  key2 = "rl:api_key:" + actor.api_key_id + ":" + action
  key3 = "quota:account:" + actor.account_id + ":cost_hour"

  if (!tokenBucketAllow(key1, 10, "1m")) deny(429, "user rate limit exceeded")
  if (actor.api_key_id && !tokenBucketAllow(key2, 30, "1m")) deny(429, "api key rate limit exceeded")
  if (!costWindowAllow(key3, estimatedCost, actor.plan.max_hourly_cost)) deny(429, "cost quota exceeded")

  activeJobs = countActiveJobs(actor.account_id)
  if (activeJobs >= actor.plan.max_concurrent_jobs) deny(429, "too many active jobs")
}

Secara implementasi, token bucket atau sliding window di Redis sering dipakai untuk pembatasan cepat. Yang penting bukan algoritmanya saja, tetapi pemilihan dimensi limit-nya.

Pencegahan abuse biaya GPU dan worker

Gunakan admission control sebelum enqueue

Worker mahal seharusnya tidak menjadi lapisan pertama yang menolak pekerjaan. API harus melakukan admission control sebelum job masuk queue:

  • Hitung estimasi biaya dari parameter.
  • Tolak job yang melebihi batas paket.
  • Tolak jika budget akun tidak cukup.
  • Terapkan pembatasan concurrency.
  • Gunakan priority queue bila ada kelas pengguna berbeda.

Ini lebih murah dibanding membiarkan queue terisi lalu gagal di worker.

Idempotency untuk submit dan export

Retry dari klien, timeout jaringan, atau refresh UI dapat menggandakan job. Endpoint mahal seperti submit dan export sebaiknya mendukung idempotency key. Dengan demikian request yang sama tidak membuat dua pekerjaan identik secara tidak sengaja.

Timeout, cancellation, dan janitor

Job terrain bisa gagal di tengah jalan atau worker bisa mati. Siapkan:

  • Timeout eksekusi worker.
  • Status job yang jelas: queued, running, succeeded, failed, cancelled.
  • Heartbeat worker untuk mendeteksi job yatim.
  • Proses janitor untuk merekonsiliasi job macet dan membersihkan artefak sementara.

Dari sisi keamanan dan biaya, ini mencegah job tak selesai mengunci kapasitas terus-menerus.

Contoh alur request yang aman

Alur submit terrain job

  1. Klien mengirim POST /jobs/submit dengan bearer token atau API key dan opsional Idempotency-Key.
  2. Backend melakukan autentikasi, cek scope, cek kepemilikan tenant/project.
  3. Backend memvalidasi schema payload.
  4. Backend menghitung estimasi biaya dan memeriksa quota/rate limit/concurrency.
  5. Jika ada referensi upload, backend memastikan upload milik user dan statusnya ready.
  6. Backend menyimpan job dengan payload yang sudah disanitasi.
  7. Backend menulis audit log dan enqueue job.
  8. Worker mengambil job dengan credential internal, bukan token user.
  9. Worker hanya membaca input dari storage key yang sudah dikeluarkan sistem, bukan path dari klien.
  10. Hasil ditulis ke storage terisolasi per tenant/job, lalu status job diperbarui.

Alur export yang aman

  1. Klien meminta export untuk job tertentu.
  2. Backend mengecek bahwa job milik user/tenant yang benar.
  3. Backend memvalidasi format export dan batas ukuran/kompleksitas.
  4. Jika export mahal, buat sub-job export terpisah dengan quota sendiri.
  5. Hasil export diberikan melalui URL sementara yang kadaluarsa singkat atau lewat endpoint download terotorisasi.

Hindari memberikan path storage internal langsung ke klien.

Tabel ancaman dan mitigasi

AncamanDampakMitigasi utama
API key bocorSubmit job tak sah, biaya GPU naikHash API key, scope sempit, expiry/rotation, rate limit per key, revoke cepat
IDOR pada job/exportAkses job tenant lainCek ownership/resource scope di setiap request, jangan percaya job_id saja
Spam submit jobQueue penuh, worker starvationRate limit per user/key/account, batas concurrency, admission control
Payload generation terlalu mahalBiaya komputasi melonjakBatas resolusi/step/tile, estimasi cost, quota per jam/hari
Upload berbahayaCrash parser, storage abuse, potensi RCE pada tool eksternalValidasi tipe nyata, ukuran maksimum, storage non-eksekutabel, scanning, sandbox parser
Retry klien membuat duplikasi jobBiaya ganda, hasil membingungkanIdempotency key untuk submit/export
Worker memakai path dari userPath traversal, baca file sensitifGunakan storage key internal, mapping metadata, jangan izinkan path arbitrer
Log menyimpan secretKebocoran credentialRedaksi token, simpan prefix saja, scrub header sensitif
Token internal terlalu luasLateral movement antar layananService account terpisah, least privilege, rotasi berkala
Job macet tidak dibersihkanKapasitas terkunci, biaya tak terlihatHeartbeat, timeout, janitor, rekonsiliasi status

Audit log yang berguna untuk insiden dan billing

Audit log bukan hanya untuk kepatuhan. Pada layanan terrain job, audit log penting untuk:

  • Investigasi siapa yang memicu lonjakan biaya.
  • Melacak export sensitif.
  • Menjelaskan kenapa job ditolak.
  • Membedakan bug sistem vs abuse pengguna.

Apa yang perlu dicatat

  • Actor: user id, account id, api key id, service account id.
  • Aksi: submit, cancel, render, export, upload finalize, key revoke.
  • Target: job id, upload id, export id.
  • Hasil: sukses, ditolak, gagal.
  • Alasan kebijakan: quota exceeded, invalid scope, ownership mismatch.
  • Metadata aman: estimasi cost, ukuran upload, format export.

Jangan simpan secret penuh, prompt sensitif lengkap, atau payload mentah jika tidak diperlukan. Log yang terlalu detail bisa menjadi sumber kebocoran baru.

Contoh format audit event

{
  "event": "job.submit",
  "actor_type": "api_key",
  "actor_id": "ak_123",
  "account_id": "acct_9",
  "target_id": "job_456",
  "result": "accepted",
  "estimated_cost": 12,
  "request_id": "req_abc",
  "timestamp": "2026-08-11T10:00:00Z"
}

Kesalahan implementasi yang sering terjadi

  • Semua endpoint pakai satu middleware auth yang sama, padahal kebutuhan submit, admin dashboard, dan worker internal berbeda.
  • Validasi hanya di frontend, sehingga klien bisa mengirim parameter berat langsung ke API.
  • Upload dianggap aman setelah berhasil disimpan, padahal file belum diverifikasi.
  • Rate limit hanya berdasarkan IP, yang tidak cukup untuk integrasi server-to-server.
  • Worker memproses input berdasarkan path dari payload, membuka peluang traversal dan akses file lokal.
  • Job di-enqueue sebelum cek quota, menyebabkan queue penuh oleh job yang akhirnya ditolak.
  • Log menyimpan Authorization header atau API key penuh.

Checklist implementasi hardening

  • Endpoint submit/render/export memeriksa auth, scope, ownership, dan quota.
  • Dashboard internal memakai session aman; API eksternal memakai token/API key terpisah.
  • API key disimpan dalam bentuk hash dan bisa dirotasi/dicabut.
  • Secret tidak ada di source code dan tidak ditulis utuh ke log.
  • Payload generation punya schema validation dan policy validation.
  • Estimasi biaya dihitung sebelum enqueue.
  • Ada limit per user, per API key, per account, dan limit concurrency.
  • Upload disimpan dengan nama internal, diverifikasi tipe/ukuran, dan discan sebelum dipakai worker.
  • Worker hanya menerima referensi storage internal, bukan path arbitrer dari user.
  • Submit dan export mendukung idempotency key.
  • Job punya timeout, heartbeat, dan proses janitor untuk cleanup.
  • Audit log mencatat actor, target, hasil, dan alasan kebijakan tanpa membocorkan secret.
  • URL download/export bersifat sementara atau diproteksi backend.
  • Service account internal memiliki izin minimal dan terpisah dari credential user.

Penutup

Hardening API terrain job tidak cukup dilakukan dengan menambah middleware auth di depan endpoint. Sistem yang aman untuk workload generasi terrain harus menggabungkan autentikasi yang tepat, authorization berbasis kepemilikan resource, validasi parameter yang sadar biaya, upload pipeline yang ketat, serta rate limit dan quota yang menghitung mahalnya pekerjaan, bukan sekadar jumlah request.

Jika Anda hanya mengambil satu prinsip dari artikel ini, ambil yang ini: tolak pekerjaan mahal sedini mungkin, sebelum menyentuh queue dan worker. Di layanan berbasis GPU atau render pipeline, keputusan keamanan terbaik sering kali sekaligus menjadi keputusan operasional dan finansial terbaik.