Ringkasan Utama
Cloudflare WriteGuard adalah lapisan di sisi server pada MCP server portal Cloudflare yang meneruskan tool call apa adanya, menambahkan atribusi agent beserta audit event yang sudah di-scrub, atau memblokirnya sebelum handler berjalan. Lapisan ini ada karena approval prompt di sisi client berbeda-beda per harness dan bisa dimatikan pengguna, sehingga tidak bisa menjadi otorisasi.
Tiga background agent milik seorang engineer menutup ribuan ticket dalam satu sore karena prompt pembersihan yang terlalu luas. Cloudflare menyebut butuh sekitar setengah jam untuk menemukan penyebabnya, dan catatan audit tidak bisa memisahkan tindakan agent dari tindakan si engineer. Sore itulah alasan WriteGuard dibuat.
Tulisan ini adalah pembacaan teliti atas pengumuman WriteGuard dari Cloudflare tanggal 5 Agustus 2026, dibandingkan dengan liputan InfoQ, dokumentasi portal, dan spesifikasi MCP. Saya belum menjalankan WriteGuard karena masih private beta. Setiap baris kode di bawah yang melampaui apa yang dipublikasikan Cloudflare saya tandai sebagai buatan saya sendiri.
Sore yang membenarkan adanya lapisan policy
MCP server portal internal Cloudflare menghubungkan 13 MCP server pada bulan April dan 27 saat artikelnya terbit. Semua server awalnya read-only. Lalu tim engineering, product, design, sales, dan customer success meminta tool yang bisa menulis, dan tool write mengubah dampak prompt yang buruk dari satu jawaban salah menjadi banyak record yang salah.
Insiden ticket itu gagal dua kali, dan keduanya butuh perbaikan berbeda. Tidak ada yang membatasi apa yang boleh dilakukan agent, dan tidak ada yang mencatat agent milik siapa. Kesimpulan Cloudflare adalah mereka tidak bisa bergantung pada setiap karyawan untuk mengonfigurasi setiap agent dengan sempurna atau mengawasi setiap tool call, jadi kontrolnya harus berada di tempat yang tidak disentuh karyawan.
Mengapa approval prompt bukan otorisasi
Spesifikasi MCP menaruh keputusan di client, dan kewajibannya dirumuskan lunak. Halaman Tools menyebut harus SELALU ada human in the loop dengan kemampuan menolak tool invocation (SHOULD), dan client SHOULD meminta konfirmasi untuk operasi sensitif. Halaman Elicitation menyebut client SHOULD membolehkan pengguna menolak kapan saja. Halaman Tools yang sama menyebut client MUST menganggap tool annotation tidak tepercaya kecuali berasal dari server tepercaya. Itu desain yang benar untuk user experience tetapi tempat yang salah untuk policy, seperti terlihat di tabel.
| Layer | Siapa yang menegakkan | Bisakah pengguna mematikannya | Apa yang diketahuinya tentang call |
|---|---|---|---|
| Confirmation prompt di client | Aplikasi client, sebagai SHOULD di spec | Tergantung harness; Cloudflare menyebut pengguna bisa menonaktifkan kontrol ini | Call tersebut, hanya di client itu |
| Tool annotation (readOnlyHint, destructiveHint) | Tidak ada: hanya hint yang boleh dibaca client | Tidak berlaku; spec menyebut anggap tidak tepercaya kecuali servernya tepercaya | Perilaku yang dideklarasikan, bukan yang diverifikasi |
| Access policy portal dan allowlist tool | Cloudflare Access di portal; admin bisa mematikan tool atau hanya membuka allowlist | Diatur admin per server dan per portal, bukan oleh pengguna akhir | Server dan tool mana yang boleh dijangkau seseorang, bukan penilaian risiko per call |
| WriteGuard | Portal, sebelum handler tool berjalan | Pemilik server mengaturnya per tool; tujuan desainnya agar pengguna individual tidak | Setiap call yang sampai kepadanya, lengkap dengan tier, outcome, user, dan client |
Baca tabel ini sebagai lapisan, bukan pengganti. Prompt di client tetap membantu manusia di depan keyboard, dan Access tetap menentukan siapa melihat server mana. Yang ditambahkan WriteGuard adalah baris keempat: keputusan yang diambil di tempat yang tidak bisa dimatikan operator agent. Baris itu hanya berlaku untuk traffic yang benar-benar lewat portal, dan bagian 7 membahasnya lagi.
Tiga hasil: pass, attribute, block
Untuk setiap call, WriteGuard bisa meneruskannya apa adanya, memberi atribusi pada write yang didukung dengan menambahkan label agent dan mengirim audit event yang sudah di-scrub, atau memblokirnya sebelum handler tool berjalan. Ini berlaku untuk setiap MCP server di balik portal tanpa mengubah kode tiap server. Cloudflare memakai tiga tool GitLab untuk menunjukkan ketiga hasil tersebut.
| Tool | Risk tier | Apa yang dilakukan WriteGuard | Apa yang dicatat |
|---|---|---|---|
get_merge_request | Read Only | Diteruskan apa adanya | Dicatat sesuai tier-nya; data audit bisa di-query per tier |
create_mr_note | Contained Write | Menambahkan atribusi agent pada field note dalam format yang didukung GitLab, lalu menjalankan handler | Event ter-scrub berisi user, tool, outcome, dan identitas agent |
merge_mr | Critical, dikonfigurasi disabled | Diblokir sebelum handler berjalan | Percobaannya, dicatat sebagai blocked |
Cloudflare menonaktifkan merge_mr karena merge biasanya memicu deployment pipeline dan manusia harus tetap dalam loop. Perhatikan arti disabled di sini: bukan bertanya ke orangnya, melainkan agent sama sekali tidak bisa melakukannya lewat jalur ini. Artikelnya tidak menjelaskan jalur approval-request untuk tool yang diblokir. Ia memblokir dan mencatat, lalu manusia melakukan merge di GitLab dengan kliknya sendiri.
Empat risk tier dan konfigurasi tiga pengaturan
Setiap tool mendapat tiga pengaturan: risk tier, flag enabled, dan konfigurasi labeling. Tier menentukan apakah sebuah aksi dicatat dan apakah call-nya diizinkan. Contoh Cloudflare untuk keempat tier adalah sebagai berikut.
- Read Only: mencari issue, membaca merge request, melihat status pipeline.
- Minimal Impact: menambah reaction, menandai notifikasi sudah dibaca, subscribe ke issue.
- Contained Write: menambah komentar, membuat merge request, mengubah field issue.
- Critical: melakukan merge, memicu deployment produksi, menghapus record secara massal.
Saat ini konfigurasinya berupa TypeScript di monorepo MCP internal Cloudflare, dan pemilik server diharapkan mengaturnya lewat MCP server portals nanti. Labeling menyisipkan atribusi ke field tertentu dalam format teks yang diterima aplikasi downstream; contoh di artikelnya menawarkan PLAIN_TEXT dan HTML. Satu perbedaan yang perlu diketahui: InfoQ menaruh penambahan komentar di Minimal Impact, sedangkan artikel Cloudflare menaruhnya di Contained Write dan contoh create_mr_note miliknya adalah Contained Write. Saya mengikuti artikel Cloudflare.
// Verbatim shape from Cloudflare's post: one tool, three settings
// (a risk tier, an enabled flag, and a labeling block).
const sendEmailTool = {
tool: EmailMCP.sendEmailTool,
writeGuard: {
riskLevel: RiskLevel.CONTAINED_WRITE,
enabled: true,
labeling: {
field: "body",
supportedFormats: [LabelFormat.PLAIN_TEXT, LabelFormat.HTML],
},
},
};
// My reconstruction of the GitLab walkthrough in the same shape. The post
// describes these three outcomes in prose; it does not publish this config,
// and the labeling field below is a placeholder, not GitLab's real name.
const merge_mr = {
tool: GitLabMCP.mergeMrTool,
writeGuard: {
riskLevel: RiskLevel.CRITICAL,
enabled: false, // blocked before the handler runs; the attempt is audited
},
};Simpan daftar tier Anda sendiri sebagai file yang bisa di-review di version control. Tier adalah penilaian manusia, jadi tool berlabel Contained Write yang sebenarnya mengubah banyak record lolos persis seperti tool kecil. Review perubahan tier seperti Anda me-review perubahan permission. Ini saran saya, bukan dari Cloudflare.
Identitas: satu manusia, tanpa akun agent
Server internal mengidentifikasi pengguna lewat Cloudflare Access dan OAuth, dan agent bertindak dengan permission karyawan tersebut. Cloudflare memilih tidak membuat akun agent tersendiri, agar tidak ada set permission kedua dan tanggung jawab tetap jelas. Aplikasi downstream jadi melihat kredensial manusia, dan WriteGuard menambahkan konteks MCP client dan session sehingga setiap write terbaca sebagai sesi agent yang bertindak untuk orang tertentu.
Konsekuensinya adalah pewarisan: agent bisa melakukan semua yang bisa dilakukan orangnya, dan itulah mengapa insiden ticket bisa terjadi. Service account memberi atribusi yang lebih bersih tetapi menambah model permission kedua yang cenderung melenceng dari yang pertama. Cloudflare memilih satu model, dan menurut saya itu tepat untuk tool internal, dengan akibat bahwa guard adalah satu-satunya yang mempersempit agent di bawah manusianya.
Audit trail: successful, failed, blocked
Setiap invocation diklasifikasikan sebagai successful, failed, atau blocked, dan event yang sudah di-scrub dikirim secara asynchronous ke audit Worker internal sehingga tidak menambah latency pada respons agent. Nilai pada key yang dianggap rahasia dihilangkan. Event memuat server, tool, risk tier, outcome, user, client, dan durasi. Log request portal sudah menampilkan tool invocation; WriteGuard menambahkan klasifikasi semantik, konteks agent, dan outcome dari server backend. InfoQ meringkasnya sebagai mengirim request yang gagal dan ditolak ke auditing, sedangkan artikel Cloudflare menyebut ketiga outcome; saya mengikuti artikelnya. Kode di bawah adalah sketsa saya untuk pola yang sama.
// A sketch of the same pattern for a server you own. This is MY code, not
// WriteGuard's: the post publishes no implementation, only the behaviour.
type Tier = "READ_ONLY" | "MINIMAL_IMPACT" | "CONTAINED_WRITE" | "CRITICAL";
type Outcome = "successful" | "failed" | "blocked";
interface ToolPolicy { tier: Tier; enabled: boolean; labelField?: string }
interface Ctx { server: string; user: string; client: string; sessionId: string }
const POLICY: Record<string, ToolPolicy> = {
get_merge_request: { tier: "READ_ONLY", enabled: true },
create_mr_note: { tier: "CONTAINED_WRITE", enabled: true, labelField: "body" },
merge_mr: { tier: "CRITICAL", enabled: false },
};
// The post says values under secret-looking keys are omitted. It does not say
// which fields carry values, so this logs argument keys with scrubbed values.
const SECRET_KEY = /token|secret|password|authorization|api[_-]?key/i;
const scrub = (args: Record<string, unknown>) =>
Object.fromEntries(
Object.entries(args).map(([key, v]) => [key, SECRET_KEY.test(key) ? "[omitted]" : v]),
);
export async function guarded(
ctx: Ctx,
tool: string,
args: Record<string, unknown>,
handler: (a: Record<string, unknown>) => Promise<unknown>,
) {
const started = Date.now();
const policy = POLICY[tool];
// Fire and forget: the audit write must never add latency to the agent.
const emit = (outcome: Outcome) =>
void fetch(process.env.AUDIT_URL!, {
method: "POST",
body: JSON.stringify({
server: ctx.server, tool, tier: policy?.tier ?? "UNCLASSIFIED",
outcome, user: ctx.user, client: ctx.client,
durationMs: Date.now() - started, args: scrub(args),
}),
}).catch(() => undefined);
// Default-deny is MY choice. The post does not say what happens to a tool
// nobody classified; an unlisted tool is the case that should fail closed.
if (!policy || !policy.enabled) {
emit("blocked");
throw new Error("blocked by write policy: " + tool);
}
const field = policy.labelField;
const next =
field && typeof args[field] === "string"
? { ...args, [field]: args[field] + "\n\n[written by an AI agent for " +
ctx.user + " via " + ctx.client + ", session " + ctx.sessionId + "]" }
: args;
try {
const result = await handler(next);
emit("successful");
return result;
} catch (err) {
emit("failed");
throw err;
}
}Tiga hal dalam sketsa itu berbeda dari prompt di client. Pemblokiran terjadi sebelum handler, penulisan audit tidak di-await, dan tool yang tidak terdaftar ditolak. Pilihan terakhir itu keputusan saya: artikelnya tidak menyebut apa yang terjadi pada tool yang belum diklasifikasikan siapa pun. Fire-and-forget juga berarti event bisa hilang jika endpoint audit sedang mati, dan Cloudflare menyebut jaminan delivery audit yang dibutuhkan pelanggan sebagai hal yang masih harus divalidasi.
Batasnya: trade-off dan pertanyaan terbuka
WriteGuard adalah private beta untuk Cloudflare MCP server portals, dengan rollout bertahap dalam beberapa bulan ke depan. Cloudflare menyebut masih harus memvalidasi bagaimana risk model dipetakan ke tool pelanggan, aplikasi downstream mana yang butuh format atribusi mana, dan jaminan delivery audit seperti apa yang dibutuhkan pelanggan. InfoQ mencatat artikelnya tidak memberi angka overhead performa, harga, atau kriteria rinci penentuan tier selain contoh. Saya juga tidak menemukannya, jadi tidak ada yang bisa saya berikan.
Tiga celah muncul dari desain yang dijelaskan, dan ini inferensi saya, bukan klaim Cloudflare. Klasifikasi dilakukan per call dan secara manual, jadi jika tool yang menutup satu ticket digolongkan Contained Write, loop sepuluh ribu penutupan lolos sepuluh ribu pemeriksaan; artikelnya tidak menjelaskan batas volume. Guard hanya mengatur traffic yang sampai kepadanya. Dan dokumentasi portal mencantumkan batasan yang perlu diketahui: hanya remote HTTP server, maksimal 80 server per portal, dan tanpa MFA independen atau purpose justification untuk server yang diotorisasi lewat portal.
Guard hanya sekuat jalur yang melewatinya. Dokumentasi portal memperingatkan bahwa pengguna yang diblokir tetap bisa terhubung ke server lewat URL langsung dan melewati Access policy, kecuali Access dikonfigurasi sebagai OAuth provider server tersebut. Dokumen tidak menjelaskan perilaku WriteGuard pada koneksi langsung, jadi pastikan tidak ada server yang bisa menulis dan dapat dijangkau tanpa melewati portal.
Jalankan insiden ticket itu secara jujur. Dengan WriteGuard seperti yang dijelaskan, Anda akan melihat sesi agent mana yang bertindak untuk engineer mana, dan pemilik bisa menonaktifkan tool penutup ticket sepenuhnya. Anda tidak otomatis terhenti dari ribuan write yang masing-masing diizinkan. Yang didapat adalah atribusi dan kill switch; pembatasan laju masih harus Anda tambahkan sendiri.
Yang dilakukan hari Senin
Anda mungkin tidak pernah mendapat WriteGuard, dan polanya bisa dipindahkan. Lima langkah, berurutan.
- Daftar setiap tool yang bisa menulis di semua MCP server Anda dari hasil tools/list yang disimpan, dengan menerapkan default spec seperti di bawah.
- Beri setiap tool salah satu dari empat tier dalam file di version control, dan biarkan apa pun yang tidak bisa Anda klasifikasikan dalam keadaan disabled.
- Nonaktifkan tool yang efeknya memicu pipeline, deployment produksi, atau penghapusan massal, dan sisakan klik manusia untuk itu.
- Taruh pemeriksaan di gateway atau server, sebelum handler, tempat operator agent tidak bisa mematikannya. Pertahankan prompt di client sebagai lapisan tambahan, jangan sebagai policy.
- Catat server, tool, tier, outcome, user, client, dan durasi untuk setiap call, termasuk yang diblokir, dan baca percobaan yang diblokir setiap minggu.
# tools-list.json is a saved tools/list result. The inventory question is
# "which tools can write, and which of those might be destructive?"
# MCP spec defaults: readOnlyHint is false and destructiveHint is true, so a
# tool with NO annotations must be read as a destructive write.
# Wrong: jq treats false as "missing", so an explicit destructiveHint of
# false is replaced by true. A harmless additive tool is reported dangerous.
jq -r '.result.tools[] | [.name, (.annotations.destructiveHint // true)] | @tsv' tools-list.json
# get_merge_request true
# create_mr_note true <- declared destructiveHint false, reported true
# merge_mr true
# Right: test for null explicitly and apply the spec defaults.
jq -r '.result.tools[] | .annotations as $a
| [.name, ($a.readOnlyHint // false),
(if $a.destructiveHint == null then true else $a.destructiveHint end)]
| @tsv' tools-list.json
# get_merge_request true true (read-only; destructive is meaningless)
# create_mr_note false false (additive write)
# merge_mr false true (no annotations: assume destructive)Jika Anda pelanggan Cloudflare, minta akses private beta dan pakai tiga pertanyaan terbuka dari bagian 7 sebagai daftar validasi Anda. Jika tidak, sketsa di atas sekitar lima puluh baris, tetapi tanpa test, tanpa batas laju, dan dengan scrubber sederhana, jadi anggap sebagai titik awal, bukan kode produksi.
Aturan yang saya bawa dari sini: otorisasi harus berada di tempat yang tidak bisa dimatikan pemanggilnya. Prompt bertanya, sedangkan tabel tier di sisi server memutuskan. Pertahankan keduanya jika mau, tetapi tulis policy hanya di yang kedua, gunakan default deny, dan pastikan setiap write menyebut agent milik siapa yang membuatnya.
Sumber dan bacaan lanjutan
- Blog Cloudflare: WriteGuard, kontrol granular untuk MCP server (5 Agustus 2026)
- InfoQ: Cloudflare WriteGuard menambah kontrol keamanan granular untuk MCP server
- Dokumentasi Cloudflare: MCP server portals
- Spesifikasi MCP 2025-06-18: Tools
- Spesifikasi MCP 2025-06-18: Elicitation
- MCP schema.ts 2025-06-18 di GitHub: ToolAnnotations

