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.

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.
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: 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
_ -> NothingparseIntTotal 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.
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:
newtype Email = Email { unEmail :: String } deriving (Show)
mkEmail :: String -> Maybe Email
mkEmail raw
| '@' `elem` raw && '.' `elem` tail raw = Just (Email raw)
| otherwise = NothingSekarang 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:
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 = NothingDengan 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.
Haskell menawarkan escape hatch bernama unsafe — unsafePerformIO, 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.-- 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 cacheJika 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.
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.
{-# 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:
eval string (tidak ada eval di Haskell untuk alasan ini).takeWhile dengan limit) agar tidak terjadi DoS.errorBundlePretty untuk log, pesan umum untuk user.Aplikasi hanya seaman dependensinya. Haskell punya alat untuk mengaudit secara berkala:
cabal-audit # periksa package terhadap database CVEcabal-audit membandingkan versi dependensi kalian dengan database kerentanan yang dikenal. Praktik yang disarankan:
cabal-audit di CI (misal tiap PR).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.
Kumpulan aturan yang menjaga kode produksi Haskell tetap aman dan sehat:
| Praktik | Hasil |
|---|---|
| Total functions | Tidak ada crash tak terduga |
| ADT + smart constructor | Illegal state tidak representable |
Tanpa unsafe* | Jaminan purity/tipe utuh |
| Validasi input dengan parser | Input asing tak pernah masuk mentah |
-Wall -Werror + -Wx-partial | Peringatan jadi error, partial terdeteksi |
cabal-audit di CI | Kerentanan terdeteksi lebih awal |
| Test Hspec/QuickCheck | Perilaku terdokumentasi dan terproteksi |
Email tidak disembunyikan (modul mengekspos konstruktor mentah), siapa pun bisa membuat Email invalid. Ekspor hanya fungsi mkEmail, bukan konstruktornya.unsafePerformIO untuk "caching" — ada cara aman: IO, IORef, atau pustaka MemoTrie. Jangan menyamar efek samping sebagai pure.Inti yang harus dibawa pulang:
unsafe* — escape hatch merusak jaminan keamanan bahasa.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!