Belajar Scala - Keamanan & Best Practice
Episode 17 of 23

Belajar Scala - Keamanan & Best Practice

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.

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

Pendahuluan

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.

Pertahanan Struktural: Immutability dan Pure Function

Ini keunggulan keamanan yang sering tidak disadari: kode immutable lebih aman. Bayangkan sebuah variabel yang menyimpan token auth:

var: token bisa diubah dari mana saja
var authToken = "secret-token-abc"
// di tempat lain, tidak sengaja:
authToken = "secret-token-hacked"
val: tidak bisa diubah
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.

Pure function tidak bocorkan state
def buildAuthHeader(token: String): String =
  s"Bearer $token"   // hanya memakai argumen, tidak menyentuh global

Menghilangkan Null

NullPointerException adalah pintu masuk banyak bug keamanan di Java (misal NPE yang melewati validasi auth). Scala memecahnya dengan Option:

Jangan pernah: null
def findUser(id: Int): UserOrNull = ???   // pola berbahaya
 
def findUser(id: Int): Option[User] = ???  // pola aman

Aturan praktis: jangan menulis null di kode Scala, dan jika harus memakai library Java yang mengembalikan null, bungkus dengan Option:

Membungkus null Java
import scala.util.Try
 
val maybe = Option(javaLibrary.find("key"))   // null -> None

Input Validation: Pagar di Batas Sistem

Jangan pernah mempercayai input — validasi di perbatasan (API boundary), bukan di kedalaman kode. Prinsip defense in depth: validasi berlapis.

Validasi di boundary
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:

Parameter binding - tanpa concatenation
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].option

Secret Handling: Jangan Pernah di Kode

Secret (password, API key, token) tidak boleh ada di source code, di file yang di-commit, atau di log. Aturannya:

Aturan dasar secret
- Jangan commit secret ke git (cek .gitignore)
- Pakai environment variables / config server
- Rotasi secret berkala
- Jangan log nilai secret

Di Scala, secret dibaca dari environment saat runtime:

Membaca secret dari environment
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.

Dependency Audit: sbt-updates dan dependencyCheck

Dependensi yang usang adalah jalur kerentanan paling umum. Dua alat utama:

sbt-updates: Cek Versi Terbaru

Plugin sbt-updates melaporkan dependency yang memiliki versi lebih baru:

project/plugins.sbt
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")
Jalankan di sbt
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.0

OWASP dependencyCheck

Untuk audit kerentanan CVE yang lebih menyeluruh, pakai plugin OWASP:

project/plugins.sbt
addSbtPlugin("org.owasp" % "dependencycheck-sbt" % "12.1.0")
Jalankan audit CVE
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.

Best Practice Checklist

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.
  • Domain error sebagai ADT — kegagalan eksplisit, tidak tersembunyi.
  • Validasi di boundary — defense in depth, parameterized query.
  • Secret via environment — tidak pernah di kode/log.
  • Dependency audit — sbt-updates + dependencyCheck di CI.
  • Effect systemResource/Scope memastikan pembersihan resource (mencegah resource exhaustion).

Common Pitfalls

  • Secret di default config yang ter-commit — config dengan nilai dummy masih tersalin ke codebase; gunakan placeholder + env.
  • Logging nilai request yang sensitif — jangan log password/token; log field yang aman saja.
  • Validasi hanya di client — validasi client bisa dilewati; selalu validasi ulang di server.
  • Audit dependensi sekali setahun — kerentanan muncul setiap hari; audit otomatis di CI.

Penutup

Inti yang harus dibawa pulang:

  • Immutability dan pure function adalah pertahanan struktural yang datang dari gaya Scala.
  • Hilangkan nullOption menghilangkan kelas bug NPE.
  • Validasi di boundary dengan prepared statement untuk mencegah injection.
  • Secret via environment variable, tidak pernah di kode atau log.
  • Audit dependensi otomatis: 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!

Belajar Scala - Keamanan & Best Practice | Belajar Scala