Belajar Fzf - Security & Best Practices
Series/Belajar Fzf/Episode 14
Episode 14 of 23

Belajar Fzf - Security & Best Practices

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.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Pendahuluan

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.

Permukaan Serangan fzf

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.

Command Injection lewat Preview

Mari kita buktikan dengan contoh sekecil mungkin. Bayangkan sebuah direktori berisi file dengan nama berikut:

Nama file yang berniat buruk
touch 'foo;touch /tmp/pwned.txt'
ls
Simulasi item yang mengandung perintah shell tersembunyi
Preview yang tidak mengutip placeholder
fzf --preview 'cat {}'
Perintah dijalankan sebagai kode, bukan argumen

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.

Mengutip Placeholder: Pertahanan Pertama

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:

Placeholder yang dikutip tunggal
fzf --preview "sed -n 1,50p '{}'"
Item masuk sebagai argumen literal, bukan kode yang dieksekusi

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.

Pemisahan Data dan Kode

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:

  1. 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.

  2. 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.

Pemisahan data dan kode
fd -t f -0 | fzf --multi --print0 | xargs -0 -I{} file {}
fzf hanya memilih; xargs -0 menangani argumen secara aman

Perhatikan bahwa file {} tetap memakai placeholder — tetapi {} sekarang adalah item yang sudah lolos filter -0, dan pembacanya (xargs -0) menjamin tidak ada pemecahan argumen.

Privasi: History yang Bocor di Ctrl+R

Sekarang 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:

LapisanCaraEfek
Jangan disimpanHISTCONTROL=ignorespace + spasi di awal perintahPerintah yang diawali spasi tidak masuk history
Jangan dicatatHISTIGNORE berisi pola secretBaris yang cocok tidak disimpan ke history
Jangan ditampilkanBinding kustom yang memfilter historyItem 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 yang difilter
__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'
grep menyingkirkan baris yang tampak berisi kredensial

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.

Output yang Tidak Boleh Bocor ke Log

Kebocoran kedua lebih halus: output fzf yang ikut tercatat. Dua jalur yang umum:

  1. 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.

  2. 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.

Best Practice untuk Tim

Ketika konfigurasi fzf dibagikan ke tim — lewat dotfiles, script bootstrap, atau plugin — kesalahan satu orang menjadi kesalahan semua orang. Standar yang saya sarankan:

  • Kutip placeholder di setiap --preview, execute, dan become. Selalu, tanpa pengecualian. Ini aturan satu kalimat yang mudah di-review.
  • Tidak ada execute dengan input yang tidak diverifikasi. Jika aksi harus menjalankan perintah, pastikan item melewati validasi dulu, atau gunakan aksi yang menerima argumen secara eksplisit.
  • Simpan konfigurasi fzf di repo dan review bersama. FZF_DEFAULT_OPTS yang aneh lebih mudah terlihat jika ada di diff PR daripada di .zshrc pribadi.
  • Jangan pernah menaruh secret di FZF_DEFAULT_COMMAND atau file history yang ikut tersinkronisasi ke repo.
  • Test di mesin uji dulu. Jalankan konfigurasi baru dengan daftar item yang sengaja berisi karakter aneh sebelum dipakai di produksi.

Kesalahan Umum

KesalahanRisikoMitigasi
Preview tanpa mengutip {}Command injectionKutip placeholder: "sed -n 1,50p '{}'"
execute memakai {q} tanpa validasiQuery pengguna dieksekusi sebagai kodeValidasi dulu, atau hindari execute dari {q}
xargs tanpa -0 pada nama fileArgumen terpecah, perintah kelirufzf --print0 | xargs -0
Secret diketik di command lineMuncul di Ctrl+R dan logSecret manager + HISTCONTROL + HISTIGNORE
Preview menampilkan file sensitifIsi bocor ke scrollback/logPreview metadata, bukan isi penuh
Konfigurasi fzf tanpa reviewKesalahan menyebar ke timSimpan 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.

Penutup

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 & actionsaccept, 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.

Belajar Fzf - Security & Best Practices | Belajar Fzf