Belajar Haskell - Keamanan & Best Practice
Episode 17 of 23

Belajar Haskell - Keamanan & Best Practice

Mengamankan aplikasi Haskell dari desain: menulis total functions yang tidak pernah crash, type-driven design untuk membuat illegal states unrepresentable, menghindari escape hatch unsafe*, validasi input pengguna dengan Megaparsec, serta mengaudit dependensi lewat cabal-audit dan pemantauan CVE.

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

Pendahuluan

Setelah 16 episode membangun kemampuan teknis, episode ini membicarakan sesuatu yang lebih halus dan lebih mahal jika salah: keamanan dan kualitas produksi. Keamanan di Haskell terlihat berbeda dari di bahasa lain — bukan hanya "tambahkan library enkripsi", tapi terutama menghilangkan kelas-kelas bug sejak desain tipe.

Mengapa bab ini penting? Karena sebagian besar kerentanan aplikasi bukan serangan siber eksotis, melainkan bug biasa yang menjadi dapat dieksploitasi: input yang tidak tervalidasi, state yang tidak konsisten, dan fungsi yang crash di jalur yang tidak terduga. Haskell memberi kalian perangkat untuk menutup pintu-pintu ini secara struktural — sebelum kalian lupa menutupnya manual.

Total Functions: Fungsi yang Tidak Pernah Crash

Di episode 16 kita mulai menghindari head/fromJust. Di sini kita menjadikannya disiplin: setiap fungsi di codebase diusahakan total — menghasilkan nilai valid untuk semua input yang mungkin.

Partial vs total
-- Partial: crash untuk input tak terduga
parseIntPartial :: String -> Int
parseIntPartial s = read s
 
-- Total: selalu ada hasil yang jelas
parseIntTotal :: String -> Maybe Int
parseIntTotal s = case reads s of
    [(n, "")] -> Just n
    _         -> Nothing

parseIntTotal tidak pernah crash: input yang tidak valid menghasilkan Nothing, dan pemanggil dipaksa memutuskan cara menanganinya. Ketika seluruh codebase hanya berisi fungsi total, kelas bug "runtime error yang tak terduga" hilang — compiler dan pola yang aman memaksa keputusan error diekspresikan, bukan disembunyikan.

Type-Driven Design: Illegal States Unrepresentable

Inilah perbedaan filosofis paling mencolok Haskell: rancang tipe sehingga state yang tidak valid tidak mungkin diwakili. Contoh klasik: alih-alih String untuk email (bisa berisi apa saja), buat tipe khusus:

Smart constructor
newtype Email = Email { unEmail :: String } deriving (Show)
 
mkEmail :: String -> Maybe Email
mkEmail raw
    | '@' `elem` raw && '.' `elem` tail raw = Just (Email raw)
    | otherwise                             = Nothing

Sekarang fungsi yang menerima Email dijamin menerima string yang sudah tervalidasi — tidak ada cara lain untuk membuat Email selain lewat mkEmail. Compiler menghapus seluruh kelas bug "email invalid sampai ke database".

Contoh kedua yang lebih bermakna — state mesin:

State yang tidak valid tidak representable
data State = Draft | Published | Archived
 
-- Partial: kombinasi yang mustahil diwakili String bebas
-- Total: hanya tiga state valid, compiler memaksa semua ditangani
nextState :: State -> Maybe State
nextState Draft     = Just Published
nextState Published = Just Archived
nextState Archived  = Nothing

Dengan ADT, kalian tidak perlu lagi mengingat "string apa saja yang valid" — compiler mengetahuinya, dan pattern match yang tidak lengkap pun terdeteksi.

Tip

Pertanyaan kunci untuk setiap field: "apakah nilai ini bisa mewakili sesuatu yang tidak boleh terjadi?" Jika ya, definisikan tipe yang lebih kecil (ADT, newtype + smart constructor). Semakin kecil set nilai valid yang bisa diwakili, semakin banyak bug yang tidak mungkin terjadi.

Menghindari unsafe*

Haskell menawarkan escape hatch bernama unsafeunsafePerformIO, unsafeCoerce, fromJust — yang menembus jaminan keamanan bahasa. Aturannya sederhana: jangan gunakan di kode aplikasi.

  • unsafePerformIO menyelipkan efek samping ke fungsi "pure" — merusak referential transparency yang menjaga segalanya.
  • unsafeCoerce mengubah tipe secara paksa — jalan pintas menuju undefined behavior.
  • fromJust mengubah Maybe menjadi nilai dengan asumsi — crash tersembunyi.
Hindari unsafe - tulis yang aman
-- Berbahaya: efek samping yang menyamar sebagai fungsi pure
nilaiCache :: IO Int
nilaiCache = pure (unsafePerformIO (readIORef cache))
 
-- Aman: tandai efek samping dengan IO
nilaiCacheAman :: IO Int
nilaiCacheAman = readIORef cache

Jika kalian menemukan kode yang butuh unsafe, itu hampir selalu tanda bahwa desainnya salah — refactor, jangan lobangi jaminan keamanannya. Audit dengan grep unsafe di codebase secara berkala.

Validasi Input dengan Megaparsec

Episode 14 memperkenalkan Megaparsec untuk parsing. Untuk keamanan, semua input yang berasal dari luar (request HTTP, file upload, argumen CLI) harus melewati parser — bukan string yang dipakai langsung.

Validasi input request
{-# LANGUAGE OverloadedStrings #-}
 
import Text.Megaparsec
import Text.Megaparsec.Char
import Data.Void
import qualified Data.Text as T
 
type P = Parsec Void T.Text
 
data Body = Body { nama :: T.Text, umur :: Int }
    deriving (Show)
 
parseBody :: T.Text -> Either String Body
parseBody input =
    case parse bodyParser "request" input of
        Left err -> Left (errorBundlePretty err)
        Right b  -> Right b
  where
    bodyParser = do
        n <- takeWhile1P (Just "nama") (/= ',')
        _ <- char ','
        u <- decimal
        _ <- eof
        pure (Body n u)

Satu titik validasi: sebelum data menyentuh logika domain, ia harus menjadi Body yang sudah pasti bentuknya. Ditambah prinsip dasar:

  • Jangan pernah eval string (tidak ada eval di Haskell untuk alasan ini).
  • Batasi ukuran input (misal takeWhile dengan limit) agar tidak terjadi DoS.
  • Jangan mengungkap detail internal di pesan error — errorBundlePretty untuk log, pesan umum untuk user.

Mengaudit Dependensi: cabal-audit dan CVE

Aplikasi hanya seaman dependensinya. Haskell punya alat untuk mengaudit secara berkala:

Audit dependensi
cabal-audit          # periksa package terhadap database CVE

cabal-audit membandingkan versi dependensi kalian dengan database kerentanan yang dikenal. Praktik yang disarankan:

  1. Jalankan cabal-audit di CI (misal tiap PR).
  2. Update dependensi rutin, bukan hanya saat ada masalah.
  3. Pantau advisory di Hackage — beberapa paket punya deprecated warning sebelum dicabut.
  4. Batasi jangkauan dependensi: semakin sedikit paket, semakin kecil permukaan serang.
Contoh workflow audit
cabal update
cabal-audit || echo "Ada kerentanan - perlu update"

Warning

cabal-audit hanya menangkap kerentanan yang terdaftar. Dependensi yang lama tapi tidak pernah diaudit sama bahayanya dengan yang terdaftar. Jadikan audit langkah CI otomatis, bukan tugas ingatan — sama seperti cabal build di setiap commit.

Checklist Best Practice

Kumpulan aturan yang menjaga kode produksi Haskell tetap aman dan sehat:

PraktikHasil
Total functionsTidak ada crash tak terduga
ADT + smart constructorIllegal state tidak representable
Tanpa unsafe*Jaminan purity/tipe utuh
Validasi input dengan parserInput asing tak pernah masuk mentah
-Wall -Werror + -Wx-partialPeringatan jadi error, partial terdeteksi
cabal-audit di CIKerentanan terdeteksi lebih awal
Test Hspec/QuickCheckPerilaku terdokumentasi dan terproteksi

Kesalahan Umum (Common Pitfalls)

  1. Smart constructor yang bisa di-bypass — jika konstruktor Email tidak disembunyikan (modul mengekspos konstruktor mentah), siapa pun bisa membuat Email invalid. Ekspor hanya fungsi mkEmail, bukan konstruktornya.
  2. unsafePerformIO untuk "caching" — ada cara aman: IO, IORef, atau pustaka MemoTrie. Jangan menyamar efek samping sebagai pure.
  3. Validasi yang baru terjadi di layer database — validasi harus di gerbang input (parser), bukan menunggu constraint SQL error.
  4. Pesan error yang bocor detail internal — bedakan pesan untuk log (detail) dan untuk user (umum).
  5. Tidak mengaudit dependensi — kalian mengeksekusi kode orang lain; ketahui isinya.

Penutup

Inti yang harus dibawa pulang:

  • Total functions menghilangkan crash tak terduga; partial functions ditandai dan dihindari.
  • Type-driven design — rancang tipe sehingga state invalid tidak mungkin diwakili (smart constructor, ADT).
  • Hindari unsafe* — escape hatch merusak jaminan keamanan bahasa.
  • Validasi input dengan Megaparsec di gerbang, bukan di dalam.
  • Audit dependensi dengan cabal-audit dan jadikan bagian CI.

Di episode 18 selanjutnya kita akan membahas FFI & interop — Foreign Function Interface ke C dengan foreign import ccall, memahami ABI, serta kompilasi ke WASM lewat target wasm32wasi di GHC 9.14 untuk menjalankan Haskell di browser dan edge runtime. Sampai jumpa di episode 18!