Skip to main content
BlogSecurity

Insiden Keamanan Hugging Face 2026: Dataset adalah Kode

Pembacaan cermat disclosure Hugging Face Juli 2026: dataset berbahaya, remote-code loader, template injection pada config, serta kontrol dan urutan rotasi yang membatasi kerusakan.

Matthews Wong

Software Engineer

Oktober 202612 menit baca
Insiden Keamanan Hugging Face 2026: Dataset adalah Kode

Ringkasan Utama

Insiden Hugging Face Juli 2026 bermula dari dataset berbahaya yang menjalankan kode lewat remote-code loader dan template injection pada konfigurasinya. Dari worker, penyerang naik ke node lalu ke credential cluster. Tim defender perlu memperlakukan dataset dan config unggahan sebagai program tak tepercaya, mengisolasi worker, membatasi admission, dan merotasi token.

Pada 16 Juli 2026 Hugging Face menerbitkan disclosure singkat yang mungkin dibaca banyak orang di dunia ML sebagai masalah pihak lain. Sebuah dataset, benda yang di-download setiap training script tanpa pikir panjang, dipakai untuk menjalankan kode di dalam infrastruktur platform itu sendiri. Saya membaca postingannya dua kali sebelum sadar apa yang tidak ada di dalamnya: tidak ada CVE, tidak ada tanggal pasti, tidak ada versi library. Detailnya tipis, dan bagian yang berguna justru bentuk serangannya.

Ini bukan cerita ulang insiden, dan saya tidak punya informasi dari dalam sistem Hugging Face. Saya membaca disclosure-nya, release notes dan docs tool yang terlibat, serta dokumentasi Kubernetes dan Jinja untuk kontrol yang relevan. Setiap klaim di bawah bisa ditelusuri ke salah satu halaman itu, dan di mana disclosure diam, saya bilang diam, bukan mengisinya sendiri. Tidak ada payload exploit di sini, hanya sisi defender.

Apa kata disclosure, dan apa yang tidak disebut

Postingan itu menjelaskan sebuah dataset berbahaya yang menyalahgunakan dua jalur eksekusi kode dalam pemrosesan dataset: remote-code dataset loader dan template injection pada konfigurasi dataset. Penyerang menjalankan kode di sebuah processing worker, naik ke akses level node, mengambil credential cloud dan cluster, lalu bergerak lateral ke beberapa cluster internal. Hugging Face menyebut pergerakan itu terjadi selama akhir pekan dan bahwa mereka mendeteksi serta merespons pada awal pekan penerbitan.

Dampak yang dilaporkan lebih sempit daripada jalurnya. Penyerang mendapat akses tanpa izin ke sebagian kecil dataset internal dan beberapa service credential. Hugging Face mengatakan tidak menemukan bukti perubahan pada model, dataset, atau Spaces publik, dan container image serta package yang dipublikasikan sudah diverifikasi bersih. Mereka masih menilai apakah data partner atau pelanggan terdampak dan akan menghubungi pihak terkait secara langsung.

Ada dua detail yang layak mendapat perhatian lebih. Kampanye ini dijalankan oleh autonomous agent framework, dengan ribuan aksi lewat sandbox berumur pendek dan command-and-control di layanan publik, dan model di baliknya tidak diketahui. Lalu insiden ini pertama kali terdeteksi lewat AI-assisted detection, ketika triase berbasis LLM mengkorelasikan sinyal-sinyalnya. Yang tidak ada juga penting untuk seberapa jauh Anda boleh menyimpulkan: tidak ada tanggal, nama library, nomor versi, maupun cloud provider. Apa pun yang lebih spesifik dari paragraf ini adalah spekulasi, termasuk dari saya.

Dataset adalah input bagi sebuah program, dan sebagian input adalah program

Pembacaan yang berguna bukan 'dataset itu berbahaya'. Melainkan bahwa setiap tahap antara upload dan node adalah tempat data ditafsirkan, dan setiap penafsiran memberi sesuatu kepada pengunggah. Jika disusun sebagai rantai, serangan ini tidak lagi terlihat eksotis. Tiap baris di bawah adalah tahap yang disebut atau tersirat dalam disclosure, apa yang dipercaya tahap itu, dan kontrol yang akan mempersempitnya.

TahapYang dipercayaKontrol yang mempersempit
Dataset loaderBahwa kode loading milik repository aman dieksekusiJangan pernah opt in ke remote code; tentukan sendiri format packaged-nya
Konfigurasi datasetBahwa string di config adalah data, bukan template yang dievaluasiTanpa template engine untuk config unggahan; sandbox hanya jika tak terhindarkan
Processing workerBahwa kode yang jalan di pod tetap di dalam podPod Security Admission dan admission policy pada namespace worker
Node dan identitas cloudBahwa container tidak bisa membaca credential cloud milik nodeHardening metadata service, role node dengan least privilege
Cluster internalBahwa credential satu cluster tidak berlaku di cluster lainIdentitas per cluster, credential berumur pendek, rotasi saat ada kecurigaan

Baca kolom kanan sebagai daftar pertanyaan untuk pipeline Anda sendiri, bukan klaim tentang apa yang kurang di Hugging Face. Disclosure tidak menyebut kontrol mana yang absen, hanya bahwa setelahnya mereka menutup jalur eksekusi kode, membangun ulang node, dan menambah admission control yang lebih ketat. Polanya juga bukan hal baru di ML: dokumentasi Hub Hugging Face sendiri menyatakan bahwa pickle, format default bobot PyTorch, bisa mengeksekusi kode sembarang saat di-load, dan bahwa scanner mereka tidak 100 persen andal. Dataset ternyata punya sifat yang sama.

Remote-code loader: opt-in adalah keputusan kepercayaan

Dataset loading script adalah Python yang dikirim penulis dataset bersama datanya. Dokumentasi datasets Hugging Face di branch main menyatakan bahwa demi keamanan library tidak menjalankan script ini secara default dan Anda harus mengirim trust_remote_code=True untuk memuat dataset yang membutuhkannya. Flag itu adalah seluruh batasnya: seseorang, atau sebuah pipeline, berkata 'jalankan apa yang repository ini suruh'. Di processing worker yang menelan dataset milik orang lain, kalimat itulah kerentanannya.

# 1. Find every place your own code opts in to running a dataset's code.
grep -rnE "trust_remote_code\s*=\s*True" --include="*.py" .

# 2. Know which datasets release each worker image really has. Release notes
#    for 4.0.0 list "Remove scripts altogether" and say trust_remote_code is
#    no longer supported - but the main docs page still describes scripts.
#    Believe the installed package, not either page.
pip show datasets | grep -i "^Version"

# 3. A third-party dataset with data files needs no code at all. Name the
#    packaged builder yourself so a repo-supplied script is never consulted.
python - <<'EOF'
from datasets import load_dataset

# Wrong: the repo decides how it gets loaded (script, builder, config).
# ds = load_dataset("some-org/some-dataset")

# Right: you decide the format; the repo only supplies bytes.
ds = load_dataset(
    "parquet",
    data_files="hf://datasets/some-org/some-dataset/data/train-00000-of-00001.parquet",
    split="train",
)
print(ds.num_rows)
EOF

Ada ganjalan yang tidak bisa saya selesaikan dari halaman yang saya baca. Release notes 4.0.0 library datasets mencantumkan 'Remove scripts altogether' sebagai breaking change dan menyatakan trust_remote_code tidak lagi didukung, tetapi halaman docs branch main masih menjelaskan script dan flag itu. Disclosure juga tidak menyebut apakah library itu terlibat, atau versi mana yang dipakai worker. Jadi saran jujurnya membosankan: cek versi terpasang di setiap worker image, dan jangan berasumsi image lama atau dependency yang di-pin punya perilaku yang lebih baru.

Dua aturan praktis menyusul. Pertama, cari opt-in itu di kode Anda sendiri, karena flag yang diset sekali di sebuah helper diam-diam berlaku untuk setiap dataset yang lewat. Kedua, pindahkan keputusan format keluar dari repository: jika datanya Parquet, JSON, atau CSV, muat dengan packaged builder dan path file, sehingga tidak ada jalur di mana script milik pengunggah ikut dipertimbangkan. Blok di atas melakukan keduanya. Saya belum menjalankannya di stack Hugging Face maupun stack siapa pun, jadi uji dulu pada dataset yang Anda kendalikan.

Pasang grep itu di CI sebagai pemeriksaan yang gagal untuk perubahan apa pun yang menambah trust_remote_code=True. Reviewer yang sekilas membaca diff akan meloloskan satu keyword argument tambahan; build merah tidak akan.

Template injection pada konfigurasi: jalur kedua

Jalur kedua adalah template injection pada konfigurasi dataset. Disclosure menyebut kelasnya, bukan engine-nya, jadi saya tidak akan menebak engine mana yang terlibat. Mekanisme umumnya sama di mana-mana: field config dimaksudkan sebagai string, seseorang meneruskannya ke template renderer untuk mengisi nama atau path, dan bahasa template yang bisa menjangkau atribut dan fungsi menjadi jalan untuk menjalankan kode. Jinja adalah contoh Python yang umum, dan dokumentasinya berguna karena terus terang soal batas perbaikannya.

# Wrong: a string from an uploaded config becomes a program.
from jinja2 import Environment

def render_name(cfg: dict, row: dict) -> str:
    return Environment().from_string(cfg["name_template"]).render(row=row)

# Better: Jinja's sandbox. Insecure access raises SecurityError, but the docs
# say plainly that "The sandbox alone is not a solution for perfect security."
from jinja2.exceptions import SecurityError
from jinja2.sandbox import ImmutableSandboxedEnvironment

SANDBOX = ImmutableSandboxedEnvironment()

def render_name_sandboxed(cfg: dict, row: dict) -> str:
    try:
        return SANDBOX.from_string(cfg["name_template"]).render(row=row)
    except SecurityError:
        raise ValueError("template rejected")  # reject the dataset, do not retry

# Best: no template engine at all. Substitution over a fixed key list cannot
# reach an attribute, a filter or a function, because there is nothing to reach.
ALLOWED_KEYS = ("id", "split", "language")

def render_name_plain(cfg: dict, row: dict) -> str:
    out = cfg["name_pattern"]
    for key in ALLOWED_KEYS:
        out = out.replace("@" + key + "@", str(row.get(key, "")))
    return out

Docs sandbox Jinja menyatakan sandbox bisa dipakai untuk merender template tak tepercaya, bahwa akses tak aman memunculkan SecurityError, dan, dalam napas yang sama, bahwa exception lain bisa muncul saat render sehingga pemanggil harus menangkapnya. Mereka juga menulis 'The sandbox alone is not a solution for perfect security.' ImmutableSandboxedEnvironment adalah varian lebih ketat yang memblokir perubahan pada list, set, dan dict bawaan. Kode di atas menunjukkan tiga tingkat: versi yang menimbulkan insiden, versi sandbox, dan versi tanpa template engine sama sekali.

Saya memilih yang ketiga. Daftar key substitusi yang tetap tidak bisa menjangkau atribut, filter, atau fungsi, jadi tidak ada sandbox untuk dijebol dan tidak ada yang harus terus di-patch. Biayanya nyata: Anda kehilangan conditional dan loop di config, dan pengguna yang menyukainya akan mengeluh. Jika tetap memakai template engine, perlakukan SecurityError sebagai alasan menolak seluruh dataset, jangan pernah mencoba ulang dengan environment yang lebih longgar.

Dari worker pod ke node: di sinilah blast radius ditentukan

Eksekusi kode di processing worker itu buruk tetapi terbatas. Yang mengubahnya jadi insiden adalah langkah berikutnya: akses level node, lalu credential cloud dan cluster, lalu pergerakan lateral. Kontrolnya semua native Kubernetes dan tidak ada yang eksotis. Pod Security Admission, stable sejak Kubernetes v1.25, menerapkan Pod Security Standards per namespace lewat label. Mode enforce menolak pod, sedangkan audit dan warn hanya mencatat atau memberi tahu client. Satu detail dari docs yang sering menjebak: enforce berlaku pada objek Pod, bukan Deployment atau Job, dan halamannya memperingatkan agar service account controller tidak dikecualikan karena itu mengecualikan siapa pun yang bisa membuat workload.

# Namespace that runs untrusted-input workers. Pod Security Admission, stable
# since Kubernetes v1.25. enforce rejects the Pod; warn only tells the client.
apiVersion: v1
kind: Namespace
metadata:
  name: dataset-workers
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/audit: restricted
---
# Your own rule on top, with ValidatingAdmissionPolicy (stable since v1.30).
# This one refuses any Pod that mounts a hostPath volume. My example, not the docs'.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: no-hostpath.example.com
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
  validations:
    - expression: "!has(object.spec.volumes) || !object.spec.volumes.exists(v, has(v.hostPath))"
      message: "hostPath volumes are not allowed in dataset-workers"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: no-hostpath-binding.example.com
spec:
  policyName: no-hostpath.example.com
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: dataset-workers

Level Baseline dari Pod Security Standards melarang jembatan pod-ke-host yang paling penting di sini: ia mewajibkan hostNetwork, hostPID, dan hostIPC tidak diset atau false, mewajibkan securityContext.privileged tidak diset atau false, dan melarang volume hostPath sepenuhnya. ValidatingAdmissionPolicy, stable sejak v1.30, memungkinkan Anda menambah aturan sendiri dalam CEL, dengan validationActions Deny, Warn, atau Audit. Manifest di atas menerapkan Baseline pada namespace worker, mengaudit terhadap Restricted, dan menambah aturan hostPath yang merupakan contoh saya sendiri, bukan dari docs.

# If the nodes are EC2 instances: require IMDSv2 and keep the PUT response hop
# limit at 1 (the default), so a process inside a container cannot mint a
# metadata token. A request without a valid token then gets 401.
# Hugging Face did not say which cloud it runs on - this is one provider's lever.
aws ec2 modify-instance-metadata-options \
  --instance-id i-0123456789abcdef0 \
  --http-tokens required \
  --http-put-response-hop-limit 1

Identitas cloud milik node adalah separuh lainnya. Disclosure menyebut credential diambil tetapi tidak dari mana, jadi saya tidak bisa mengatakan metadata service terlibat. Jika node Anda instance EC2, AWS mendokumentasikan bahwa IMDSv2 berbasis session, bahwa request tanpa token valid mendapat 401 setelah token diwajibkan, dan bahwa hop limit response PUT secara default 1 pada level protokol IP. Mewajibkan token dan membiarkan hop limit di 1 adalah tuasnya, seperti pada blok kedua di atas. AWS juga mencatat Anda mungkin menaikkan hop limit demi kompatibilitas dengan layanan container, dan itulah setelan yang perlu diaudit.

Trade-off, dan apa yang tidak diperbaiki

Setiap kontrol di atas punya biaya, dan berpura-pura sebaliknya adalah cara checklist semacam ini ditinggalkan. Menerapkan Baseline di namespace worker akan menolak pod sah yang memakai hostPath untuk scratch volume atau device plugin, jadi Anda akan menghabiskan seminggu mencarinya. Menolak remote code berarti sebagian dataset lama tidak bisa di-ingest, dan jawabannya adalah meminta penulisnya mengonversi ke format standar, bukan membuat pengecualian. Menghapus templating dari config mematahkan fitur yang dipakai seseorang.

Ada juga batas pada apa yang bisa Anda simpulkan dari disclosure. Ia tidak menyebut apakah loader yang dieksploitasi ada di library open-source, fork internal, atau kode pemrosesan khusus. Tidak ada versi, tanggal, atau provider. Hugging Face juga menyatakan penilaian atas data partner dan pelanggan masih berjalan. Defender yang menyalin daftar kontrol dari disclosure sesingkat ini sedang menebak kontrol mana yang akan berpengaruh, dan sebaiknya mengatakannya di design review.

Jangan membaca 'tidak ada bukti perubahan pada model, dataset, atau Spaces publik' sebagai 'tidak ada yang perlu dilakukan'. Itu pernyataan tentang apa yang diverifikasi Hugging Face di sisinya. Token Anda sendiri, dataset yang Anda cache, dan pipeline apa pun yang menarik dari platform selama periode insiden tetap menjadi tanggung jawab Anda untuk ditinjau, dan saran disclosure sendiri adalah merotasi access token dan memeriksa aktivitas akun terbaru.

Satu catatan lagi soal forensik. Hugging Face mengatakan analisisnya awalnya terhalang guardrail pada model komersial hosted, sehingga mereka menjalankan analisis dengan model open-weight zai-org/GLM-5.2 di infrastruktur sendiri, menjaga data dan credential penyerang tetap di lingkungan mereka. Mereka menyarankan model self-hosted yang mumpuni sudah diperiksa sebelum insiden. Itu keputusan perencanaan dengan biaya, hardware, dan reviu sendiri, bukan sesuatu yang diimprovisasi saat Anda membutuhkannya.

Apa yang diubah Hugging Face, dan padanannya untuk Anda

Daftar remediation di disclosure adalah template yang lebih baik daripada kebanyakan post-mortem vendor karena konkret. Tabel berikut memetakan tiap butir ke apa yang bisa dilakukan tim yang menjalankan ingestion dataset atau model sendiri. Kolom kanan adalah terjemahan saya, bukan dari Hugging Face.

Hugging Face melakukanPadanannya untuk Anda
Menutup jalur eksekusi kode dataset yang dipakai untuk akses awalHapus trust_remote_code dan templating config dari setiap jalur ingestion
Membersihkan foothold di cluster terdampak dan membangun ulang node yang terkompromiBangun ulang node dari image, jangan dibersihkan; anggap node yang pernah menjalankan kode tak tepercaya sudah hangus
Mencabut dan merotasi credential terdampak, lalu memulai rotasi pencegahan yang lebih luasRotasi berurutan menurut blast radius, mulai dari apa pun yang bisa dibaca worker atau node
Menambah guardrail dan admission control cluster yang lebih ketatPod Security Admission plus ValidatingAdmissionPolicy pada namespace worker
Memperbaiki deteksi dan alerting untuk sinyal severity tinggiBeri alert saat worker pod memunculkan shell, mengakses alamat metadata, atau memanggil cluster lain

Jika Anda memakai Hugging Face, instruksi disclosure sendiri adalah merotasi access token sebagai tindakan pencegahan, meninjau aktivitas terbaru di akun, dan melaporkan kekhawatiran ke security@huggingface.co. Jika Anda menjalankan ingestion sendiri, rotasi dengan urutan ini, karena urutan itulah yang membatasi kerusakan bila Anda terpotong di tengah jalan.

  1. Credential yang bisa dibaca worker langsung: environment variable, secret yang di-mount, dan token service account.
  2. Credential yang bisa dibaca node: cloud role, instance profile, dan secret registry atau image pull.
  3. Credential cluster yang bisa dipakai identitas worker atau node terhadap cluster lain, termasuk kubeconfig dan bearer token.
  4. Access token pribadi dan CI untuk Hub atau sumber dataset mana pun yang ditarik pipeline, diterbitkan dengan scope tersempit yang masih berfungsi.
  5. Sisanya, sebagai pencegahan, setelah empat langkah pertama selesai dan nilai lama terkonfirmasi mati, bukan sekadar diganti.

Yang dikerjakan hari Senin

Semua ini tidak butuh proyek. Cukup satu sore, daftar tempat pipeline Anda menelan data yang bukan Anda tulis, dan kesiapan untuk mengetahui bahwa ternyata ada.

  1. Jalankan grep trust_remote_code di setiap repository dan notebook yang memberi makan worker bersama, dan catat versi datasets terpasang di setiap worker image.
  2. Daftar setiap field config yang sampai ke template renderer. Untuk masing-masing, putuskan: hapus engine, sandbox, atau pastikan field itu tak pernah diisi pengguna.
  3. Beri label namespace ingestion dengan Pod Security Admission enforce di baseline serta warn dan audit di restricted, lalu baca isi warning selama seminggu sebelum mengetatkan.
  4. Periksa apa yang bisa dijangkau pod dari dalam: endpoint metadata node, API server cluster lain, dan credential yang di-mount. Jika di EC2, wajibkan IMDSv2 dan pastikan hop limit.
  5. Tulis urutan rotasi Anda dan siapa yang bisa menjalankannya jam 03.00. Checklist yang belum pernah dilatih adalah dokumen, bukan kontrol.

Lalu baca ulang disclosure Hugging Face dengan pipeline Anda sendiri di kepala, dan tandai tiap kalimat sebagai 'kami terpapar', 'kami tidak', atau 'kami tidak tahu'. Tumpukan ketiga adalah hasil sebenarnya.

Pelajaran yang saya ambil adalah aturan, bukan daftar: apa pun yang diunggah orang asing lalu dibuka oleh sebuah program adalah program, apa pun ekstensi filenya. Loader, template config, dan bobot model semuanya termasuk. Tentukan sendiri formatnya, jangan beri interpreter apa pun untuk dievaluasi, anggap worker pada akhirnya akan menjalankan sesuatu yang bermusuhan, dan pastikan apa yang bisa dijangkaunya dari sana kecil, berumur pendek, dan bisa dirotasi.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa yang terjadi pada insiden keamanan Hugging Face Juli 2026?

Hugging Face mengumumkan pada 16 Juli 2026 bahwa sebuah dataset berbahaya menyalahgunakan remote-code dataset loader dan template injection pada konfigurasi dataset untuk menjalankan kode di processing worker. Penyerang naik ke level node, mengambil credential cloud dan cluster, lalu masuk ke beberapa cluster internal. Hugging Face melaporkan akses ke sebagian kecil dataset internal dan service credential.

Apakah model, dataset, atau Spaces publik Hugging Face diubah?

Hugging Face menyatakan tidak menemukan bukti perubahan pada model, dataset, atau Spaces publik, dan container image serta package yang dipublikasikan diverifikasi bersih. Saat disclosure terbit, mereka masih menilai apakah data partner atau pelanggan terdampak. Itu pernyataan tentang verifikasi mereka sendiri, jadi Anda tetap perlu meninjau token dan aktivitas akun Anda.

Apa yang harus saya lakukan jika memakai token Hugging Face?

Hugging Face menyarankan merotasi access token sebagai tindakan pencegahan dan meninjau aktivitas terbaru di akun Anda. Kekhawatiran bisa dilaporkan ke security@huggingface.co. Saat menerbitkan token baru, beri scope tersempit yang masih berfungsi dan cabut nilai lama, jangan dibiarkan berdampingan.

Apakah trust_remote_code=True berbahaya?

Ya, itu keputusan eksplisit untuk menjalankan kode yang disediakan repository dataset. Docs datasets menyebut script tidak dijalankan secara default demi keamanan dan membutuhkan flag itu. Release notes 4.0.0 mencantumkan script dihapus dan flag tidak lagi didukung, jadi cek versi di setiap worker image dan cari opt-in itu di kode Anda.

Bagaimana mencegah worker yang terkompromi menjangkau node?

Beri label namespace worker dengan Pod Security Admission enforce di baseline, yang melarang container privileged, host namespace, dan volume hostPath, lalu tambahkan ValidatingAdmissionPolicy untuk aturan Anda sendiri. Setelah itu batasi apa yang bisa diberikan node: wajibkan IMDSv2 dengan hop limit 1 di EC2 dan beri role node least privilege. Disclosure tidak menyebut cloud Hugging Face, jadi anggap ini kontrol umum, bukan diagnosis.