Memahami risiko command injection di balik --preview dan --bind, cara mengutip placeholder dengan benar agar item tidak dieksekusi sebagai kode, serta menjaga privasi riwayat perintah dan output agar tidak bocor ke log. Termasuk standar menulis konfigurasi fzf yang aman untuk tim.

Di episode 13 kalian mulai menjalankan fzf di server lewat SSH dan mengeksekusi perintah pada item yang dipilih. Di sanalah kekuatan fzf juga berubah menjadi risiko: semakin banyak fzf menjalankan perintah untuk kalian — preview, execute, become — semakin besar permukaan tempat input yang tidak bersahabat bisa menyusup. Episode ini bukan cerita menakut-nakuti, melainkan pelajaran teknis yang konkret: dari mana risiko itu lahir, bagaimana injection terjadi, dan kebiasaan apa yang menyelamatkan kalian.
Kita akan membahas tiga area. Pertama, command injection di balik --preview dan --bind — bagaimana item yang tampak polos bisa dieksekusi sebagai kode shell, dan bagaimana mengutip placeholder dengan benar. Kedua, privasi dan higiene — mencegah history berisi secret muncul di Ctrl+R dan memastikan output fzf tidak bocor ke log. Ketiga, standar menulis perintah fzf yang aman untuk tim.
Ingat kembali cara kerja fzf yang kita bangun dari episode awal: ia menerima daftar teks di stdin, menyaringnya, lalu mencetak pilihan ke stdout. Permukaan serangan muncul ketika teks itu ikut serta dalam perintah shell. Dua tempat yang paling sering terdampak:
--preview 'perintah {}' — perintah dijalankan ulang setiap kali kursor berpindah, dengan {} diganti teks item yang sedang disorot.--bind 'tombol:execute(perintah {})' atau become(perintah {}) — perintah dijalankan saat tombol ditekan, dengan {} yang sama.Intinya: fzf mengganti {} dengan teks mentah item, lalu menyerahkan seluruh string kepada shell. Jika item mengandung karakter yang bermakna bagi shell — spasi, titik koma, $(), backtick — shell akan mengartikannya sebagai kode, bukan sekadar data.
Important
Sumber item tidak pernah bisa dipercaya. Daftar file bisa memuat nama yang tidak biasa, history command bisa memuat apa pun, dan output program lain bisa saja menyisipkan teks aneh. Perlakukan setiap item sebagai data yang belum diverifikasi, bukan sebagai argumen yang aman.
Mari kita buktikan dengan contoh sekecil mungkin. Bayangkan sebuah direktori berisi file dengan nama berikut:
touch 'foo;touch /tmp/pwned.txt'
lsfzf --preview 'cat {}'Begitu kursor menyorot file foo;touch /tmp/pwned.txt, fzf menyusun string perintah cat foo;touch /tmp/pwned.txt lalu menyerahkannya ke shell. Shell menjalankan cat foo (error, file itu tidak ada), lalu mengeksekusi touch /tmp/pwned.txt. Dari perspektif kalian, preview tampak "error" — padahal di belakang layar sebuah perintah telah berjalan. Ganti touch dengan rm -rf atau perintah yang dikirim ke luar, dan dampaknya jelas.
Kabar baiknya, pertahanannya sederhana: kutip placeholder sehingga shell memperlakukan item sebagai satu argumen literal. Dengan mengapit {} dalam kutip tunggal di dalam perintah preview, isi item tidak lagi punya makna bagi shell:
fzf --preview "sed -n 1,50p '{}'"Perintah yang disusun fzf menjadi sed -n 1,50p 'foo;touch /tmp/pwned.txt' — semua karakter di dalam kutip tunggal dianggap data murni. Ini bukan obat mujarab total (nama file yang mengandung kutip tunggal bisa menembus), tetapi menutup hampir semua vektor yang nyata.
Warning
Mengutip placeholder adalah perlindungan lapis pertama, bukan izin untuk lengah. Untuk daftar yang penuh kejutan, tambahkan lapis kedua: filter item sebelum masuk ke fzf — misalnya menolak item yang mengandung karakter kontrol atau glob. Dan untuk tindakan destruktif (rm, restart, drop), biasakan menampilkan preview lalu meminta konfirmasi, atau jalankan dengan mode kering terlebih dahulu.
Prinsip yang lebih dalam dari sekadar mengutip: pisahkan data dari kode. Semakin kompleks perintah fzf kalian, semakin mudah aturan ini dilanggar. Dua pola yang membantu:
Jangan menempelkan output program ke perintah. Daripada --bind 'enter:execute(rm $(cat {q}))', biarkan fzf hanya memilih, lalu konsumsi hasilnya dengan alat yang menangani argumen secara eksplisit — seperti xargs -0 atau parameter shell yang dikutip.
Gunakan pembatas null untuk daftar file. Item yang dipisahkan null tidak bisa dipecah oleh spasi atau newline, sehingga konsumen output (xargs -0, while IFS= read -r) menerima satu item utuh per argumen. Ini pola yang sama dengan --read0 dan --print0 dari episode 6, dan di sini ia berfungsi ganda sebagai pengaman.
fd -t f -0 | fzf --multi --print0 | xargs -0 -I{} file {}Perhatikan bahwa file {} tetap memakai placeholder — tetapi {} sekarang adalah item yang sudah lolos filter -0, dan pembacanya (xargs -0) menjamin tidak ada pemecahan argumen.
Ctrl+RSekarang kita pindah ke sisi privasi. Ctrl+R dari integrasi shell membaca command history — dan history itu sering berisi hal yang seharusnya tidak pernah ditampilkan: token API, password yang terlanjur diketik, string koneksi database. Setiap kali kalian membuka Ctrl+R di layar bersama atau demo, daftar itu jadi pajangan publik.
Pertahanan berlapis untuk masalah ini:
| Lapisan | Cara | Efek |
|---|---|---|
| Jangan disimpan | HISTCONTROL=ignorespace + spasi di awal perintah | Perintah yang diawali spasi tidak masuk history |
| Jangan dicatat | HISTIGNORE berisi pola secret | Baris yang cocok tidak disimpan ke history |
| Jangan ditampilkan | Binding kustom yang memfilter history | Item mencurigakan tidak muncul di fzf |
Untuk lapisan terakhir, ganti pemanggilan Ctrl+R dengan widget yang menyingkirkan baris yang tampak berisi kredensial sebelum masuk ke fzf:
__history_clean() {
builtin fc -lnr -1000 |
grep -Ev '(pass(word|wd)|token|api[_-]?key|secret|BEGIN (RSA|EC|OPENSSH) PRIVATE)' |
fzf --height 40% --layout reverse
}
bind -x '"\C-x\C-r": __history_clean'Gantilah pola regex dengan kata kunci yang relevan dengan lingkungan kalian. Ini bukan keamanan penuh — penyaringan berbasis pola bisa meleset — tetapi menutup kebocoran yang paling sering terjadi: secret yang tampil tanpa sengaja di layar.
Kebocoran kedua lebih halus: output fzf yang ikut tercatat. Dua jalur yang umum:
Preview membaca file sensitif. --preview 'cat {}' pada daftar file konfigurasi bisa menampilkan isi file berisi secret ke layar, dan jika terminal kalian merekam scrollback atau outputnya dialihkan ke log, isinya ikut tersimpan.
Output stdout yang tidak disaring. Jika hasil fzf dipipakan langsung ke script yang menulis log (misalnya daftar command yang pernah dijalankan), secret ikut tertulis. Saring dengan grep atau sed sebelum menuju log.
Kebiasaan yang menolong: batasi preview pada metadata (ukuran, waktu, git log) daripada isi penuh; sembunyikan preview untuk direktori tertentu; dan sebelum memipakan output ke log, uji satu kali apa saja yang benar-benar keluar dari stdout fzf.
Tip
Salah satu aturan emas operasional: perintah yang sensitif tidak pernah diketik di command line. Gunakan secret manager atau environment variable yang disuntikkan saat runtime, bukan token yang tertanam di history. Dengan begitu, mengamankan Ctrl+R dan log menjadi jauh lebih mudah — karena tidak ada yang perlu disembunyikan sejak awal.
Ketika konfigurasi fzf dibagikan ke tim — lewat dotfiles, script bootstrap, atau plugin — kesalahan satu orang menjadi kesalahan semua orang. Standar yang saya sarankan:
--preview, execute, dan become. Selalu, tanpa pengecualian. Ini aturan satu kalimat yang mudah di-review.execute dengan input yang tidak diverifikasi. Jika aksi harus menjalankan perintah, pastikan item melewati validasi dulu, atau gunakan aksi yang menerima argumen secara eksplisit.FZF_DEFAULT_OPTS yang aneh lebih mudah terlihat jika ada di diff PR daripada di .zshrc pribadi.FZF_DEFAULT_COMMAND atau file history yang ikut tersinkronisasi ke repo.| Kesalahan | Risiko | Mitigasi |
|---|---|---|
Preview tanpa mengutip {} | Command injection | Kutip placeholder: "sed -n 1,50p '{}'" |
execute memakai {q} tanpa validasi | Query pengguna dieksekusi sebagai kode | Validasi dulu, atau hindari execute dari {q} |
xargs tanpa -0 pada nama file | Argumen terpecah, perintah keliru | fzf --print0 | xargs -0 |
| Secret diketik di command line | Muncul di Ctrl+R dan log | Secret manager + HISTCONTROL + HISTIGNORE |
| Preview menampilkan file sensitif | Isi bocor ke scrollback/log | Preview metadata, bukan isi penuh |
| Konfigurasi fzf tanpa review | Kesalahan menyebar ke tim | Simpan di repo, kutip placeholder di semua aksi |
Caution
Episode ini mengajarkan penghormatan pada aturan: data tidak pernah menjadi kode secara otomatis. Setiap kali kalian menulis --preview, execute, atau become, tanyakan satu hal: "apakah isi {} bisa saja tidak seperti yang kuharapkan?" Jika jawabannya "bisa", kutip, filter, atau hindari. Kebiasaan sekecil itu yang membedakan setup yang aman dari setup yang baru menunggu hari naas.
Pada episode 14 ini kalian memahami sisi gelap kekuatan fzf dan cara menutupnya: permukaan serangan di balik --preview dan --bind tempat item bisa berubah menjadi kode shell; kutipan placeholder sebagai pertahanan pertama dan pemisahan data dari kode sebagai prinsip yang lebih dalam; privasi riwayat perintah lewat HISTCONTROL, HISTIGNORE, dan widget Ctrl+R yang memfilter secret; serta standar konfigurasi yang aman untuk dibagikan ke tim.
Yang harus kalian bawa pulang: fzf adalah filter, dan filter tidak boleh mengeksekusi apa pun tanpa kalian meminta. Dengan aturan itu, kalian bisa memakai kekuatan --bind tanpa rasa takut.
Dan itulah babak selanjutnya. Di episode 15 kita membahas custom keybindings & actions — accept, execute, execute-silent, become, reload, change-preview-window, preview-top, select-all, toggle+, hingga --expect dan --no-clear. Kali ini kalian akan membangun aksi yang benar-benar personal: membuka editor, git add, hingga membunuh proses — semua dari dalam satu antarmuka fuzzy.