Cara Menjaga Kunci API dari Agen AI
Agen AI dapat membocorkan kunci API dalam satu pemanggilan alat. Gunakan token terbatas berumur pendek melalui gateway kredensial, bukan kunci asli.
Apa arti menjaga rahasia agar tidak diakses agen AI
Agen AI adalah proses Linux normal yang menjalankan perintah. Setiap variabel lingkungan yang dimiliki proses tersebut dapat dibaca oleh kode yang dijalankannya, sehingga kunci API dalam lingkungan agen adalah kunci yang dapat dikirimkan agen ke host mana pun yang dapat dijangkaunya. Menjaga rahasia agar tidak diakses agen berarti memberikan handle alih-alih kunci tersebut: token dengan cakupan terbatas dan masa berlaku singkat, atau placeholder yang akan ditukar oleh sistem lain dengan nilai sebenarnya di batas jaringan.
Ini bukan cerita tentang model yang berubah menjadi jahat. Mekanismenya jauh lebih sederhana. Agen membaca halaman web, README, atau komentar issue yang berisi instruksi, lalu agen tersebut mengikutinya, karena bagi model bahasa tidak ada perbedaan antara teks yang Anda tulis dan teks yang diambilnya. Itulah prompt injection. Setelah hal itu terjadi, kerusakan dibatasi oleh satu hal saja: apa yang dapat dibaca oleh proses tersebut. Jika Anda belum menetapkan batasan, menjalankan agen pengodean dengan aman di server membahas tingkatan isolasi yang menjadi dasar panduan ini.
Model ancaman dalam istilah sederhana
Jalankan ini sebagai pengguna yang menjalankan agen Anda.
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'Setiap baris yang dicetaknya berjarak satu permintaan HTTP dari server orang asing. Sekarang perhatikan apa yang ada di disk di dekat agen tersebut.
grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'Agen dengan akses shell tidak memerlukan eksploitasi cerdas untuk memindahkan data tersebut. Empat jalur biasa dapat melakukan pekerjaan itu, dan keempatnya terlihat seperti aktivitas normal di log:
curlataufetchkeluar ke host mana pun, dengan nilai di dalam query string.git commitdangit pushke repositori yang dapat ditulis oleh agen.- Skrip instalasi paket, yang menjalankan kode arbitrer sebagai pengguna agen.
- Pencarian DNS untuk hostname yang berisi nilai tersebut, yang tetap terkirim meskipun akses HTTP keluar diblokir.
Anda tidak dapat menyelesaikan masalah ini hanya dengan meninjau kode. Perbaikannya adalah memastikan tidak ada data berharga yang berada dalam jangkauan.
Rahasia di working tree menjadi rahasia dalam jendela konteks
Agen membaca file. File .env di repositori tempat agen bekerja akan dibaca. Setelah dibaca, file tersebut masuk ke jendela konteks. Artinya, file tersebut ada dalam transkrip, dalam log apa pun yang Anda simpan, dan dalam apa pun yang ditulis agen berikutnya.
Sebelumnya, saat key berada di tree tempat agen bekerja:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingSetelah file dipindahkan ke luar jangkauan:
sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.envPengguna agen tidak dapat lagi membuka file tersebut karena working tree tidak lagi memuatnya. Aturan deny dalam konfigurasi agen sendiri merupakan lapisan kedua, bukan lapisan pertama. Claude Code membaca aturan izin dari .claude/settings.json di dalam project:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}Aturan tersebut mencegah kesalahan agen saat membuka file ketika melakukan eksplorasi. Aturan itu juga berlaku di luar file tools: Claude Code memeriksa aturan ini terhadap perintah file yang dikenali dalam Bash, seperti cat dan head, serta terhadap target redirection seperti < .env. Aturan tersebut tidak mencegah instruksi yang diinjeksi menjalankan grep -r KEY . di dalam direktori project, atau mencegah skrip Python singkat membuka .env secara langsung. Dokumentasi permission menyatakan bahwa aturan deny tidak berlaku untuk perintah yang membaca file tanpa menyebutkan namanya atau untuk subprocess yang membuka file secara mandiri. Untuk kasus tersebut, dokumentasi mengarahkan ke sandbox Claude Code, yang menerapkan pemblokiran pada tingkat sistem operasi. Apakah Anda diminta memberikan persetujuan sebelum perintah semacam itu dijalankan bergantung pada permission mode sesi. auto mode kini menjadi mode awal bawaan untuk sesi Claude Code interaktif, sehingga server yang tidak Anda awasi akan menjalankan lebih banyak perintah tersebut tanpa meminta persetujuan. Batasan yang sama berlaku untuk hal-hal yang membentuk kebiasaan agen, bukan izinnya: skill yang membatasi agen pada perubahan terkecil yang berfungsi mencegah proses menjangkau file yang tidak perlu dibukanya, tetapi hal itu tetap merupakan saran yang dapat diabaikan oleh model. Perlakukan konfigurasi sebagai pagar pengaman dan permission filesystem sebagai dinding. Pemisahan yang sama berlaku di dalam container: env file dan secret di Docker Compose membahas versi masalah ini pada lapisan yang lebih rendah.
Beri setiap agent user unprivileged sendiri
Jika agent berjalan sebagai user Anda, agent tersebut mewarisi SSH keys, cloud credentials, dan shell history Anda. User terpisah hanya memerlukan satu perintah dan menghilangkan semua akses tersebut.
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519Baris terakhir harus gagal dengan cat: /home/you/.ssh/id_ed25519: Permission denied. Jika baris tersebut mencetak key, user agent dapat mengakses file itu. Membuka file berdasarkan namanya memerlukan permission search (x) pada setiap direktori di atasnya, bukan permission read. Karena itu, home directory dengan mode 711 yang tidak dapat dilihat isinya oleh user lain tetap dapat memberikan akses ke key jika file key itu sendiri dapat dibaca. chmod 700 ~ menutup jalur tersebut, dan chmod 600 ~/.ssh/id_ed25519 memperbaiki key. Jangan menambahkan user agent ke sudo, dan jangan memberinya aturan NOPASSWD yang lebih luas daripada satu perintah yang benar-benar dibutuhkannya. User dengan hak akses minimum pada VPS membahas detail group dan sudoers. Pertahankan pemisahan ini setelah Anda menjalankan lebih dari satu session pada server, karena satu session Claude Code dapat mengirim teks langsung ke session lain, dan apa pun yang disimpan session pertama dapat melewati saluran tersebut dalam satu pesan.
Ada satu batasan lain yang perlu ditambahkan pada cloud VPS. Layanan instance metadata merespons pada alamat link local yang tetap, dan sering memberikan role credentials kepada apa pun yang memintanya. Endpoint tersebut tidak memeriksa user lokal mana yang melakukan permintaan. Pada EC2, AWS mendokumentasikan bahwa panggilan IMDSv1 tidak memerlukan token. AWS juga menyarankan local firewall rule berdasarkan ID user untuk mencegah satu user mengakses layanan tersebut.
sudo iptables -I OUTPUT 1 -p tcp -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTMasukkan rule di posisi teratas dengan -I, bukan menambahkannya di bagian akhir menggunakan -A. Pada host yang menjalankan ufw dengan kebijakan default allow outgoing, chain OUTPUT ufw menerima setiap koneksi TCP baru pada rule terakhir. Akibatnya, rule yang ditambahkan di bagian akhir tidak pernah tercapai. Rule iptables juga hilang setelah reboot kecuali Anda menyimpannya secara persisten. Jika Anda mengaktifkan endpoint IPv6 layanan metadata, [fd00:ec2::254], tambahkan rule yang sama menggunakan ip6tables.
Periksa dari sisi agent. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ harus tidak mencetak apa pun dan keluar dengan status non zero karena packet ditolak sebelum meninggalkan server. Jika perintah tersebut mencetak listing, rule belum tercapai.
Suntikkan kredensial pada batas layanan
Pola yang benar-benar menyelesaikan masalah ini adalah injeksi kredensial. Agent tidak pernah menyimpan key yang sebenarnya. Agent mengirimkan request melalui gateway lokal, lalu gateway mengganti placeholder dengan secret yang sebenarnya saat request keluar. Secret disimpan di storage gateway, dalam proses yang berbeda dan dimiliki oleh user yang berbeda.
OneCLI adalah salah satu implementasi open source untuk pola ini. OneCLI menggunakan lisensi Apache-2.0, kecuali direktori ee/ yang menggunakan lisensi enterprise terpisah dan memerlukan subscription untuk penggunaan production. OneCLI berjalan dalam container di samping agent. Per Oktober 2026, dokumentasi project ini menyediakan setup Docker Compose yang membaca tiga secret yang dibuat sebelumnya dari file .env di sebelah file compose:
git clone https://github.com/onecli/onecli.git && cd onecli/docker
cat > .env <<EOF
SECRET_ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
GATEWAY_INTERNAL_SECRET=$(head -c 32 /dev/urandom | base64)
BETTER_AUTH_SECRET=$(head -c 32 /dev/urandom | base64)
COMPOSE_PROFILES=runner
EOF
chmod 600 .env
docker compose up -d --waitBuat backup file .env tersebut, karena kehilangan SECRET_ENCRYPTION_KEY akan membuat semua secret yang disimpan bersamanya menjadi orphan. Dashboard mendengarkan pada port 10254, sedangkan gateway mendengarkan pada port 10255. Keduanya secara default terikat ke 127.0.0.1. Simpan kredensial yang sebenarnya satu kali, lalu berikan setiap agent nilai placeholder sebagai pengganti key dan access token yang cakupannya hanya untuk agent tersebut. Agent mengirimkan token itu dalam header Proxy-Authorization. Gateway mencocokkan request keluar berdasarkan host dan path, mendekripsi kredensial yang sesuai, lalu menggantinya. Environment agent tidak menyimpan apa pun yang bernilai bagi penyerang.
Nilai utama pendekatan ini bukan enkripsinya. Nilainya adalah pertanyaan "kredensial apa yang digunakan agent ini, dan kapan" dapat dijawab melalui query log. Anda cukup membaca satu audit trail, bukan menebak environment mana dari enam environment yang menyimpan salinan key.
Serahkan secret kepada proses, bukan ke environment
Jika Anda menjalankan agent dengan systemd, Anda sama sekali tidak memerlukan environment variable. LoadCredential= menempatkan secret dalam direktori privat yang hanya dapat dibaca oleh user service, yang diekspos sebagai %d dalam unit file dan sebagai $CREDENTIALS_DIRECTORY di dalam proses. Pembatasan ini berlaku berdasarkan user, bukan service. Karena itu, gunakan user terpisah untuk setiap service agent: proses lain yang berjalan sebagai user yang sama dapat membuka direktori tersebut, kecuali unit juga menggunakan filesystem namespacing, seperti PrivateMounts=yes, yang menyembunyikan direktori itu dari service lain. Nilainya tidak pernah muncul di /proc/<pid>/environ, sehingga ps eww tidak dapat menampilkannya, dan direktori tersebut dihapus ketika service berhenti.
Enkripsi credential untuk mesin terlebih dahulu. Perintah berikut berasal dari dokumentasi systemd dan berfungsi pada systemd 250 atau yang lebih baru. Versi ini mencakup Ubuntu 24.04 dan Debian 13:
echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
systemd-creds cat api_keyPerintah terakhir menampilkan sk-example-value. Ini membuktikan bahwa file terenkripsi dapat didekripsi pada host ini. Kemudian, referensikan file tersebut dari unit:
[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-workerKode agent Anda membuka file di $AGENT_KEY_FILE saat membutuhkan nilainya. Pembacaan file hanya berlangsung sesaat. Environment variable bertahan selama proses berjalan, termasuk di setiap child process yang dibuatnya.
Utamakan token berumur pendek daripada kunci berumur panjang
Kunci yang tidak pernah kedaluwarsa akan tetap valid kapan pun kunci tersebut muncul, bahkan berbulan-bulan kemudian, di dalam log atau transkrip. Jika layanan menawarkan token sesi, gunakan token sesi tersebut dan atur masa berlaku sesingkat mungkin sesuai kebutuhan pekerjaan Anda.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900Lima belas menit adalah durasi minimum yang diterima oleh AWS STS (security token service), dan durasi ini biasanya cukup untuk satu tugas agen. Untuk GitHub, berikan pengguna agen login gh miliknya sendiri dengan token yang memiliki cakupan terbatas (fine-grained) pada satu repositori tempat ia bekerja, sehingga gh auth token di dalam sesi tersebut akan menghasilkan sesuatu yang tidak dapat mengakses hal lain. Lakukan pembatasan berdasarkan sumber daya terlebih dahulu, kemudian berdasarkan waktu.
Verifikasi, lalu terus verifikasi
Tiga pemeriksaan layak dijalankan setelah setiap perubahan pada konfigurasi agent. Jalankan pemeriksaan tersebut sebagai user agent, bukan sebagai user Anda sendiri.
sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/userPerintah pertama seharusnya tidak menampilkan apa pun. Perintah kedua seharusnya menampilkan ls: cannot open directory '/home/you/': Permission denied, tetapi itu hanya menunjukkan bahwa direktori tidak dapat dicantumkan; pengujian cat sebelumnya yang membuktikan bahwa file di dalamnya tidak dapat dibaca. Perintah ketiga menunjukkan identitas yang disajikan oleh jalur jaringan agent. Inilah pertanyaan yang hendak dijawab oleh pola gateway: 401 berarti permintaan mencapai GitHub tanpa kredensial yang diterima GitHub, baik karena tidak ada kredensial maupun karena kredensial tersebut tidak valid atau sudah kedaluwarsa. 200 berarti permintaan membawa kredensial yang valid, sehingga Anda perlu mengetahui token yang digunakan. Jika agent dijalankan tanpa pengawasan, mengendalikan biaya AI agent pada VPS membahas batas anggaran yang melengkapi batas akses ini.
FAQ
Bisakah saya sekadar memercayai model agar tidak membocorkan kunci saya?
Tidak, karena model bukanlah penyerang dalam model ancaman ini. Agen membaca teks dari halaman web, repositori, dan pelacak masalah (issue tracker), dan teks tersebut dapat berisi instruksi. Model tidak memiliki cara yang andal untuk membedakan instruksi Anda dari teks yang diambilnya. Kontrol apa pun yang bergantung pada pilihan model yang tepat akan gagal saat instruksi injeksi pertama yang meyakinkan muncul, sehingga kontrol harus ditempatkan di sistem operasi atau jaringan.
Apakah variabel lingkungan benar-benar seburuk itu untuk rahasia agen?
Variabel tersebut buruk dalam satu hal spesifik: variabel tersebut diwariskan. Setiap proses anak yang dijalankan agen mendapatkan salinannya, termasuk skrip build, test runner, dan hook instalasi paket apa pun. Variabel tersebut juga dapat dibaca melalui /proc/<pid>/environ oleh pengguna yang sama, sehingga apa pun yang dijalankan agen dapat membacanya tanpa perlu diteruskan oleh agen. File yang dibaca pada saat penggunaan, dengan LoadCredential= atau gateway, membatasi paparan hanya pada saat itu saja.
Apakah menyimpan rahasia di dalam vault menyelesaikan masalah ini dengan sendirinya?
Hanya sebagian. Vault memperbaiki penyimpanan. Jika Anda melakukan self-host pada vault tersebut, vault itu memerlukan langkah pengerasan (hardening) tersendiri, karena server Vaultwarden biasanya dibobol melalui token admin atau file cadangannya, bukan melalui item terenkripsi yang disimpannya. Hal ini tidak memperbaiki langkah terakhir, di mana sesuatu menarik rahasia keluar dari vault dan memberikannya kepada agen sebagai variabel lingkungan, yang mengembalikan Anda ke titik awal. Yang penting adalah siapa yang melakukan substitusi. Jika agen mengambil rahasia tersebut, maka agen memegang rahasia tersebut. Jika gateway atau sistem init melakukan substitusi di luar proses agen, agen tidak pernah memegangnya.
Bagaimana saya tahu apakah agen sudah membocorkan sesuatu?
Biasanya Anda tidak dapat mengetahuinya setelah kejadian, itulah argumen untuk penggunaan gateway. Tanpa gateway, bukti Anda tersebar di riwayat shell, transkrip agen, dan log koneksi keluar yang mungkin tidak Anda simpan. Dengan gateway kredensial, setiap penggunaan kredensial adalah satu baris log dengan identitas agen dan stempel waktu. Jika Anda mencurigai adanya kebocoran, lakukan rotasi kunci terlebih dahulu, baru kemudian lakukan investigasi. Rotasi itu murah, sedangkan kepastian tidak.
Apa hal minimum yang harus saya lakukan hari ini?
Pindahkan setiap file .env keluar dari direktori tempat agen Anda bekerja, dan buat satu pengguna tanpa hak akses istimewa (unprivileged user) untuk setiap agen. Kedua perubahan tersebut memakan waktu sekitar sepuluh menit dan menutup jalur yang paling umum, yaitu agen yang membaca file kredensial yang seharusnya tidak perlu berada di dekat kode. Gateway dan token berumur pendek adalah langkah berikutnya, bukan langkah pertama. Titik awal yang sama berlaku untuk runtime agen apa pun, termasuk menjalankan agen otonom dengan aman di VPS.