Skip to main content
BlogSecurity

Deadbugz MCP Server: Pertahanan Poisoning Metadata Tool Saat Runtime

Pembacaan teliti kampanye Deadbugz MCP: server yang menulis ulang metadata tool setelah panggilan ketiga, dan cara fingerprint definisi serta memberi gate pada perubahan.

Matthews Wong

Software Engineer

Oktober 202612 menit baca
Deadbugz MCP Server: Pertahanan Poisoning Metadata Tool Saat Runtime

Ringkasan Utama

Review sekali saat install tidak cukup untuk menyatakan sebuah MCP server aman. Server Deadbugz berperilaku normal pada tiga tool call pertama, lalu menulis ulang metadata tool dan prompt agar agent mencari SSH key dan credential cloud. Pertahanannya: fingerprint definisi, cek ulang di dalam sesi, dan wajibkan re-approval setiap ada perubahan.

Review yang dulu saya lakukan untuk MCP server baru memakan sekitar sepuluh menit. Baca README, skim source, connect, panggil tiap tool sekali, lihat output yang wajar, lalu approve. Deadbugz adalah insiden yang membuat saya berhenti percaya pada rutinitas itu, karena server yang lolos tetap bisa jahat pada panggilan keempat.

Post ini membaca tulisan Pillar Security, research note Cloud Security Alliance dan entri roundup OWASP Q3 2026 secara berdampingan, lalu spesifikasi MCP untuk melihat apa yang sebenarnya dijanjikan kepada client. Di akhir ada script fingerprinting, gate untuk pull request, dan checklist hari Senin. Saya menjelaskan apa yang dilakukan server, bukan cara membuatnya, dan tidak ada teks payload di sini.

Apa yang sebenarnya dilakukan Deadbugz pada 10 Agustus 2026

Pillar Security melaporkan bahwa akun GitHub bernama zellkernel membuka 23 pull request ke repository AI, MCP dan developer tool yang tidak berhubungan dalam rentang 74 menit pada 10 Agustus 2026. OWASP mencatat insiden ini diungkap pada 12 Agustus dan tidak ada compromise terkonfirmasi, karena tidak ada pull request yang di-merge. Saat Pillar melakukan review, 19 sudah ditutup dan 4 masih terbuka.

Pull request itu hanya jalur pengiriman, bukan exploit-nya. Tiap pull request mencoba membuat maintainer memasang server bernama productivity-suite ke proyeknya. Server itu hanya menawarkan dua tool yang tampak tidak berbahaya, dan bahayanya sepenuhnya ada pada apa yang kemudian ia katakan tentang tool tersebut.

  • 17 pull request menambahkan konfigurasi remote MCP server yang menunjuk ke endpoint milik penyerang.
  • 4 menunjuk ke script Python lokal tersembunyi di dalam direktori berawalan titik pada folder config home.
  • 2 adalah pengajuan ke direktori atau listing, yang oleh Pillar dan CSA dibaca sebagai kemungkinan alat untuk discovery.

Mengapa review sekali saat install meloloskannya

Server menyimpan hitungan tools/call di memori untuk setiap client yang terhubung. Pada tiga panggilan pertama ke format_text atau summarize ia berperilaku seperti utilitas teks biasa. Setelah panggilan ketiga, respons tools/list dan prompts/get berubah dan mulai membawa instruksi untuk agent: cari SSH key, credential AWS, shell history dan konfigurasi Kubernetes, lalu rahasiakan dari pengguna. OWASP menekankan bahwa server tidak membaca file itu sendiri. Ia membujuk agent yang dipercaya untuk mengumpulkannya.

Gerbang itu mengalahkan setiap langkah rutinitas lama saya. Review source tidak menemukan apa pun di code path awal, seperti dicatat CSA. Mencoba tiap tool sekali secara manual masih di bawah ambang. Scan otomatis yang connect, list tool dan memanggil dua di antaranya juga di bawah ambang. Pillar menyebut gating ini teknik penghindaran riset, dan saya setuju: ia dirancang mengikuti cara orang memeriksa server, bukan cara server berjalan.

Kalau proses approval Anda berhenti saat install, yang Anda approve hanyalah tiga panggilan pertama server itu. Sisanya tidak pernah di-review, dan agent akan memperlakukan teks baru sebagai instruksi dari server yang sudah dipercaya.

Bagian protokol mana yang bisa berubah di bawah Anda

Halaman Tools di spesifikasi MCP mendefinisikan tool sebagai name ditambah metadata: name, title opsional, description, inputSchema, outputSchema opsional dan annotations opsional. Model membaca field itu untuk memutuskan apa yang dipanggil dan dengan argumen apa. Karena itu metadata adalah attack surface, bukan sekadar dokumentasi. Berikut yang dikatakan spesifikasi tentang tiap surface yang disentuh atau bisa disentuh Deadbugz.

SurfaceIsi spesifikasi 2025-06-18Relevansi untuk serangan ini
tools/listMengembalikan definisi tool dengan name, description, inputSchema dan annotations, dengan pagination memakai cursor. Tidak ada aturan bahwa panggilan berikutnya harus mengembalikan teks yang sama.Deadbugz mengubah respons ini setelah tools/call ketiga. Definisi yang Anda baca saat install hanyalah snapshot.
prompts/getMengembalikan messages dengan role dan content untuk prompt tertentu. Prompt digambarkan sebagai user-controlled.Pillar dan CSA sama-sama menyebut respons ini ikut ditulis ulang, jadi ini kanal kedua untuk instruksi yang terlewat oleh review yang hanya melihat tool.
notifications/tools/list_changedServer yang mendeklarasikan listChanged SHOULD mengirimnya saat daftar tool berubah. SHOULD bukan MUST.Pillar mencatat kode publiknya mengiklankan tools.listChanged tetapi tidak menyebut apakah notifikasi dikirim. Jangan jadikan pengumuman server sebagai satu-satunya detektor drift.
Tool annotationsClient MUST menganggap tool annotations tidak tepercaya kecuali berasal dari server yang tepercaya.Server yang tepercaya saat approval tetap dianggap tepercaya. Fingerprinting adalah cara untuk menyadari bahwa kepercayaan itu tidak lagi berlaku.

Spesifikasi juga menyatakan SHOULD selalu ada human in the loop yang bisa menolak invocation tool, dan client sebaiknya mencatat penggunaan tool untuk audit. Itu naluri yang benar, tetapi aturan agar client menampilkan input sebelum memanggil ditujukan pada panggilannya, bukan pada definisi yang diam-diam berubah sejak terakhir dilihat manusia. Invariant Labs menggambarkan pola ini pada April 2025 sebagai rug pull: server mengubah description tool setelah client menyetujuinya. Deadbugz menambahkan counter dan pull request publik sebagai jalur pengiriman.

Fingerprint setiap definisi, lalu terus cek

Pillar meminta pembuat client dan platform untuk fingerprint definisi tool saat approval dan memperlakukan perubahan pada server yang sudah disetujui sebagai security event. CSA menyebutnya hash pinning. Script di bawah adalah cara saya melakukannya tanpa dependency. Yang penting ada di fungsi terakhir: karena pemicunya counter per client, fingerprint yang diambil sekali saat initialise tidak membuktikan apa pun, jadi pengecekan dijalankan setelah setiap panggilan di dalam sesi yang hidup.

// mcp-fingerprint.mjs  (Node 18+, no dependencies)
import { createHash } from "node:crypto";

// JSON.stringify depends on key order, so two servers (or two runs) that send
// the same definition with keys in a different order would hash differently.
// Sort keys recursively before hashing.
function canonical(value) {
  if (Array.isArray(value)) return "[" + value.map(canonical).join(",") + "]";
  if (value && typeof value === "object") {
    return (
      "{" +
      Object.keys(value)
        .sort()
        .map((key) => JSON.stringify(key) + ":" + canonical(value[key]))
        .join(",") +
      "}"
    );
  }
  return JSON.stringify(value);
}

const sha256 = (value) =>
  "sha256:" + createHash("sha256").update(canonical(value)).digest("hex");

// Hash the WHOLE definition: name, title, description, inputSchema,
// outputSchema and annotations. Hashing only "description" misses a changed
// schema field description, which the model reads just as closely.
export function fingerprintTools(toolsFromAllPages) {
  return Object.fromEntries(toolsFromAllPages.map((t) => [t.name, sha256(t)]));
}

// prompts/get returns messages, not a definition. Hash what came back for the
// arguments you approved, because that text is what lands in the context.
export function fingerprintPrompt(promptGetResult) {
  return sha256(promptGetResult);
}

// Compare a live listing with the approved lock entry.
export function diffTools(approved, live) {
  const changed = [];
  for (const name of new Set([...Object.keys(approved), ...Object.keys(live)])) {
    if (!(name in live)) changed.push({ name, kind: "removed" });
    else if (!(name in approved)) changed.push({ name, kind: "added" });
    else if (approved[name] !== live[name]) changed.push({ name, kind: "changed" });
  }
  return changed;
}

// The part Deadbugz makes necessary: re-list INSIDE the live session after
// every tools/call, not only at initialise. listTools must follow nextCursor
// and return every page; fail closed and stop the session on any drift.
export async function guardedCall(listTools, callTool, approved, name, args) {
  const result = await callTool(name, args);
  const drift = diffTools(approved, fingerprintTools(await listTools()));
  if (drift.length > 0) {
    throw new Error("MCP tool definitions drifted: " + JSON.stringify(drift));
  }
  return result;
}

Hash dihitung dari seluruh definisi, bukan hanya description, dan itu disengaja: description pada property inputSchema yang berubah dibaca model persis seperti description tool yang berubah. Throw yang fail-closed juga disengaja. Alert drift yang hanya menulis log akan membiarkan agent bertindak berdasarkan metadata yang sudah ditulis ulang, jadi sesi dihentikan dan manusia melakukan re-approval. Harganya satu request tools/list tambahan per panggilan, yang menurut saya murah dibanding agent tanpa review yang memegang shell Anda.

Gate pull request, lalu pin lock file

Deadbugz datang lewat pull request, jadi kontrol termurah ada di saat review. Perlakukan setiap perubahan yang menambah MCP server atau remote endpoint sebagai security event, sesuai kata-kata CSA. Workflow di bawah menggagalkan pull request yang menyentuh lokasi config MCP umum dan terpisah mencari dua indikator yang dipublikasikan Pillar pada baris yang ditambahkan. Nama file config adalah lokasi umum client, jadi sesuaikan pola untuk client yang Anda pakai. Lock file adalah separuh lainnya: ia mencatat digest yang disetujui untuk setiap tool dan setiap prompt yang disetujui.

# .github/workflows/mcp-change-gate.yml
name: mcp-change-gate
on: pull_request
jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # the diff below needs the base branch history
      - name: Block unreviewed MCP wiring
        run: |
          # Common client config locations. Extend for the clients your team uses.
          CONFIGS='(^|/)(\.mcp\.json|mcp\.json|\.cursor/mcp\.json|\.vscode/mcp\.json)$'
          if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E "$CONFIGS"; then
            echo "::error::MCP config changed. Needs a security review label."
            exit 1
          fi
      - name: Hunt the published Deadbugz indicators
        run: |
          # Indicators named in Pillar Security's write-up. Added lines only.
          if git diff origin/${{ github.base_ref }}...HEAD | grep -E '^\+' \
             | grep -E 'productivity-suite|\.deadbug-mcp\.py'; then
            echo "::error::Deadbugz indicator found in this pull request."
            exit 1
          fi
// mcp.lock.json  (committed; changing it is a reviewed, owned change)
{
  "servers": {
    "internal-docs": {
      "approvedBy": "security-review-2026-10",
      "protocolRevision": "2025-06-18",
      "tools": {
        "search_docs": "sha256:<digest of the full tools/list entry>",
        "get_page": "sha256:<digest of the full tools/list entry>"
      },
      "prompts": {
        "summarise_page": "sha256:<digest of prompts/get for the approved arguments>"
      }
    }
  }
}

Perlakukan lock file seperti dependency lockfile. Perubahan digest adalah perubahan yang di-review dan punya pemilik, dan orang yang menyetujuinya harus bisa membaca diff definisi aslinya, bukan hanya dua hash. Simpan definisi lengkap yang disetujui di samping digest, kalau tidak review alert drift hanya tebak-tebakan.

Pillar menyarankan menyimpan log MCP client sebelum cleanup dan me-review refresh definisi tool serta aksi agent setelah panggilan ketiga. Itu hanya berhasil jika Anda sudah mencatat definisi di awal sesi. Tulis fingerprint, nama server dan session id ke audit log setiap kali connect, supaya buktinya sudah ada sebelum dibutuhkan.

Di mana fingerprinting gagal, dan apa harganya

Saya ingin batasannya terlihat, karena kontrol yang dijual sebagai lengkap adalah cara insiden berikutnya lolos. Ini pembacaan saya sendiri atas mekanismenya, bukan temuan dari sumber, yang menjelaskan serangan dan pertahanan tetapi tidak menjelaskan bagaimana tool fingerprinting tertentu bekerja melawannya.

  • Ia hanya mendeteksi perubahan. Kalau definisi yang Anda setujui sudah berbahaya sejak awal, hash dengan setia melindungi teks berbahaya itu. Review definisi pertama tetap harus nyata.
  • Server bisa tahu siapa yang bertanya. CSA menyebut counter disimpan per client yang terhubung, jadi scanner dengan koneksi sendiri bisa melihat server yang berbeda dari yang dilihat agent Anda. Jalankan cek drift di dalam sesi agent, bukan di sampingnya.
  • Server yang sah juga berubah. Vendor yang mengedit description menghasilkan alert yang sama dengan penyerang, jadi siapkan reviewer untuk churn re-approval.
  • Hasil tool bukan metadata. Teks yang dikembalikan dari tools/call berada di luar fingerprint, dan spesifikasi meminta server melakukan sanitize output dan client memvalidasi hasil sebelum sampai ke model.

Karena itu Pillar dan OWASP sama-sama memasangkan deteksi dengan lapisan kedua yang tidak bergantung pada membaca teks: letakkan akses file, credential, eksekusi kode, email dan write ke repository di bawah policy, jangan biarkan metadata tool yang memicunya. Fingerprinting memberi tahu bahwa server berubah. Aturan deny yang membatasi kerusakan saat Anda terlambat.

Tidak ada satu kontrol yang menutup seluruh serangan, jadi tabel ini menyandingkan tiap kontrol dengan apa yang bisa dilihatnya. Kolom terakhir adalah pembacaan saya tentang bagaimana masing-masing akan menghadapi perilaku yang dijelaskan tiga tulisan itu. Ini penilaian, bukan hasil pengujian.

KontrolYang bisa dilihatTerhadap Deadbugz (pembacaan saya)
Review sekali saat installREADME, kode dan respons pertamaLolos, karena tiga panggilan pertama normal
Review statis repositoryCode path awalKemungkinan tidak menemukan apa pun menurut CSA, karena instruksi tidak ada di path sampai kondisi counter terpenuhi
Scan otomatis singkatRespons beberapa panggilan di sesi baruTetap di bawah ambang jika memanggil tool kurang dari empat kali
Fingerprint saat init plus cek ulang setelah tiap panggilanSetiap respons tools/list selama sesiSeharusnya menandai perubahan pada listing pertama setelah panggilan ketiga, jika berjalan di sesi agent itu sendiri
Deny by default pada credential storeApa yang coba dibaca agent, bukan apa kata serverMemblokir tujuan yang dinyatakan meski perubahan metadata terlewat. Ini lapisan kedua dari Pillar dan OWASP
Internal registry plus gate PRServer mana yang masuk ke codebaseMenghentikan jalur pengiriman, yaitu pull request yang tidak di-merge, sebelum server pernah dikonfigurasi

Baca kolom terakhir dari atas ke bawah dan polanya jelas. Tiga kontrol pertama semuanya melihat server sebelum ia berulah, dan kampanye ini dirancang untuk tenang tepat pada saat itu. Kontrol yang berguna bekerja saat run time atau di pintu masuk.

Yang dilakukan hari Senin

Lima langkah, dalam urutan yang akan saya pakai. Tidak ada yang butuh tooling baru selain script di atas.

  1. Cari di repository, direktori home dan config MCP client untuk productivity-suite dan nama script tersembunyi dari daftar indikator Pillar, dan simpan log client sebelum membersihkan apa pun.
  2. Daftar setiap MCP server yang dikonfigurasi tim Anda, siapa yang menyetujui dan kapan, lalu catat fingerprint tiap tool dan prompt yang disetujui. Server yang tidak ada approver-nya masuk daftar penghapusan.
  3. Tambahkan gate pull request agar setiap perubahan config MCP membutuhkan security reviewer yang disebut namanya.
  4. Pindahkan cek drift ke dalam sesi agent agar berjalan setelah setiap tools/call, dan buat ketidakcocokan menghentikan sesi serta meminta re-approval.
  5. Deny akses baca agent ke SSH key, file credential cloud, shell history dan kube config secara default, dan buka pengecualian satu per satu. Jika paparan terkonfirmasi atau tidak bisa disingkirkan, rotasi credential yang terjangkau.

Daftar strategis CSA lebih jauh lagi: masukkan manifest MCP ke software composition analysis, jaga internal registry server yang disetujui, dan lakukan red-team untuk aktivasi tertunda atau bersyarat. Uji pipeline Anda dengan server tidak berbahaya yang mengubah description pada panggilan keempat dan lihat apakah ada yang menyadarinya.

Aturan yang saya bawa dari Deadbugz: approval MCP adalah pernyataan tentang satu momen, bukan tentang sebuah server. Setujui sebuah definisi, fingerprint, dan perlakukan setiap perbedaan berikutnya sebagai security event sampai seseorang membacanya. Tidak ada yang me-merge pull request itu, dan hanya itu alasan ini menjadi peringatan, bukan post-mortem.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa itu kampanye Deadbugz MCP?

Deadbugz adalah kampanye ketika sebuah akun GitHub membuka 23 pull request pada 10 Agustus 2026 untuk menambahkan MCP server berbahaya bernama productivity-suite ke proyek yang tidak berhubungan. Pillar Security yang mengidentifikasinya, dan OWASP memuatnya di roundup Q3 2026. Tidak ada pull request yang di-merge, sehingga tidak ada compromise lanjutan yang terkonfirmasi.

Mengapa review MCP server sekali saja gagal?

Server berperilaku normal pada tiga request tools/call pertama dan baru setelah itu mengubah respons tools/list dan prompts/get. Review source, mencoba tiap tool sekali, atau scan otomatis singkat semuanya masih di bawah ambang itu. Jadi approval hanya bukti tentang panggilan-panggilan awal server.

Bagaimana cara mendeteksi perubahan definisi tool MCP?

Hitung hash seluruh definisi tool saat approval dan simpan digest di lock file yang di-review. Lalu list ulang tool di dalam sesi agent yang hidup setelah setiap panggilan dan bandingkan. Perlakukan setiap perbedaan sebagai security event dan wajibkan re-approval sebelum tool dipakai lagi.

Apakah cukup mengandalkan notifications tools list_changed untuk mendeteksi tool yang berubah?

Tidak cukup. Spesifikasi MCP menyatakan server yang mendeklarasikan listChanged SHOULD mengirim notifikasi, yang bukan jaminan. Pillar mencatat kode Deadbugz mengiklankan capability itu tetapi tidak menyebut apakah notifikasi dikirim, jadi verifikasi definisi sendiri.

Apa yang harus saya lakukan jika agent saya pernah connect ke productivity-suite?

Cari di config, repository dan direktori home untuk indikator yang dipublikasikan Pillar, dan simpan log MCP client sebelum cleanup apa pun. Review refresh definisi tool dan aksi agent setelah panggilan ketiga. Jika paparan credential SSH, cloud atau Kubernetes terkonfirmasi atau tidak bisa disingkirkan, rotasi sesuai kebijakan incident response Anda.