Hampir setiap tim di Indonesia kini punya alur kerja yang melibatkan model bahasa besar (LLM): meringkas tiket layanan pelanggan, menganalisis log insiden, mengklasifikasi keluhan, hingga membantu analis SOC menyusun laporan. Masalahnya, teks yang masuk ke prompt sering memuat data pribadi: NIK pelanggan, NPWP, nomor telepon, dan alamat email. Begitu teks itu dikirim ke layanan LLM pihak ketiga, Anda sedang melakukan pemrosesan dan, bisa jadi, transfer data pribadi. Dalam tutorial ini kita membangun lapisan redaksi sederhana berbasis Python (hanya pustaka standar) yang menyaring data pribadi khas Indonesia sebelum prompt keluar dari perimeter Anda.
Mengapa Redaksi Sebelum Prompt Penting
Undang-Undang Nomor 27 Tahun 2022 tentang Pelindungan Data Pribadi (UU PDP) mengatur prinsip-prinsip dasar pemrosesan data pribadi, termasuk pembatasan tujuan dan kewajiban menjaga keamanan data. Mengirim data pelanggan mentah ke layanan eksternal hanya untuk membuat ringkasan adalah contoh klasik pemrosesan yang berlebihan: LLM tidak perlu tahu NIK seseorang untuk meringkas keluhannya. Prinsip data minimization menyarankan agar kita hanya mengirim apa yang benar-benar dibutuhkan untuk tugas tersebut.
Ada tiga alasan praktis lain. Pertama, prompt dan respons sering tercatat di log aplikasi, sistem observabilitas, atau riwayat percakapan sehingga data pribadi menyebar ke banyak tempat. Kedua, jika terjadi insiden pada penyedia layanan, cakupan dampak ikut mengecil bila data yang terkirim sudah dipseudonimkan. Ketiga, redaksi di sisi klien adalah kontrol yang bisa Anda audit sendiri, tidak bergantung pada janji pihak ketiga.
Catatan: tutorial ini adalah contoh teknis, bukan nasihat hukum. Konsultasikan kewajiban kepatuhan spesifik organisasi Anda dengan penasihat hukum dan Pejabat/Petugas PDP.
Rancangan: Deteksi, Validasi, Pseudonimisasi, Pemulihan
Lapisan yang kita bangun memiliki empat tahap:
- Deteksi kandidat data pribadi memakai ekspresi reguler.
- Validasi struktur agar angka acak 16 digit tidak ikut tersensor (mengurangi false positive).
- Pseudonimisasi: nilai asli diganti token deterministik berbasis HMAC, sehingga entitas yang sama selalu mendapat token yang sama dan LLM tetap bisa menalar hubungan antar-entitas ("pelanggan yang sama muncul dua kali").
- Pemulihan pada respons: token dikembalikan ke nilai asli, hanya di dalam sistem Anda, memakai peta token yang tidak pernah dikirim keluar.
Struktur NIK yang Bisa Divalidasi
NIK terdiri dari 16 digit. Enam digit pertama adalah kode wilayah (dua digit pertama menunjukkan provinsi), enam digit berikutnya adalah tanggal lahir dalam format HHBBTT, dan empat digit terakhir adalah nomor urut. Khusus perempuan, angka tanggal ditambah 40 (misalnya lahir tanggal 5 menjadi 45). Struktur ini memungkinkan kita menolak deretan 16 digit yang jelas bukan NIK, seperti nomor transaksi atau nomor referensi yang kebetulan panjangnya sama. Validasi ini bersifat heuristik: ia tidak membuktikan bahwa NIK itu terdaftar, hanya bahwa polanya masuk akal. Untuk redaksi, sifat konservatif ini memang yang kita inginkan.
Untuk NPWP, wajib pajak orang pribadi kini bertransisi memakai NIK sebagai NPWP (16 digit), sementara format lama 15 digit (misalnya 09.254.294.3-407.000) masih sering muncul di dokumen dan sistem lama. Dalam contoh ini kita mendeteksi format lama yang memakai tanda titik dan tanda hubung; NPWP 16 digit otomatis tertangkap oleh pola NIK. Periksa ketentuan terbaru Direktorat Jenderal Pajak untuk kebutuhan produksi Anda.
Langkah 1: Validator NIK
import re, hmac, hashlib, os
from dataclasses import dataclass
from datetime import date
KODE_PROVINSI = {"11","12","13","14","15","16","17","18","19","21",
"31","32","33","34","35","36","51","52","53",
"61","62","63","64","65","71","72","73","74","75","76",
"81","82","91","92","93","94","95","96"}
def nik_valid(nik: str) -> bool:
if not re.fullmatch(r"\d{16}", nik):
return False
if nik[:2] not in KODE_PROVINSI:
return False
hari, bulan, tahun2 = int(nik[6:8]), int(nik[8:10]), int(nik[10:12])
if hari > 40: # perempuan: tanggal + 40
hari -= 40
if not 1 <= bulan <= 12:
return False
for abad in (1900, 2000):
try:
d = date(abad + tahun2, bulan, hari)
except ValueError:
continue
if d <= date.today():
return True
return False
Daftar kode provinsi di atas adalah contoh yang perlu Anda sesuaikan dengan data wilayah resmi terbaru, karena pemekaran provinsi dapat menambah kode baru. Di produksi, simpan daftar itu sebagai berkas konfigurasi yang mudah diperbarui, bukan konstanta di kode.
Langkah 2: Pola Deteksi dan Token HMAC
PATTERNS = [
("NIK", re.compile(r"(?<!\d)\d{16}(?!\d)"), nik_valid),
("NPWP", re.compile(r"(?<!\d)\d{2}\.\d{3}\.\d{3}\.\d-\d{3}\.\d{3}(?!\d)"), None),
("EMAIL", re.compile(r"[\w.+-]+@[\w-]+(?:\.[\w-]+)+"), None),
("HP", re.compile(r"(?<!\d)(?:\+62|62|0)8\d{8,11}(?!\d)"), None),
]
KEY = os.environ.get("REDACT_KEY", "ganti-di-produksi").encode()
def token(label, nilai):
h = hmac.new(KEY, nilai.encode(), hashlib.sha256).hexdigest()[:8]
return f"[{label}_{h}]"
Mengapa HMAC dan bukan hash biasa? NIK punya ruang kemungkinan yang relatif kecil dan terstruktur, sehingga hash SHA-256 polos mudah di-brute force oleh siapa pun yang memegang token. Dengan kunci rahasia (HMAC), token tidak bisa dibalik atau ditebak tanpa kunci. Simpan kunci di secret manager, jangan di repositori, dan rotasikan sesuai kebijakan. Delapan karakter heksadesimal cukup untuk membedakan entitas dalam satu dokumen, tetapi perpanjang bila Anda memproses korpus besar agar tabrakan token tidak terjadi.
Langkah 3: Fungsi Redaksi dan Pemulihan
@dataclass
class Temuan:
label: str
mulai: int
akhir: int
def redaksi(teks):
temuan = []
for label, pola, cek in PATTERNS:
for m in pola.finditer(teks):
if cek is None or cek(m.group()):
temuan.append(Temuan(label, m.start(), m.end()))
temuan.sort(key=lambda t: (t.mulai, -t.akhir))
hasil, terakhir, peta = [], 0, {}
for t in temuan:
if t.mulai < terakhir: # lewati tumpang tindih
continue
asli = teks[t.mulai:t.akhir]
tk = token(t.label, asli)
peta[tk] = asli
hasil += [teks[terakhir:t.mulai], tk]
terakhir = t.akhir
hasil.append(teks[terakhir:])
return "".join(hasil), peta
def pulihkan(teks, peta):
for tk, asli in peta.items():
teks = teks.replace(tk, asli)
return teks
Fungsi redaksi mengembalikan dua hal: teks aman untuk dikirim dan peta token-ke-nilai-asli. Peta ini adalah bagian paling sensitif. Simpan hanya di memori proses atau penyimpanan terenkripsi dengan masa hidup pendek, jangan ikut ke prompt, log, maupun respons API.
Langkah 4: Uji Coba
uji = ("NIK 3171014505900001 dan 3171854505900001, "
"NPWP 09.254.294.3-407.000, email a@b.id, "
"hp 081234567890, acak 1234567890123456")
aman, peta = redaksi(uji)
print(aman)
# NIK [NIK_f1f4cb85] dan [NIK_e98d4c08], NPWP [NPWP_1a1f5f5b],
# email [EMAIL_b7ed7409], hp [HP_94dfe3ff], acak 1234567890123456
print(pulihkan(aman, peta) == uji) # True
Perhatikan bahwa deretan "1234567890123456" tidak disensor karena gagal validasi struktur NIK, sedangkan NIK perempuan (tanggal lahir ditambah 40) tetap terdeteksi. Token hasil keluaran akan berbeda di sistem Anda karena bergantung pada nilai REDACT_KEY.
Menyambungkan ke Pemanggilan LLM
Pola integrasinya sederhana: bungkus fungsi pemanggilan LLM Anda sehingga setiap prompt melewati redaksi lebih dulu, dan setiap respons melewati pulihkan sebelum ditampilkan ke pengguna berwenang. Tambahkan instruksi sistem agar model memperlakukan token seperti [NIK_f1f4cb85] sebagai pengenal buram dan tidak mencoba menebak isinya.
def tanya_llm_aman(prompt_mentah, panggil_llm):
prompt_aman, peta = redaksi(prompt_mentah)
jawaban = panggil_llm(
system="Token seperti [NIK_xxxx] adalah pengenal buram. "
"Jangan menebak atau merekonstruksi nilai aslinya.",
user=prompt_aman,
)
return pulihkan(jawaban, peta)
Keterbatasan yang Harus Dipahami
Pendekatan berbasis pola punya batas. Nama orang, alamat, dan data kontekstual (misalnya "pasien di kamar 12 yang baru menjalani operasi") tidak tertangkap regex. Untuk kebutuhan itu, pertimbangkan kombinasi dengan pengenal entitas bernama (NER) atau pustaka de-identifikasi seperti Microsoft Presidio, yang mendukung recognizer kustom sehingga validator NIK di atas bisa dipasang sebagai komponennya. Selain itu, pseudonimisasi bukan anonimisasi: selama ada peta atau kunci, data tetap dapat dipulihkan, jadi secara hukum data tersebut umumnya masih diperlakukan sebagai data pribadi di sisi Anda.
Risiko lain adalah variasi format: nomor telepon dengan spasi atau tanda hubung, NIK yang ditulis dengan spasi, atau data di dalam gambar dan PDF hasil pindai. Bangun korpus uji dari pola nyata di organisasi Anda (dengan data sintetis, bukan data asli), ukur recall dan precision, dan perlakukan kegagalan deteksi sebagai insiden yang ditinjau.
Daftar Periksa Penerapan
- Pasang redaksi di satu titik keluar (gateway) agar tidak ada jalur yang terlewat.
- Jangan mencatat prompt mentah maupun peta token di log.
- Simpan
REDACT_KEYdi secret manager dan rotasi berkala. - Uji dengan data sintetis, termasuk kasus tepi (NIK perempuan, tanggal tidak valid, angka acak 16 digit).
- Dokumentasikan alur ini dalam catatan pemrosesan (RoPA) dan penilaian dampak pelindungan data bila diwajibkan.
- Tinjau perjanjian pemrosesan data dengan penyedia LLM, termasuk lokasi pemrosesan dan kebijakan retensi.
Penutup
Redaksi sebelum prompt adalah kontrol murah dengan dampak besar: sedikit kode, tetapi mengurangi paparan data pribadi di setiap pemanggilan LLM. Mulailah dari pola yang paling sering muncul di data Anda, ukur hasilnya, lalu perluas ke NER dan format dokumen lain. Siberindo dapat membantu organisasi Anda merancang arsitektur pemanfaatan AI yang aman, mulai dari penilaian risiko data hingga pengujian keamanan alur LLM.

