Mengamankan aplikasi Scala sejak awal: immutability dan pure function sebagai pertahanan struktural, menghindari null dengan Option, input validation, penanganan secret yang benar, serta audit dependensi dengan sbt-updates dan dependencyCheck.

Setelah menguasai error handling di episode 16, sekarang kita membangun pertahanan yang lebih menyeluruh: keamanan. Di dunia produksi, keamanan bukan fitur opsional — ia adalah properti yang muncul dari ribuan keputusan kecil: bagaimana data disimpan, bagaimana input diproses, bagaimana secret dikelola, dan bagaimana dependensi diaudit.
Mengapa topik ini penting di series Scala? Karena banyak praktik keamanan di aplikasi JVM justru datang "gratis" dari gaya Scala yang kita pelajari — immutability, Option yang menghilangkan null, dan type system yang ketat. Episode ini menghubungkan pelajaran-pelajaran itu dengan praktik keamanan konkret.
Ini keunggulan keamanan yang sering tidak disadari: kode immutable lebih aman. Bayangkan sebuah variabel yang menyimpan token auth:
var authToken = "secret-token-abc"
// di tempat lain, tidak sengaja:
authToken = "secret-token-hacked"val authToken = "secret-token-abc"Dengan val, tidak ada cara lain mengubah nilai — satu-satunya jalan adalah membuat instance baru. Ini menghilangkan satu kelas kerentanan: state mutation yang tidak diinginkan. Prinsip least privilege juga lebih mudah diterapkan: pure function tidak bisa "membocorkan" state global ke pemanggil lain.
def buildAuthHeader(token: String): String =
s"Bearer $token" // hanya memakai argumen, tidak menyentuh globalNullPointerException adalah pintu masuk banyak bug keamanan di Java (misal NPE yang melewati validasi auth). Scala memecahnya dengan Option:
def findUser(id: Int): UserOrNull = ??? // pola berbahaya
def findUser(id: Int): Option[User] = ??? // pola amanAturan praktis: jangan menulis null di kode Scala, dan jika harus memakai library Java yang mengembalikan null, bungkus dengan Option:
import scala.util.Try
val maybe = Option(javaLibrary.find("key")) // null -> NoneJangan pernah mempercayai input — validasi di perbatasan (API boundary), bukan di kedalaman kode. Prinsip defense in depth: validasi berlapis.
enum ValidationError:
case EmptyField(field: String)
case InvalidFormat(field: String)
def validateUserInput(name: String, email: String): Either[ValidationError, (String, String)] =
if name.trim.isEmpty then Left(ValidationError.EmptyField("name"))
else if !email.contains("@") then Left(ValidationError.InvalidFormat("email"))
else Right((name.trim, email))Dan karena query database memakai prepared statement / parameter binding (episode 15), SQL injection tidak akan pernah terjadi lewat jalur resmi:
import doobie.*
import doobie.implicits.*
// AMAN: input sebagai parameter
sql"SELECT * FROM users WHERE email = $inputEmail".query[User].option
// BERBAHAYA: concatenation string
// sql"SELECT * FROM users WHERE email = '$inputEmail'".query[User].optionSecret (password, API key, token) tidak boleh ada di source code, di file yang di-commit, atau di log. Aturannya:
- Jangan commit secret ke git (cek .gitignore)
- Pakai environment variables / config server
- Rotasi secret berkala
- Jangan log nilai secretDi Scala, secret dibaca dari environment saat runtime:
import scala.util.Try
def env(name: String): Option[String] = Option(System.getenv(name))
val dbPassword: Either[String, String] =
env("DB_PASSWORD").toRight("DB_PASSWORD tidak ter-set di environment")System.getenv("DB_PASSWORD") mengembalikan null jika tidak ada — Option(...) membungkusnya, lalu .toRight mengubah menjadi Either dengan pesan error yang jelas. Secret dikonfigurasi di infrastruktur (Docker secret, CI secret, vault), bukan di kode.
Dependensi yang usang adalah jalur kerentanan paling umum. Dua alat utama:
Plugin sbt-updates melaporkan dependency yang memiliki versi lebih baru:
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")sbt
sbt:scala-app> dependencyUpdates
[info] Found 3 dependencies:
[info] org.http4s:http4s-ember-server 0.23.30 -> 0.23.31
[info] org.scalameta:munit 1.0.0 -> 1.1.0Untuk audit kerentanan CVE yang lebih menyeluruh, pakai plugin OWASP:
addSbtPlugin("org.owasp" % "dependencycheck-sbt" % "12.1.0")sbt
sbt:scala-app> dependencyCheck
[info] Checking for updates and analyzing dependencies...
[info] Found 1 known CVE in project dependencies.dependencyCheck mengunduh database NVD dan mencocokkan dependensi dengan CVE yang dikenal. Integrasikan ke CI sebagai gate: build gagal jika ada kerentanan kritis.
Warning
Audit dependensi wajib masuk CI, bukan sekadar ritual lokal. Pola terbaik: job CI yang menjalankan dependencyCheck dan dependencyUpdates setiap push, sehingga kerentanan baru terdeteksi segera — bukan saat sudah dieksploitasi.
Rekap praktik yang sudah kalian kenali sepanjang series, dari sudut keamanan:
val > var — state yang tidak bisa diubah = attack surface yang mengecil.Option/Either > null/exception — tidak ada NPE yang melewati auth.Resource/Scope memastikan pembersihan resource (mencegah resource exhaustion).Inti yang harus dibawa pulang:
Option menghilangkan kelas bug NPE.sbt-updates untuk versi, dependencyCheck untuk CVE — keduanya di CI.Di episode 18 selanjutnya, kita menjembatani dua dunia: interop dengan Java — memakai library Java, konversi collection dengan CollectionConverters, dan strategi migrasi bertahap. Sampai jumpa di episode 18!