Lewati ke konten

Sandbox dan keamanan

Agen membaca berkas, menjalankan perintah shell, dan memanggil API eksternal atas nama Anda. Kegagalan yang Anda khawatirkan jarang berupa niat jahat — melainkan agen yang salah membaca jalur lalu menulis ulang checkout kerja Anda, atau agen yang mendorong cabang yang belum selesai Anda tinjau. Jadi pertanyaan yang harus dijawab produk ini bukan “apakah agen ini dapat dipercaya” melainkan “seberapa besar radius dampaknya saat agen salah”.

Control Center menjawabnya secara berlapis, dan lapisan-lapisan itu sengaja dibuat independen. Jika satu hilang, yang lain tetap bertahan. Itu penting, karena lapisan terluar — sandbox OS — adalah satu-satunya yang bersyarat: ia bergantung pada host yang menyediakan backend, dan di Windows tidak ada host yang menyediakannya.

Ada lima hal yang membatasi eksekusi agen di setiap host, dengan atau tanpa sandbox. Tidak satu pun merupakan batas kernel; semuanya nyata. Bila host menyediakan backend, sandbox OS berada di atasnya, dan “Apa yang ditambahkan sandbox dan di mana batasnya” di bawah membahas apa yang berubah karenanya.

Worktree copy-on-write-nya sendiri. Setiap percakapan menyediakan checkout-nya sendiri untuk tiap repo yang ditautkan, diklon secara copy-on-write melalui rift native yang dibundel alih-alih disalin byte demi byte, sehingga isolasinya cukup murah untuk dijadikan bawaan. Repositori terdaftar yang Anda tambahkan ke ruang kerja bukanlah direktori tempat agen bekerja — dua percakapan yang menyentuh repo yang sama tidak dapat melihat suntingan satu sama lain dan keduanya tidak menyunting checkout Anda.

Butirannya perlu diperjelas: worktree bersifat per percakapan, bukan per agen. Dua agen dalam percakapan yang sama mendapat direktori kerja overlay yang berbeda yang berbagi repos/ percakapan tersebut — dan memang itu tujuannya, karena keduanya berkolaborasi pada satu perubahan — sementara direktori agen privat masing-masing tetap terpisah. Copy-on-write adalah satu-satunya backend di mana pun rift tersedia: sistem berkas yang tidak dapat melakukan reflink, atau direktori data pada volume berbeda dari repo, menggagalkan penyediaan alih-alih beralih ke cara lain. Windows adalah pengecualian — tidak ada backend copy-on-write di sana, jadi git worktree adalah backendnya, bukan degradasi diam-diam.

Pembatasan jalur di alat berkas harness bawaan. Alat read, write, edit, apply_patch, search, find, dan search_files milik harness menyelesaikan setiap jalur terhadap direktori kerja eksekusi plus akar bersama — direktori repos/ percakapan, tempat worktree berada, dan direktori lampiran space bila ada — dan menolak apa pun yang berada di luarnya. Jalur absolut hanya diterima jika memang sudah berada di dalam. Symlink di dalam worktree yang menunjuk ke luar juga diselesaikan lalu ditolak. Inilah sebabnya agen tidak dapat write ke ~/.ssh bahkan tanpa sandbox sama sekali.

Permukaan alat milik mode. Mode sebuah percakapan menentukan alat mana yang diwujudkan untuk eksekusi tersebut. Dalam mode hanya-baca (plan, review, orchestrate), alat yang mengubah keadaan memang tidak ada di registri dan kebijakan perintah shell untuk mode tersebut menolak setiap perintah yang mengubah keadaan secara langsung — git commit, git push, npm install, docker, dan lainnya — alih-alih menanyakannya. Alat yang tidak ada tidak dapat dipanggil, dan itu jaminan yang lebih kuat daripada alat yang ada tetapi dijaga.

Pagar pengaman tindakan yang terpadu. Setiap alat yang mengubah keadaan mendeklarasikan kelas efek yang dapat dihasilkannya dan mesin kebijakan menyelesaikan masing-masingnya menjadi izinkan, tanya, atau tolak sebelum panggilan berjalan. Sebuah prompt tanpa penyetuju yang terhubung diselesaikan menjadi ditolak — pertanyaan yang tidak dapat dijawab siapa pun tidak pernah menjadi ya. Lihat Pagar pengaman untuk model penyelesaiannya.

Kredensial dari broker dan lingkungan yang disanitasi. Kredensial dibuat per peluncuran dan dicabut saat eksekusi berakhir, dan token GitHub yang dipegang eksekusi dapat membaca tetapi tidak dapat push — lihat “Push melewati Control Center” di bawah. ACP, bash harness, dan Claude Code di bawah pembungkus native mendapat lingkungan yang dibersihkan dari vektor injeksi dynamic-linker dan interpreter (LD_PRELOAD, DYLD_INSERT_LIBRARIES, NODE_OPTIONS, GIT_SSH_COMMAND, dan sejenisnya) sebelum proses dimulai. Claude Code melalui NoSandboxAdapter mewarisi lingkungan host.

Alat berkas milik harness berjalan di dalam proses server, bukan di proses anak, sehingga tidak ada profil sandbox yang dapat membatasinya bahkan jika dipasang — hanya bash yang melewati runner perintah yang di-sandbox. Pembatasannya adalah pemeriksaan jalur yang dijelaskan di atas, dan karena itu pemeriksaan tersebut bersifat menopang, bukan sekadar lapisan pertahanan kedua.

Alat bash adalah tempat lain yang perlu ketelitian. Perintahnya dievaluasi terhadap kebijakan perintah mode lalu dijalankan dengan lingkungan yang disanitasi dan worktree sebagai direktori kerjanya. Bila backend tersedia, proses itu dibungkus, jadi sandbox-lah yang mencegah perintah shell menulis di luar worktree; bila tidak ada — Windows, atau Linux tanpa bwrap dan socat — kebijakan perintah adalah satu-satunya yang berdiri antara perintah dan sisa disk. Dalam mode chat kebijakan menolak secara keras daftar tetap (eskalasi hak istimewa, penghancuran sistem dan disk, pelarian kontainer dan namespace) dan menanyakan mutasi keadaan jarak jauh (git push, gh pr create, publikasi dan pemasangan paket). Apa pun yang tidak ada di kedua daftar itu akan berjalan. Pipeline, rantai, dan sub-shell diurai dan tiap sub-perintah dievaluasi terpisah, dengan keputusan paling ketat yang menang.

Claude Code, saat dipakai sebagai adapter, adalah kasus ketika sandbox memikul beban terbesar. Control Center meluncurkannya dengan --dangerously-skip-permissions, karena claude -p yang non-interaktif akan tersendat selamanya pada prompt persetujuannya sendiri. Panggilan baca, tulis, dan edit-nya terjadi di dalam prosesnya sendiri dan Control Center tidak pernah melihatnya. Panggilan Bash-nya dikirim, sebelum dijalankan, ke hook di server yang mengklasifikasikan baris perintah dan menanyakan aturan yang cocok, dan alat mcp__* yang dilayani Control Center melewati gerbang seperti biasa. Bila backend tersedia, panggilan tak terlihat itu tetap terjadi di dalam pembungkus sandbox, jadi OS-lah batas di sekelilingnya. Bila tidak ada, tidak ada apa pun. Setiap adapter menyatakan hal ini dalam matriks penegakannya — lihat matriks kejujuran adapter.

Tidak ada flag izin per agen. Apakah agen boleh membuat commit, melakukan push, membuka pull request, menulis ke pelacak, atau menjangkau jaringan ditentukan oleh pagar pengaman — izinkan, tanyakan dulu, atau tolak — dan aturan yang sama berlaku adapter mana pun yang menjalankan agen.

Push adalah aksi yang harus tetap bertahan bahkan terhadap agen yang mencoba mengakali aturan, jadi push ditegakkan lewat apa yang dipegang agen, bukan lewat apa yang dijalankannya. Dengan GitHub App yang terkonfigurasi, eksekusi di worktree GitHub mendapat token instalasi bercakupan repo yang dapat membaca konten dan bekerja dengan pull request, tetapi tidak dapat push. Konfigurasi git eksekusi menulis ulang setiap URL push GitHub (https://github.com/, git@github.com:, ssh://git@github.com/) ke gateway run agen, sebuah endpoint loopback di cc_server. Gateway membaca ref mana yang diperbarui oleh push, menanyakan aturan Push ke remote — sehingga aturan yang dibatasi ke branch, seperti “tolak push ke main”, ikut berlaku — dan baru setelah itu meneruskan push ke GitHub dengan token tulis yang dibuatnya untuk keperluan itu dan tidak pernah diserahkan ke agen. Push yang ditolak muncul di keluaran git itu sendiri:

! [remote rejected] main -> main (Control Center policy: …)

Gateway hanya meneruskan push ke repo milik worktree itu sendiri atau ke repo GitHub yang ditautkan ke ruang kerja. Fetch langsung ke GitHub dengan token baca.

Perintah shell lainnya menemui gerbang yang lebih lunak. Alat bash milik harness dan, melalui hook, alat Bash milik Claude Code mengklasifikasikan setiap baris perintah dan menanyakan aturan yang menaunginya: git commit, merge, rebase, cherry-pick, revert, dan am adalah Buat commit; gh pr create adalah Buka pull request; gh pr merge, review, comment, dan close adalah Publikasikan review atau merge; gh issue create, edit, comment, dan close adalah Tulis ke pelacak eksternal; instalasi paket adalah Instal paket; curl dan wget adalah Akses jaringan. Ini mengenali penulisan lazim sebuah aksi, dan agen yang bertekad menyembunyikannya (eval, berkas skrip, panggilan API langsung) bisa melakukannya. Push tidak bergantung pada hal ini.

Tidak ada kunci API tiket yang ditaruh di sandbox. Agen menulis tiket melalui alat MCP Control Center, yang menanyakan Tulis ke pelacak eksternal.

Aturan Akses jaringan dibaca sekali, saat eksekusi dimulai. Jaringan sandbox hanya bisa menyala atau mati, jadi tolak mematikannya sementara izinkan dan tanyakan dulu membiarkannya menyala; alat web_fetch dan web_search milik harness tetap bertanya di setiap panggilan. Kredensial dicabut saat eksekusi dibongkar, bersama setiap token tulis yang dibuat gateway, jadi handle tidak hidup lebih lama daripada eksekusi.

Apa yang ditambahkan sandbox dan di mana batasnya

Bagian berjudul “Apa yang ditambahkan sandbox dan di mana batasnya”

Enum SandboxBackend memiliki tiga nilai — native (Seatbelt di macOS, bubblewrap di Linux dan WSL2), none, dan microvm. Pembungkusan perintah agen hanya memakai dua yang pertama: microvm adalah backend VM tertutup untuk terminal interaktif dan rigs, bukan peningkatan diam-diam dari pembungkus bash. Memahami bentuk native menjelaskan apa yang Anda dapatkan saat backend terpasang dan apa yang memang tidak akan pernah diberikannya.

cc_server menyelidiki host sekali saat boot dan bila backend merespons ia mengisi kedua titik sambung yang dimiliki jalur pengiriman, karena transportnya tidak berbagi satu. Yang pertama (SandboxPort) membungkus CLI Claude Code. Yang kedua (SandboxManager) membungkus adapter ACP dan alat bash harness bawaan. Mengisi hanya salah satu akan membuat yang lain dijalankan tanpa pembungkus, dan itulah yang dahulu terjadi. Di host tanpa backend, atau di bawah --sandbox off, semuanya dijalankan tanpa pembungkus dan server menyebutkan kasus mana yang Anda alami di baris startup-nya. ACP dan bash harness tetap menyanitasi lingkungan; Claude Code melalui NoSandboxAdapter mewarisi lingkungan host.

Satu permukaan sengaja memilih keluar: terminal yang dihosting server menerima argumen backend per sesi dan cc_server membangunnya dengan bawaan shell host, jadi terminal yang Anda buka dari aplikasi berjalan tanpa sandbox kecuali klien menyebut native secara eksplisit. Itu adalah shell sungguhan di host server, bukan eksekusi agen.

Di Linux, bubblewrap membangun namespace mount, sehingga agen benar-benar hanya melihat apa yang di-bind ke dalamnya. Di macOS, sandbox-exec menerapkan profil Seatbelt yang permisif secara bawaan — profil yang sepenuhnya menolak secara bawaan membuat shell interaktif tidak dapat dipakai, karena jumlah panggilan dyld, XPC, dan Mach yang harus dienumerasi tidak praktis. Karena itu profil macOS dimulai dari (allow default) lalu memahat penolakannya: pembacaan jalur rahasia, kebijakan tulis yang diatur ulang ke tolak dengan izin eksplisit di atasnya, pemblokiran unlink pada setiap jalur yang ditolak beserta leluhurnya agar rename tidak dapat melewati penolakan tulis, penolakan exec untuk biner yang selalu berbahaya dan untuk apa pun yang disalin ke direktori yang dapat ditulis, serta jaringan yang dibatasi ke port proksi loopback plus DNS.

Konsekuensi praktisnya: di Linux pandangan agen terhadap sistem berkas dipersempit; di macOS agen masih dapat membaca sebagian besar host dan checkout terdaftar Anda dilindungi oleh penolakan tulis dan rename yang eksplisit, bukan oleh ketaknampakan. Keduanya adalah isolasi tingkat namespace, bukan batas keamanan kernel — para backend menyatakannya di catatan kemampuan mereka masing-masing.

Himpunan bind-mount itulah tempat batas antaragen ditarik. Pengiriman overlay (Claude Code dan ACP) me-mount hingga empat jalur, yang diselesaikan per eksekusi: direktori kerja overlay agen dengan akses baca-tulis, direktori konfigurasi agen dengan akses hanya-baca, direktori repos/ bersama milik percakapan dengan akses baca-tulis bila ada, dan direktori lampiran space dengan akses hanya-baca bila ada. Direktori agen saudara tidak pernah di-mount. Pembungkus bash harness lebih sempit: ia hanya me-bind-mount direktori kerja eksekusi. Ini bentuk yang sama dengan yang ditegakkan pemeriksaan jalur dalam proses milik harness di perangkat lunak, jadi keduanya sejalan — tetapi hanya pemeriksaan jalur yang mencakup alat berkas milik harness sendiri, yang sama sekali tidak pernah mencapai namespace mount.

Checkout terdaftar Anda bergabung ke himpunan tolak-tulis di atas itu, dalam setiap mode dan pada ketiga jalur yang dibungkus (Claude Code, ACP, bash harness), dan tiap aturan dikeluarkan dalam ejaan literalnya maupun ejaan yang sudah diselesaikan symlink-nya. Ejaan kedua bukan sekadar kerapian: kernel mencocokkan aturan tolak terhadap jalur yang sudah diselesaikan dan macOS menjadikan penyelesaian itu kasus umum, bukan kasus langka — /tmp sebenarnya /private/tmp, /var/folders/… sebenarnya /private/var/folders/… — sehingga checkout yang dicapai lewat keduanya akan dapat ditulis padahal profil tampak melarangnya.

Jaringan, di bawah sandbox, adalah daftar izin domain per sesi yang ditegakkan oleh proksi loopback dalam proses. Egress ditolak secara bawaan: saat jaringan mati, proksi tetap dimulai tetapi daftar izinnya kosong, jadi setiap koneksi keluar ditolak. Saat menyala, resolver menyusun daftar izin dari baseline terkurasi — penyedia API LLM, hosting git, registri paket, katalog model — plus domain GitHub, lalu mencocokkan host tujuan setiap koneksi dengannya. Wildcard berfungsi (*.example.com cocok dengan subdomain, bukan apex-nya). Daftar yang selalu ditolak terpisah menang atas daftar izin dan mencakup endpoint metadata cloud serta tujuan telemetri. Yang tidak dapat dikonfigurasi adalah daftar itu sendiri: spesifikasi membawa bidang egressAllowlist untuk domain tambahan, tetapi tidak ada apa pun di jalur pengiriman yang mengisinya, jadi dalam praktiknya setiap eksekusi mendapat baseline plus GitHub dan tidak lebih.

Penolakan, di macOS, dapat diamati: sebuah monitor menyadap log sistem untuk penolakan Sandbox, menyaring derau yang dikenal, menghilangkan duplikat badai percobaan ulang, dan memunculkan masing-masingnya sebagai SandboxViolation yang menyebutkan apa yang dicoba dan apa yang diblokir. Di Linux jalur pembungkus mengurai stderr untuk EPERM, Operation not permitted, dan Permission denied lalu memancarkan jenis peristiwa yang sama — tidak ada penyadapan log sistem. Peristiwa itu tiba di aliran peristiwa milik eksekusi, sehingga panggilan sistem yang ditolak muncul di transkrip, bukan hanya di log. Telemetri ini hanya ada selama sandbox native benar-benar membungkus proses — di host tanpa backend tidak ada apa pun untuk diamati. Ini juga tidak berkaitan dengan pagar pengaman: pelanggaran berasal dari panggilan sistem yang ditolak, tidak pernah dari pencarian aturan.

Konfirmasi berasal dari pagar pengaman, bukan sandbox

Bagian berjudul “Konfirmasi berasal dari pagar pengaman, bukan sandbox”

Sandbox tidak punya jalur persetujuan. Ia meluncurkan, mengeksekusi, memancarkan peristiwa, lalu membongkar; tidak ada tempat di dalamnya bagi manusia untuk menjawab pertanyaan. Saat Anda melihat konfirmasi di sebuah space — setujui push ini, setujui penghapusan ini — konfirmasi itu dimunculkan oleh mesin pagar pengaman (atau kebijakan perintah harness) sebelum tindakan itu mencapai runtime sama sekali.

Urutan ini disengaja. Persetujuan yang terjadi setelah panggilan sistem hanyalah catatan audit, bukan gerbang. Itu juga sebabnya aturan gagal-tertutup berada di pagar pengaman: bila tidak ada penyetuju yang terhubung, keputusan dibuat sebelum apa pun berjalan dan hasilnya adalah penolakan.

Jalankan diagnosis agen adalah satu-satunya tempat yang menyatakan apa yang dapat diperiksa secara manual.