Kontrak API aman bukan hanya soal autentikasi berhasil atau tidak. Masalah yang lebih berbahaya muncul ketika satu endpoint atau satu payload bisa dibaca sebagai permintaan data, perintah untuk mengeksekusi aksi, sekaligus jalan untuk memakai hak akses yang lebih tinggi dari yang seharusnya.

Ini sering terlihat pada pola integrasi modern: sistem internal, agent, workflow automation, atau tooling berbasis prompt mengirim input yang tampak sederhana seperti fix this code, tetapi kontrak API di belakangnya terlalu longgar. Akibatnya, backend menafsirkan input itu bukan sekadar membaca konteks, melainkan juga menjalankan perubahan, memanggil sistem lain, atau memakai token dengan scope berlebihan. Pelajarannya jelas: jangan gabungkan endpoint baca data, eksekusi aksi, dan eskalasi hak akses dalam kontrak yang ambigu.

Referensi konteks yang relevan: laporan mengenai kekhawatiran terhadap prompt sederhana yang berujung pada perilaku sistem tak diinginkan menunjukkan bahwa masalah utama sering kali bukan “prompt ajaib”, tetapi desain kontrak dan kontrol eksekusi yang lemah. Fokus artikel ini adalah pada desain API, auth, dan jebakan integrasi.

Mengapa kontrak API ambigu berbahaya

Kontrak API menjadi berbahaya saat server harus menebak maksud klien dari field generik seperti action, mode, type, atau bahkan teks bebas. Semakin besar ruang interpretasi, semakin mudah terjadi hal berikut:

  • Confused deputy: layanan dengan hak lebih tinggi bertindak atas nama pemanggil yang seharusnya tidak punya izin itu.
  • Privilege creep: integrasi memakai satu token “serba bisa” untuk baca, tulis, dan approve.
  • Unsafe retries: retry otomatis mengulang operasi tulis atau aksi sensitif karena server tidak membedakan request baru vs pengulangan.
  • Webhook loops: event callback memicu endpoint yang menjalankan aksi yang sama lagi.
  • Schema drift: field baru ditambahkan tanpa validasi ketat, lalu dipakai sebagai instruksi tersembunyi.

Pola ini sering muncul dari niat baik: ingin membuat API fleksibel, generik, dan cepat diintegrasikan. Namun fleksibilitas yang tidak dibatasi biasanya berpindah menjadi permukaan serangan dan sumber bug operasional.

Prinsip inti: pisahkan query, command, dan approval

1. Query hanya untuk membaca data

Endpoint query seharusnya tidak memiliki efek samping. Jika klien memanggil endpoint untuk mengambil data, hasilnya harus deterministik sesuai parameter input dan otorisasi, tanpa mengubah status sistem.

GET /v1/repos/123/files/main.py
Authorization: Bearer <token-scope:repo.read>

Endpoint seperti ini aman untuk cache, retry, observability, dan debugging. Jika request diulang, tidak ada tindakan tambahan yang terjadi.

2. Command hanya untuk mengeksekusi perubahan

Operasi tulis atau aksi harus eksplisit. Nama endpoint, metode HTTP, schema body, dan respons harus menegaskan bahwa ini adalah command.

POST /v1/code-fixes
Authorization: Bearer <token-scope:codefix.write>
Idempotency-Key: 7f3bde3a-1e7e-4a2f-8c08-5d9d99d9a221
Content-Type: application/json

{
  "repository_id": "123",
  "file_path": "main.py",
  "patch": "...diff or structured patch...",
  "reason": "lint_error"
}

Di sini server tidak perlu menebak apakah klien hanya ingin analisis, preview, atau benar-benar menerapkan perubahan. Semua jelas dari endpoint dan kontraknya.

3. Approval dipisahkan dari command

Aksi sensitif sebaiknya tidak langsung dieksekusi hanya karena command valid. Tambahkan tahap approval eksplisit, terutama untuk perubahan produksi, transaksi finansial, penghapusan permanen, atau eskalasi akses.

POST /v1/code-fixes
...

HTTP/1.1 202 Accepted
{
  "fix_id": "fix_890",
  "status": "pending_approval",
  "required_approval_scope": "codefix.approve"
}
POST /v1/code-fixes/fix_890/approve
Authorization: Bearer <token-scope:codefix.approve>
Content-Type: application/json

{
  "approved_by": "user_42",
  "reason": "reviewed"
}

Pemisahan ini penting karena banyak insiden terjadi bukan saat data dibaca, tetapi saat sistem secara otomatis “membantu” sampai mengeksekusi sesuatu yang seharusnya butuh keputusan manusia atau token berbeda.

Anti-pattern yang sering terjadi dan perbaikannya

Anti-pattern 1: satu endpoint untuk semua hal

POST /v1/assistant
Authorization: Bearer <token-admin>
Content-Type: application/json

{
  "prompt": "fix this code",
  "repository_id": "123",
  "auto_apply": true,
  "approve_if_safe": true
}

Masalahnya:

  • prompt bersifat ambigu.
  • Token admin dipakai untuk tugas yang semestinya hanya butuh baca.
  • auto_apply dan approve_if_safe mencampur analisis, keputusan risiko, dan eksekusi.
  • Tidak jelas apakah request ini hanya meminta saran, membuat patch, atau menerapkan patch.

Perbaikan: pecah menjadi alur yang eksplisit.

  1. Query/analysis: minta analisis atau patch usulan.
  2. Command: buat draft perubahan.
  3. Approval: setujui perubahan dengan scope berbeda.
  4. Execution: terapkan hanya setelah approved.
POST /v1/code-analysis
Authorization: Bearer <token-scope:repo.read>
Content-Type: application/json

{
  "repository_id": "123",
  "file_path": "main.py",
  "task": "suggest_fix"
}
POST /v1/code-fixes
Authorization: Bearer <token-scope:codefix.write>
Idempotency-Key: 0f48f0b1-2f2e-4b37-8d6b-12e0d6abf155
Content-Type: application/json

{
  "analysis_id": "an_456",
  "apply_mode": "draft"
}

Anti-pattern 2: token serba bisa untuk semua integrasi

Banyak sistem memakai satu API key atau bearer token untuk:

  • membaca data repositori,
  • membuat perubahan,
  • approve deployment,
  • menghapus resource,
  • mengakses audit log.

Jika token ini bocor atau dipakai layanan yang salah, blast radius-nya sangat besar.

Perbaikan: gunakan scope minimal dan pisahkan identitas per fungsi.

  • repo.read untuk membaca file dan metadata.
  • codefix.write untuk membuat draft patch.
  • codefix.approve hanya untuk approval.
  • deploy.trigger terpisah dari semua scope di atas.

Lebih baik lagi jika approval memakai identitas manusia atau service account khusus yang diawasi ketat, bukan token yang sama dengan proses analisis.

Anti-pattern 3: retry memicu penulisan berulang

Client timeout, lalu mengirim ulang request. Tanpa idempotency key, server bisa membuat dua patch, dua invoice, atau dua deployment.

Perbaikan: untuk semua operasi tulis yang bisa diulang oleh jaringan, proxy, job runner, atau user interface, dukung Idempotency-Key.

POST /v1/payments
Idempotency-Key: 8bf6c0ea-560f-4f58-84fc-bf14a9d0f001

Server harus menyimpan kunci tersebut bersama hasil request yang pertama. Jika request identik datang lagi dengan key yang sama, server mengembalikan hasil yang sama, bukan mengeksekusi ulang. Jika payload berbeda tetapi key sama, balas error yang jelas karena itu indikasi bug client.

Anti-pattern 4: webhook yang memicu aksi yang sama lagi

Contoh umum:

  1. Sistem A membuat perubahan di Sistem B.
  2. Sistem B mengirim webhook change.created.
  3. Consumer di Sistem A tidak membedakan event eksternal vs hasil dari aksinya sendiri.
  4. A memanggil endpoint create lagi.

Perbaikan:

  • Sertakan event_id unik dan simpan deduplication record.
  • Sertakan source_system atau correlation ID.
  • Webhook handler jangan langsung menjalankan aksi destruktif tanpa pengecekan state.
  • Gunakan model state transition yang sah, bukan “set status apa saja dari event apa saja”.

Scope token minimal dan boundary auth yang jelas

Desain otorisasi yang aman bukan hanya memeriksa “sudah login atau belum”, tetapi juga apa yang boleh dilakukan token ini pada endpoint ini, terhadap resource ini, dan pada tahap workflow yang mana.

Praktik yang disarankan

  • Scope per aksi, bukan per aplikasi besar. Hindari scope generik seperti full_access.
  • Audience terbatas: token untuk service A tidak otomatis valid di service B jika tidak perlu.
  • Short-lived token untuk operasi sensitif.
  • Resource-level authorization: token boleh menulis pada repositori tertentu, bukan semua repositori.
  • Step-up auth untuk aksi sensitif: approval butuh token atau identitas dengan level trust lebih tinggi.

Contoh pemetaan scope

GET /v1/repos/{id}/files/*           - requires: repo.read
POST /v1/code-analysis              - requires: repo.read
POST /v1/code-fixes                 - requires: codefix.write
POST /v1/code-fixes/{id}/approve    - requires: codefix.approve
POST /v1/code-fixes/{id}/apply      - requires: codefix.execute

Pemisahan ini mencegah sistem analisis atau UI preview diam-diam menjadi jalur eksekusi produksi hanya karena semua memakai token yang sama.

Validasi schema ketat: jangan beri ruang tafsir berlebih

Kontrak API aman membutuhkan schema request dan response yang ketat. Tujuannya bukan sekadar validasi format, tetapi membatasi ruang perilaku yang tidak direncanakan.

Apa yang perlu divalidasi

  • Field wajib dan tipe data yang jelas.
  • Enum untuk aksi yang terbatas, bukan string bebas.
  • Reject unknown fields bila memungkinkan, terutama pada command sensitif.
  • Batas ukuran payload, list, dan string.
  • Pattern validation untuk identifier, path, dan reference.
  • State transition validation: misalnya hanya pending_approval -> approved, bukan lompat dari apa saja ke applied.

Contoh schema longgar yang berbahaya

{
  "action": "fix",
  "target": "main.py",
  "options": {
    "maybe_apply": true,
    "if_safe": true,
    "elevate": "if_needed"
  }
}

Field seperti if_safe atau elevate sangat problematik karena keputusan keamanan berpindah ke interpretasi runtime yang mungkin berubah antar versi layanan.

Contoh schema yang lebih aman

{
  "analysis_id": "an_456",
  "apply_mode": "draft",
  "change_set": [
    {
      "file_path": "main.py",
      "patch_format": "unified_diff",
      "patch": "..."
    }
  ]
}

Perintahnya jelas: buat draft perubahan dari analisis tertentu. Tidak ada field untuk eskalasi diam-diam. Jika ingin approval atau apply final, lakukan di endpoint lain.

Idempotency key untuk operasi tulis

Idempotency key wajib dipertimbangkan untuk endpoint yang melakukan create, apply, transfer, enqueue job, atau mutasi lain yang mungkin terkena retry dari jaringan dan middleware.

Cara kerjanya

  1. Client menghasilkan key unik per operasi logis.
  2. Server menyimpan kombinasi key, identitas pemanggil, endpoint, fingerprint request, dan hasil pertama.
  3. Jika request yang sama datang lagi dengan key sama, kembalikan respons sebelumnya.
  4. Jika payload berbeda dengan key sama, tolak sebagai konflik.

Hal yang sering salah

  • Menggunakan timestamp kasar sebagai key sehingga bentrok.
  • Menganggap retry hanya terjadi di client, padahal proxy, job worker, dan gateway juga bisa retry.
  • Tidak menyimpan status in-progress sehingga dua worker tetap mengeksekusi bersamaan.
  • Memakai idempotency pada GET, tetapi justru melupakan POST/PUT yang kritis.

Idempotency bukan pengganti transaksi atau locking, tetapi lapisan penting untuk mencegah duplikasi efek samping.

Approval untuk aksi sensitif

Jika sebuah command berdampak tinggi, validasi schema dan scope saja belum cukup. Anda perlu approval boundary.

Kapan approval layak ditambahkan

  • Perubahan ke produksi atau branch terlindungi.
  • Penghapusan data permanen.
  • Transfer dana atau perubahan limit finansial.
  • Rotasi credential, pemberian akses baru, atau perubahan kebijakan auth.
  • Aksi yang dipicu dari masukan tidak terstruktur atau dari sistem eksternal.

Bentuk approval yang umum

  • Human-in-the-loop: operator atau reviewer menyetujui.
  • Dual control: butuh dua pihak berbeda.
  • Policy engine: rule eksplisit memutuskan apakah perlu review manual.
  • Time delay: ada jendela pembatalan untuk aksi tertentu.

Yang penting, approval harus tercermin dalam kontrak API, bukan sekadar konvensi UI. Jika backend tetap mengizinkan apply final dengan token write biasa, maka approval di UI hanya ilusi.

Audit trail yang benar-benar berguna

Audit trail bukan log generik “request masuk”. Untuk investigasi dan kepatuhan operasional, catat siapa melakukan apa, terhadap resource apa, dengan dasar keputusan apa, dan melalui alur mana.

Minimal yang perlu dicatat

  • actor: user ID, service account, atau principal lain.
  • action: create_draft, approve, apply, reject.
  • resource: ID objek dan jenisnya.
  • request_id dan correlation_id.
  • idempotency_key bila ada.
  • before/after state untuk perubahan penting.
  • auth context: scope yang dipakai, bukan token mentah.
  • reason atau justification untuk approval/rejection.

Contoh catatan audit

{
  "timestamp": "2026-06-24T10:15:32Z",
  "actor": {
    "type": "service_account",
    "id": "svc-codefix-bot"
  },
  "action": "create_draft_fix",
  "resource": {
    "type": "code_fix",
    "id": "fix_890"
  },
  "request_id": "req_1a2b",
  "correlation_id": "corr_9c8d",
  "idempotency_key": "0f48f0b1-2f2e-4b37-8d6b-12e0d6abf155",
  "auth_scope": ["codefix.write"],
  "result": "pending_approval"
}

Audit trail yang baik sangat membantu saat debugging integrasi: apakah aksi terpicu oleh user, service account, webhook duplikat, atau retry dari gateway?

Webhook dan retry tanpa aksi berulang

Aturan dasar untuk consumer webhook

  • Verifikasi signature atau mekanisme autentikasi webhook.
  • Anggap event bisa datang lebih dari sekali dan tidak selalu berurutan.
  • Simpan event_id untuk deduplikasi.
  • Jangan mengeksekusi command jika state target sudah sesuai.
  • Pisahkan endpoint webhook ingestion dari endpoint command internal.

Contoh alur aman

  1. Webhook diterima di POST /v1/webhooks/provider-x.
  2. Sistem memverifikasi signature dan menyimpan event mentah.
  3. Worker memproses event secara idempotent berdasarkan event_id.
  4. Worker melakukan lookup state internal.
  5. Jika perlu tindakan, worker memanggil endpoint command internal dengan idempotency key baru dan correlation ID yang mengaitkan event asal.

Dengan pola ini, webhook tidak menjadi jalur eksekusi langsung yang sulit dikontrol dan sulit diaudit.

Alur request yang lebih aman

Berikut contoh alur untuk kasus “perbaiki kode”, tetapi tanpa kontrak yang ambigu:

  1. Analisis: klien meminta saran perbaikan dengan token repo.read.
  2. Draft: klien membuat draft patch dengan token codefix.write dan Idempotency-Key.
  3. Review: sistem atau manusia meninjau hasil draft.
  4. Approval: reviewer menyetujui dengan token codefix.approve.
  5. Apply: executor terpisah menerapkan perubahan dengan token codefix.execute.
  6. Webhook status: sistem mengirim event status; consumer memproses secara idempotent.
POST /v1/code-analysis
Authorization: Bearer <repo.read>

{
  "repository_id": "123",
  "file_path": "main.py",
  "task": "suggest_fix"
}

---

POST /v1/code-fixes
Authorization: Bearer <codefix.write>
Idempotency-Key: 4f0d77c7-9f1b-4d8c-bb2f-89f0ac09c111

{
  "analysis_id": "an_456",
  "apply_mode": "draft"
}

---

POST /v1/code-fixes/fix_890/approve
Authorization: Bearer <codefix.approve>

{
  "reason": "reviewed by maintainer"
}

---

POST /v1/code-fixes/fix_890/apply
Authorization: Bearer <codefix.execute>
Idempotency-Key: 2ab441bc-58d7-4c2f-9d18-c8d8c5f96831

{
  "target_branch": "main"
}

Alur ini mungkin terasa lebih panjang, tetapi keuntungannya besar: niat jelas, otorisasi sempit, observability lebih baik, approval nyata, dan retry lebih aman.

Checklist review kontrak API

Gunakan daftar ini saat meninjau API baru atau integrasi yang sudah berjalan:

  • Apakah endpoint baca data benar-benar bebas efek samping?
  • Apakah command dipisahkan dari query secara eksplisit?
  • Apakah approval untuk aksi sensitif dipisahkan dari create/apply?
  • Apakah token dibatasi dengan scope minimal dan audience yang tepat?
  • Apakah ada token serba bisa yang dipakai lintas fungsi?
  • Apakah resource-level authorization diperiksa, bukan hanya scope global?
  • Apakah schema request menolak field tak dikenal untuk command sensitif?
  • Apakah enum aksi terbatas dan terdokumentasi?
  • Apakah endpoint tulis mendukung idempotency key?
  • Apakah retry dari client, queue, gateway, dan webhook sudah diperhitungkan?
  • Apakah webhook handler idempotent dan memiliki deduplication berdasarkan event ID?
  • Apakah state transition divalidasi agar tidak lompat sembarangan?
  • Apakah audit trail mencatat actor, action, resource, auth context, dan correlation ID?
  • Apakah correlation ID menghubungkan request awal, command internal, dan webhook balik?
  • Apakah ada jalur “bypass UI” yang tetap bisa mengeksekusi apply tanpa approval backend?

Tips debugging untuk insiden integrasi

  • Lacak correlation ID ujung ke ujung: request awal, job async, webhook, dan command turunan.
  • Bandingkan scope token yang dipakai saat analisis vs saat apply. Scope yang terlalu luas sering menjadi akar masalah.
  • Cari request duplikat dengan payload mirip tetapi tanpa idempotency key atau dengan key yang salah dipakai ulang.
  • Periksa audit trail state transition: apakah objek berpindah dari pending_approval langsung ke applied tanpa event approval?
  • Amati field ambigu seperti auto, safe, mode, execute, elevate. Field semacam ini sering menyimpan logika berisiko.

Penutup

Pelajaran penting dari banyak kegagalan integrasi adalah ini: masalahnya sering bukan input teksnya, melainkan sistem yang memberi terlalu banyak kebebasan interpretasi dan terlalu banyak hak akses pada jalur yang sama. Kontrak API aman harus membedakan dengan tegas antara membaca data, menyiapkan perubahan, menyetujui aksi, dan mengeksekusi perubahan.

Jika Anda merancang API untuk automation, agent, workflow engine, atau integrasi lintas layanan, jangan buat satu endpoint yang “bisa memahami maksud”. Buat beberapa endpoint dengan niat yang sempit, scope token minimal, schema ketat, approval eksplisit, audit trail lengkap, dan mekanisme idempotency untuk semua operasi tulis. Desain seperti ini mungkin sedikit lebih formal, tetapi jauh lebih aman, mudah diuji, dan lebih mudah dioperasikan saat terjadi insiden.