Seluruh Section Deployment dan Operasional berdiri di atas satu asumsi yang belum pernah kita uji: data yang mengalir ke sistem adalah data yang jujur, artefak model hanya disentuh orang yang berhak, dan setiap permintaan yang datang lewat API dimaksudkan baik. Kenyataannya, ketiga pintu tersebut bisa menjadi sasaran kegiatan yang secara sengaja menurunkan performa, membocorkan data, atau menyalahgunakan model. Chapter ini membangun threat model untuk sistem machine learning: memetakan attack surface, memilah data poisoning dari evasion, lalu menempatkan keduanya dalam kerangka OWASP ML Security Top 10. Dua praktikum kemudian membuktikan bahwa ancaman tersebut bukan teori — label latih yang dicemari dan kueri API yang tak dibatasi sama-sama terjadi di dalam kode yang bisa kita jalankan.

Tiga Pintu Masuk: Attack Surface Sistem Machine Learning #

Threat model selalu dimulai dari satu pertanyaan: komponen mana saja yang bisa disentuh pihak luar? Pada sistem ML modern, jawabannya biasanya tiga komponen. Pertama, jalur data — data latih yang terkumpul dari pengguna, pihak ketiga, atau feedback loop produksi. Kedua, artefak model — berkas hasil serialisasi (joblib, pickle) yang disimpan di registri, bucket, atau repositori. Ketiga, inference API — endpoint yang menerima input mentah dan mengembalikan prediksi. Tabel berikut merangkum risiko utama pada tiap komponen.

KomponenPihak yang biasanya menyentuhRisiko keamanan utama
Jalur data latihPengguna, vendor data, sistem eksternalData poisoning, pencemaran feedback loop
Artefak model (joblib/pickle)Tim data, registri model, pipeline CI/CDModel poisoning, eksekusi kode berbahaya saat loading
Inference APIPengunjung anonim, pelanggan, aplikasi pihak ketigaEvasion, model theft, model inversion, membership inference

Perhatikan pola pentingnya: semakin banyak pihak yang boleh menulis ke satu komponen, semakin besar permukaan yang harus dijaga. Jalur data yang menerima kontribusi publik — misalnya laporan spam dari pengguna — adalah komponen dengan paparan tertinggi, karena penulisnya tidak bisa diautentikasi niatnya.

Taxonomy: Poisoning, Evasion, dan Dua Jenis Kebocoran #

Kerangka paling sederhana untuk memilah ancaman ML adalah waktu kejadian. Data poisoning terjadi saat pelatihan: attacker menyuntikkan baris data yang dirancang khusus agar decision boundary model bergeser ke arah yang menguntungkan dirinya. Evasion terjadi saat inferensi: input dimodifikasi sedikit — tanpa menyentuh data latih sama sekali — sehingga model yang sudah terlatih salah memutuskan. Dua kategori lain berbeda sifat: sasarannya bukan menurunkan performa, melainkan membocorkan informasi dari model. Tabel berikut membandingkan keempatnya.

Jenis ancamanWaktu kejadianAkses yang dibutuhkan attackerSasaran
Data poisoningPelatihan (atau retraining)Menulis ke data latihDecision boundary model
Evasion (adversarial example)InferensiHanya mengirim inputPrediksi satu permintaan
Model theft (extraction)Inferensi berulangAPI terbuka + anggaran kueriFungsi keputusan model
Model inversion / membership inferenceInferensi berulangOutput peluang/skorData latih di balik model

Pembedaan ini menentukan pilihan pertahanan. Poisoning dijawab dengan validasi data dan pembatasan kontributor; evasion dijawab dengan adversarial training; kebocoran dijawab dengan pembatasan granularitas output — materi Chapter Privasi Data dan Machine Learning.

Data Poisoning di Praktik #

Mari kita turunkan taxonomy di atas ke kasus konkret. Masalah nyatanya: filter email yang belajar dari laporan pengguna — pengguna mana pun boleh menandai email sebagai “wajar” atau “bermasalah”, sehingga jalur datanya terbuka. Mekanismenya: LogisticRegression menghitung rata-rata bukti dari seluruh baris latih; jika satu wilayah fitur didominasi label palsu, wilayah tersebut dipelajari sebagai wilayah kelas palsu tersebut, dan boundary bergeser menjauhi wilayah itu.

Praktikum berikut membangun 300 email sintetis dengan dua fitur, melatih model bersih, lalu mencemari 11% data latih: seluruh email bermasalah pada satu “kantong” dekat batas keputusan dilabeli ulang sebagai wajar — persis pola laporan palsu yang terkoordinasi.

python
# Demonstrasi data poisoning pada data latih: label yang dicemari pada satu
# "kantong" wilayah fitur menggeser decision boundary LogisticRegression,
# sehingga kelompok email bermasalah yang serupa lolos dari filter.

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

# ---------- Data sintetis: 300 email, 2 fitur ----------
# fitur_1 = frekuensi kata promosi, fitur_2 = frekuensi tautan singkat.
# Kelas 0 = email wajar (pusat kiri-bawah), kelas 1 = email bermasalah.
rng = np.random.default_rng(42)
n = 300

X_wajar = rng.normal(loc=[-1.2, -1.2], scale=1.0, size=(n // 2, 2))
X_masalah = rng.normal(loc=[1.2, 1.2], scale=1.0, size=(n // 2, 2))
X = np.vstack([X_wajar, X_masalah])
y = np.array([0] * (n // 2) + [1] * (n // 2))

X_latih, X_uji, y_latih, y_uji = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=42
)

# ---------- Model bersih: dilatih pada label apa adanya ----------
model_bersih = LogisticRegression().fit(X_latih, y_latih)
akurasi_bersih = accuracy_score(y_uji, model_bersih.predict(X_uji))

# ---------- Poisoning: label latih kelas 1 "kantong" di-flip ke kelas 0 ----------
# Kantong = titik bermasalah dengan jumlah kedua fitur < 1.0, yaitu wilayah
# yang paling dekat dengan batas keputusan. Pelaku serangan (attacker)
# melaporkan email miliknya sebagai "wajar" agar wilayah tersebut dipelajari
# sebagai wilayah wajar.
kantong_latih = (y_latih == 1) & (X_latih.sum(axis=1) < 1.0)
y_latih_cemar = y_latih.copy()
y_latih_cemar[kantong_latih] = 0

model_cemar = LogisticRegression().fit(X_latih, y_latih_cemar)
akurasi_cemar = accuracy_score(y_uji, model_cemar.predict(X_uji))

print("== Data poisoning: label dicemari pada data latih ==")
print(f"baris latih            : {X_latih.shape[0]}")
print(f"label yang di-flip     : {int(kantong_latih.sum())} "
      f"({kantong_latih.mean() * 100:.1f}% data latih)")
print(f"akurasi uji model bersih : {akurasi_bersih:.3f}")
print(f"akurasi uji model cemar  : {akurasi_cemar:.3f}")

# ---------- Efek pada wilayah kantong: email bermasalah lolos ----------
kantong_uji = (y_uji == 1) & (X_uji.sum(axis=1) < 1.0)
benar_bersih = (model_bersih.predict(X_uji) == y_uji)[kantong_uji]
benar_cemar = (model_cemar.predict(X_uji) == y_uji)[kantong_uji]
print("\n== Akurasi khusus pada kantong yang dicemari ==")
print(f"titik uji di kantong   : {int(kantong_uji.sum())}")
print(f"tertangkap model bersih: {benar_bersih.mean():.3f}")
print(f"tertangkap model cemar : {benar_cemar.mean():.3f}")

# ---------- Perubahan decision boundary dan satu contoh konkret ----------
print("\n== Pergeseran decision boundary ==")
print(f"koefisien model bersih : {np.round(model_bersih.coef_[0], 3).tolist()} "
      f"intercept {model_bersih.intercept_[0]:+.3f}")
print(f"koefisien model cemar  : {np.round(model_cemar.coef_[0], 3).tolist()} "
      f"intercept {model_cemar.intercept_[0]:+.3f}")

# Email bermasalah tipikal di tepi kantong: jumlah fitur = 1.0.
sampel = np.array([[0.5, 0.5]])
p_bersih = model_bersih.predict_proba(sampel)[0, 1]
p_cemar = model_cemar.predict_proba(sampel)[0, 1]
print("\n== Satu email uji pada titik (0.5, 0.5) ==")
print(f"peluang 'bermasalah' model bersih : {p_bersih:.3f}")
print(f"peluang 'bermasalah' model cemar  : {p_cemar:.3f}")
print("kesimpulan: email pada kantong yang sama dengan data latih yang "
      "dicemari kini lolos dari filter")

Output yang Dihasilkan:

text
== Data poisoning: label dicemari pada data latih ==
baris latih            : 210
label yang di-flip     : 23 (11.0% data latih)
akurasi uji model bersih : 0.978
akurasi uji model cemar  : 0.889

== Akurasi khusus pada kantong yang dicemari ==
titik uji di kantong   : 9
tertangkap model bersih: 0.778
tertangkap model cemar : 0.000

== Pergeseran decision boundary ==
koefisien model bersih : [1.586, 1.748] intercept +0.446
koefisien model cemar  : [2.272, 2.053] intercept -2.442

== Satu email uji pada titik (0.5, 0.5) ==
peluang 'bermasalah' model bersih : 0.892
peluang 'bermasalah' model cemar  : 0.430
kesimpulan: email pada kantong yang sama dengan data latih yang dicemari kini lolos dari filter

Mari kita telaah bagian-bagian pentingnya:

  1. Baris 15–24 (data sintetis): rng berseed 42 membangkitkan dua klaster — email wajar di sekitar (-1.2, -1.2) dan email bermasalah di sekitar (1.2, 1.2) — lalu train_test_split dengan random_state=42 menjaga evaluasi tetap pada data yang tidak pernah dilatih.
  2. Baris 27–28 (model bersih): LogisticRegression().fit(...) pada label asli menjadi baseline; akurasi ujinya 0,978.
  3. Baris 35–39 (pencemaran): masker kantong_latih memilih baris latih kelas 1 dengan jumlah dua fitur di bawah 1,0 — wilayah terdekat dengan batas keputusan. Salinan y_latih_cemar mengubah 23 baris (11% data latih) menjadi kelas 0; data uji sama sekali tidak disentuh.
  4. Baris 48–53 (evaluasi kantong): akurasi dihitung ulang khusus pada titik uji kelas 1 yang jatuh di wilayah kantong — inilah metrik yang benar-benar relevan bagi attacker, karena kampanye penandaan palsunya hanya menargetkan wilayah tersebut.
  5. Baris 60–70 (bukti pergeseran): intercept turun dari +0.446 ke -2.442 — batas keputusan bergeser menjauhi kantong. Pada titik uji (0.5, 0.5), peluang “bermasalah” runtuh dari 0,892 ke 0,430, di bawah ambang 0,5.

Cara membaca hasilnya: akurasi keseluruhan hanya turun dari 0,978 ke 0,889 — angka yang masih tampak sehat di dashboard. Namun pada kantong yang disasar, tingkat penangkapan jatuh dari 0,778 ke 0,000. Pelajaran praktisnya: metrik agregat menyembunyikan poisoning yang terlokalisasi; evaluasi per segmen wilayah fitur adalah alat diagnosis yang jauh lebih tajam.

Model Theft lewat Inference API #

Ancaman kedua tidak menyentuh data latih sama sekali. Masalah nyatanya: API prediksi yang terbuka menjual akses ke hasil, tetapi bagi siapa pun yang sabar mengumpulkan cukup banyak pasangan input-output, hasil tersebut bisa direkonstruksi menjadi model tiruan — surrogate. Mekanismenya: attacker mengirim input acak yang menutupi ruang fitur, mencatat label keluaran (bahkan hard label saja sudah cukup), lalu melatih model pengganti pada kumpulan kueri itu. Fungsi keputusan victim dan surrogate akan saling mendekat seiring jumlah kueri.

Praktikum berikut melatih victim pada 2.000 baris data privat, lalu mengukur dua hal seiring bertambahnya kueri: kesepakatan keputusan (fraksi titik evaluasi yang diputuskan sama oleh victim dan surrogate) serta kosinus arah koefisien (seberapa mirip arah decision boundary yang berhasil dipulihkan).

python
# Demonstrasi model theft lewat query: model "victim" dilatih pada data
# privat, tetapi inference API-nya terbuka. Attacker hanya mengirim input
# acak dan membaca label keluaran, lalu melatih surrogate murni dari
# pasangan (input, label) tersebut - tanpa memegang data asli sama sekali.

import numpy as np
from sklearn.linear_model import LogisticRegression

rng = np.random.default_rng(42)

# ---------- Data privat milik victim: dua klaster tumpang tindih ----------
n = 2000
X_privat = np.vstack([
    rng.normal(loc=[-1.0, -0.6], scale=1.2, size=(n // 2, 2)),
    rng.normal(loc=[1.0, 0.6], scale=1.2, size=(n // 2, 2)),
])
y_privat = np.array([0] * (n // 2) + [1] * (n // 2))

victim = LogisticRegression().fit(X_privat, y_privat)

# ---------- Titik evaluasi: sampel baru dari proses yang sama ----------
X_eval = np.vstack([
    rng.normal(loc=[-1.0, -0.6], scale=1.2, size=(500, 2)),
    rng.normal(loc=[1.0, 0.6], scale=1.2, size=(500, 2)),
])
y_eval = np.array([0] * 500 + [1] * 500)
label_victim = victim.predict(X_eval)
akurasi_victim = (label_victim == y_eval).mean()

# ---------- Ekstraksi: query acak di dalam kotak [-4, 4] x [-4, 4] ----------
print("== Model theft lewat kueri label (hard label saja) ==")
print(f"akurasi victim pada data evaluasi : {akurasi_victim:.3f}")
print()
print("kueri | kesepakatan | kosinus koefisien | akurasi surrogate")
for m in (25, 100, 400, 1600, 6400):
    X_kueri = rng.uniform(-4, 4, size=(m, 2))
    y_kueri = victim.predict(X_kueri)  # satu panggilan API per baris

    surrogate = LogisticRegression().fit(X_kueri, y_kueri)
    kesepakatan = (surrogate.predict(X_eval) == label_victim).mean()
    akurasi_surrogate = (surrogate.predict(X_eval) == y_eval).mean()

    w_v = victim.coef_[0] / np.linalg.norm(victim.coef_[0])
    w_s = surrogate.coef_[0] / np.linalg.norm(surrogate.coef_[0])
    kosinus = float(w_v @ w_s)

    print(f"{m:5d} | {kesepakatan:11.3f} | {kosinus:17.3f} | {akurasi_surrogate:.3f}")

print()
print("kesimpulan: kesepakatan keputusan dan arah decision boundary victim")
print("pulih hampir sempurna hanya dari label kueri - data latih aslinya")
print("tidak pernah dibocorkan, yang bocor adalah fungsi keputusannya")

Output yang Dihasilkan:

text
== Model theft lewat kueri label (hard label saja) ==
akurasi victim pada data evaluasi : 0.840

kueri | kesepakatan | kosinus koefisien | akurasi surrogate
   25 |       0.919 |             0.964 | 0.839
  100 |       0.959 |             0.998 | 0.835
  400 |       0.985 |             1.000 | 0.833
 1600 |       0.999 |             1.000 | 0.839
 6400 |       1.000 |             1.000 | 0.840

kesimpulan: kesepakatan keputusan dan arah decision boundary victim
pulih hampir sempurna hanya dari label kueri - data latih aslinya
tidak pernah dibocorkan, yang bocor adalah fungsi keputusannya

Bedah baris-per-baris untuk contoh kedua:

  1. Baris 11–19 (victim): victim dilatih pada 2.000 baris privat dengan dua klaster yang tumpang tindih — akurasi 0,840 pada data evaluasi, angka yang wajar untuk masalah non-trivial.
  2. Baris 22–28 (titik evaluasi): 1.000 titik evaluasi dibangkitkan dari proses yang sama, tetapi tidak pernah dilatihkan ke siapa pun; titik ini menjadi arena pengukuran kesepakatan.
  3. Baris 34–36 (kueri): setiap iterasi mengambil m titik seragam di kotak [-4, 4]² lalu hanya membaca victim.predict() — satu panggilan API per baris, tanpa peluang, tanpa skor, tanpa akses koefisien.
  4. Baris 38–44 (surrogate): LogisticRegression yang sama dilatih murni pada pasangan (X_kueri, y_kueri) — model pengganti tidak melihat satu baris data privat pun.
  5. Baris 45–48 (kosinus arah): kedua vektor koefisien dinormalisasi lalu dikalikan; nilai 1,000 berarti arah decision boundary telah dipulihkan sempurna.

Hasilnya ringkas dan tegas: 25 kueri sudah membeli kesepakatan 0,919; 6.400 kueri membeli kesepakatan 1,000 dengan arah koefisien identik, dan akurasi surrogate (0,840) menyamai victim. Bagi penyedia API, pembatasan laju (rate limit) dan anggaran kueri bukan fitur tambahan, melainkan pertahanan inti terhadap kategori ini.

OWASP ML Security Top 10 #

Komunitas keamanan merangkum ancaman-ancaman di atas ke dalam daftar baku OWASP ML Security Top 10 (rilis 2023) — padanan dari OWASP Top 10 yang sudah lama menjadi rujukan keamanan aplikasi web. Daftar ini berguna sebagai checklist saat merancang threat model: setiap entri menunjuk satu kategori risiko beserta vektor aksesnya.

KodeNama entriInti risiko
ML01:2023Input Manipulation AttackInput yang dimodifikasi mengubah prediksi (termasuk evasion)
ML02:2023Data Poisoning AttackData latih yang dicemari menggeser model
ML03:2023Model Inversion AttackRekonstruksi data latih dari output model
ML04:2023Membership Inference AttackMenyimpulkan keanggotaan satu baris di data latih
ML05:2023Model TheftModel tiruan dibangun dari kueri API
ML06:2023ML Supply Chain AttacksPustaka atau artefak tercemar di rantai pasok
ML07:2023Transfer Learning AttackPola tersembunyi dibawa dari model pralatih
ML08:2023Model SkewingFeedback loop produksi dibelokkan
ML09:2023Output Integrity AttackOutput dimodifikasi sebelum sampai konsumen
ML10:2023Neural Net ReprogrammingBobot model diubah untuk mengubah perilaku

Dua praktikum kita memetakan langsung ke ML02 (poisoning kantong label) dan ML05 (pencurian fungsi keputusan lewat kueri). Entri sisanya tersebar di sepanjang siklus hidup yang sudah kita bangun pada Section sebelumnya — ML08, misalnya, hidup di dalam feedback loop retraining yang justru kita rancang di Chapter Pembaruan Model dan Retraining.

Pertahanan Berlapis #

Tidak ada satu alat tunggal yang menutup seluruh daftar di atas; praktik baku adalah pertahanan berlapis yang mengikuti attack surface. Pada jalur data: validasi skema, pembatasan kontributor, dan penandaan provenance tiap baris. Pada artefak model: verifikasi integritas berkas, kontrol akses registri, dan hanya memuat artefak dari sumber terverifikasi. Pada inference API: pembatasan laju dan anggaran kueri, pembulatan output peluang, serta monitoring pola permintaan. Sebagian besar lapisan ini adalah kebijakan dan proses — bukan algoritma baru.

Satu kategori, bagaimanapun, menuntut pemahaman matematis khusus: evasion — input yang digeser sangat tipis sehingga manusia tidak melihat perbedaannya, tetapi model mengubah keputusannya. OWASP menyebutnya ML01; kita akan membangunnya sendiri, mengukurnya, dan melihat pertahanannya bekerja, di Chapter Adversarial Examples.

Referensi Resmi #