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.
| Komponen | Pihak yang biasanya menyentuh | Risiko keamanan utama |
|---|---|---|
| Jalur data latih | Pengguna, vendor data, sistem eksternal | Data poisoning, pencemaran feedback loop |
Artefak model (joblib/pickle) | Tim data, registri model, pipeline CI/CD | Model poisoning, eksekusi kode berbahaya saat loading |
| Inference API | Pengunjung anonim, pelanggan, aplikasi pihak ketiga | Evasion, 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 ancaman | Waktu kejadian | Akses yang dibutuhkan attacker | Sasaran |
|---|---|---|---|
| Data poisoning | Pelatihan (atau retraining) | Menulis ke data latih | Decision boundary model |
| Evasion (adversarial example) | Inferensi | Hanya mengirim input | Prediksi satu permintaan |
| Model theft (extraction) | Inferensi berulang | API terbuka + anggaran kueri | Fungsi keputusan model |
| Model inversion / membership inference | Inferensi berulang | Output peluang/skor | Data 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.
# 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:
== 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 filterMari kita telaah bagian-bagian pentingnya:
- Baris 15–24 (data sintetis):
rngberseed 42 membangkitkan dua klaster — email wajar di sekitar(-1.2, -1.2)dan email bermasalah di sekitar(1.2, 1.2)— lalutrain_test_splitdenganrandom_state=42menjaga evaluasi tetap pada data yang tidak pernah dilatih. - Baris 27–28 (model bersih):
LogisticRegression().fit(...)pada label asli menjadi baseline; akurasi ujinya 0,978. - Baris 35–39 (pencemaran): masker
kantong_latihmemilih baris latih kelas 1 dengan jumlah dua fitur di bawah 1,0 — wilayah terdekat dengan batas keputusan. Salinany_latih_cemarmengubah 23 baris (11% data latih) menjadi kelas 0; data uji sama sekali tidak disentuh. - 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.
- Baris 60–70 (bukti pergeseran): intercept turun dari
+0.446ke-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).
# 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:
== 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 keputusannyaBedah baris-per-baris untuk contoh kedua:
- 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.
- 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.
- Baris 34–36 (kueri): setiap iterasi mengambil
mtitik seragam di kotak[-4, 4]²lalu hanya membacavictim.predict()— satu panggilan API per baris, tanpa peluang, tanpa skor, tanpa akses koefisien. - Baris 38–44 (surrogate):
LogisticRegressionyang sama dilatih murni pada pasangan(X_kueri, y_kueri)— model pengganti tidak melihat satu baris data privat pun. - 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.
| Kode | Nama entri | Inti risiko |
|---|---|---|
| ML01:2023 | Input Manipulation Attack | Input yang dimodifikasi mengubah prediksi (termasuk evasion) |
| ML02:2023 | Data Poisoning Attack | Data latih yang dicemari menggeser model |
| ML03:2023 | Model Inversion Attack | Rekonstruksi data latih dari output model |
| ML04:2023 | Membership Inference Attack | Menyimpulkan keanggotaan satu baris di data latih |
| ML05:2023 | Model Theft | Model tiruan dibangun dari kueri API |
| ML06:2023 | ML Supply Chain Attacks | Pustaka atau artefak tercemar di rantai pasok |
| ML07:2023 | Transfer Learning Attack | Pola tersembunyi dibawa dari model pralatih |
| ML08:2023 | Model Skewing | Feedback loop produksi dibelokkan |
| ML09:2023 | Output Integrity Attack | Output dimodifikasi sebelum sampai konsumen |
| ML10:2023 | Neural Net Reprogramming | Bobot 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 #
- Model persistence — scikit-learn
— peringatan resmi mengenai risiko keamanan berkas
pickle/joblibdan panduan menyimpan model. - OWASP Machine Learning Security Top 10 — proyek resmi OWASP yang memuat daftar ML01–ML10 beserta deskripsi tiap risiko.
- Persistence — joblib — mekanisme serialisasi model dan catatan batas kepercayaannya.