Skip to main content
BlogSecurity

Transformers trust_remote_code Vulnerability: CVE-2026-4372

CVE-2026-4372: config.json buatan penyerang membuat transformers sebelum 5.3.0 menjalankan kode Hub walau trust_remote_code mati. Isi fix dan checklist loading.

Matthews Wong

Software Engineer

Oktober 202611 menit baca
Transformers trust_remote_code Vulnerability: CVE-2026-4372

Ringkasan Utama

CVE-2026-4372 menimpa Hugging Face transformers sebelum 5.3.0: config.json buatan penyerang bisa mengarahkan field attention internal ke repository Hub milik penyerang, lalu from_pretrained mengunduh dan menjalankan kode Python-nya walau trust_remote_code mati. Upgrade ke 5.3.0 atau lebih baru, pin revision model ke commit hash yang sudah direview, dan muat dari snapshot yang diunduh lebih dulu.

Kalimat di advisory CVE-2026-4372 yang membuat saya berhenti adalah yang menyebut celah ini terjangkau lewat pemakaian standar yang terdokumentasi. Bukan rantai exploit buatan tangan dan bukan flag aneh: hanya pemanggilan from_pretrained biasa pada sebuah model repository, dengan trust_remote_code tetap di default False. Kebanyakan host inference yang akan saya audit menjadikan default itu satu-satunya pagar.

Post ini membaca sumber primer dengan teliti: advisory GitLab dan GitHub, commit fix a7f8e7f, dan dokumentasi Hugging Face terbaru soal remote code, kernel, offline mode dan pickle scanning. Tidak ada exploit di sini, dan saya memisahkan apa yang dinyatakan advisory, apa yang terlihat di diff, dan apa yang tidak bisa saya konfirmasi. Tujuannya checklist loading yang bisa langsung dipakai di host inference self-hosted.

Apa isi advisory sebenarnya

Advisory GitLab, terbit 26 Mei 2026 dan diperbarui 1 Juli 2026, menyebut isu ini sebagai remote code execution di HuggingFace transformers. Isinya: config.json berbahaya bisa mengisi field _attn_implementation_internal dengan ID repository Hub milik penyerang. Saat korban memuat model itu dengan AutoModelForCausalLM.from_pretrained(), library mengunduh dan menjalankan kode Python dari repository tersebut dengan hak akses sistem operasi korban. Advisory menyebut penyebabnya: deserialisasi atribut config tanpa filter, sanitasi field internal yang lemah, dan eksekusi kernel hasil unduhan tanpa sandbox.

Tiga fakta menentukan eksposur Anda. Semua versi sebelum 5.3.0 terdampak, dan 5.3.0 satu-satunya versi perbaikan yang tercantum. Skor dasar CVSS 3.0 adalah 7.8 (High), vector AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H, dengan CWE-502 dan CWE-1066. Dan advisory menyatakan celah ini melewati mekanisme trust_remote_code dan terjadi pada pemakaian standar, jadi orang yang memuat model bisa tidak sadar. Entri GitHub Advisory Database GHSA-29pf-2h5f-8g72 sejalan soal versi dan skor, mencantumkan seorang analyst sebagai kredit, menautkan laporan bounty huntr, dan EPSS 0,599 persen saat saya membacanya.

Kenapa trust_remote_code salah tempat untuk dicek

Review yang hanya melakukan grep trust_remote_code=True lalu berhenti menganggap satu flag sebagai seluruh batas keamanan. Dokumentasi menunjukkan minimal tiga jalur terpisah di mana pemuatan model bisa menjalankan kode, masing-masing dengan gate sendiri atau tanpa gate sama sekali. Tabel berikut mendaftarkannya, dan baris terakhir adalah yang ditambahkan CVE ini.

JalurPemicunyaGate-nyaYang tidak dicakup gate
Custom modelling codeMemuat repo yang membawa file modelling sendiritrust_remote_code, default False; docs bilang aktifkan hanya untuk repository yang Anda percaya dan kodenya sudah Anda bacaTidak ada yang lain: ia hanya menjaga jalur ini
Hub attention kernelString attn_implementation berbentuk org/repoDocs terbaru: kernels-community dipercaya, repo lain butuh allow_all_kernels=TrueHanya berguna kalau kode penegaknya berjalan pada nilai yang sampai ke sana
Weights berformat pickleMemuat checkpoint pickle seperti pytorch_model.binTanpa flag. Hub menampilkan import pickle per file dan menjalankan scan ClamAVDocs Hub sendiri menyebut scan itu tidak 100 persen aman
Field internal config.json (CVE-2026-4372)Config Hub yang membawa key _attn_implementation_internalMenurut advisory, trust_remote_code tidak menjaganya di versi sebelum 5.3.0Semuanya: nilainya datang sebagai data, bukan flag yang Anda berikan

Pelajarannya ada pada titik di mana data tak tepercaya berubah menjadi jalur kode. Flag yang Anda berikan sendiri mudah diaudit. String yang datang di dalam file unduhan lalu diperlakukan sebagai konfigurasi tidak mudah, itulah sebabnya fix harus mengubah cara config dibaca, bukan menambah warning.

Apa yang diubah commit fix

Commit-nya a7f8e7f, berjudul hanya fix security vuln, menyentuh dua file dengan 22 baris ditambah dan 9 dihapus. Pesannya tidak menyebut CVE, jadi semua yang di bawah ini berasal dari diff dan komentar kodenya, bukan dari tulisan analisis. Dua guard ditambahkan, dan yang kedua lebih informatif.

# Condensed from the diff of commit a7f8e7f. Not a verbatim patch.

# src/transformers/configuration_utils.py, in the loop that applies extra
# kwargs from a Hub config as attributes on the config object.
# Wrong (before): every key from the downloaded config became an attribute.
setattr(self, key, value)

# Right (after): the two internal fields are skipped. The diff comment says
# these should use the public field instead.
if key not in ("_attn_implementation_internal", "_experts_implementation_internal"):
    setattr(self, key, value)   # still inside the existing try/except

# src/transformers/integrations/hub_kernels.py, is_kernel(attn_implementation)
# Before: one regex; any string shaped like org/repo[@rev][:name] was a Hub kernel.
# After:  None returns False; the org name is captured; anything outside
#         "kernels-community" logs warning_once ("For security reasons, we
#         currently only accept kernels from the kernels-community ...")
#         and returns False.

Guard pertama membuat pemuatan konfigurasi mengabaikan dua field internal, sehingga config Hub tidak lagi bisa mengisinya. Komentar di diff bilang field itu seharusnya memakai field publik. Guard kedua mempersempit apa yang dihitung sebagai Hub kernel: nama org kini ditangkap, dan apa pun di luar kernels-community mencatat warning lalu mengembalikan False, dengan komentar alasan bahwa memuat kernel acak membuka arbitrary code execution. Bersama-sama keduanya memotong jalur di sumber dan di tujuannya.

Jangan simpulkan ini bukti bahwa tidak ada jalur lain. Advisory menyebut tiga penyebab, dan commit menangani dua yang terlihat di diff. Saya tidak mengaudit 5.3.0 di luar commit itu, dan advisory tidak mencantumkan rilis perbaikan di jalur 4.x, jadi host yang terkunci di 4.x tidak punya fix selain upgrade major.

Cek apakah host Anda terpapar

Dua hal layak dicek di setiap mesin yang memuat model: versi library terpasang, dan config di cache yang membawa key internal dengan nilai berbentuk repository. Script di bawah hanya memakai standard library. Ia membaca lokasi cache seperti yang dijelaskan dokumentasi Hugging Face, HF_HUB_CACHE dulu, lalu HF_HOME dengan subfolder hub, dan tidak mereproduksi apa pun yang berbahaya.

#!/usr/bin/env python3
"""Audit one host for CVE-2026-4372 exposure. Read-only; standard library only.
Exit code 1 means: upgrade, or review a flagged cache entry."""
import json
import os
import re
import sys
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path

FIXED = (5, 3, 0)  # the advisories list 5.3.0 as the fixed version
INTERNAL_KEYS = {"_attn_implementation_internal", "_experts_implementation_internal"}
TRUSTED_ORG = "kernels-community"


def parse(v: str) -> tuple:
    # "5.3.0rc1" -> (5, 3, 0). re.match avoids reading "0rc1" as 1.
    return tuple(int(re.match(r"\d+", p).group()) if re.match(r"\d+", p) else 0
                 for p in v.split(".")[:3])


def cache_root() -> Path:
    # Order from the huggingface_hub docs: HF_HUB_CACHE, else HF_HOME/hub.
    # XDG_CACHE_HOME only matters when HF_HOME is unset and is not handled here.
    if os.environ.get("HF_HUB_CACHE"):
        return Path(os.environ["HF_HUB_CACHE"])
    home = os.environ.get("HF_HOME") or str(Path.home() / ".cache" / "huggingface")
    return Path(home) / "hub"


def internal_values(node, path=""):
    if isinstance(node, dict):
        for key, val in node.items():
            if key in INTERNAL_KEYS:
                yield f"{path}/{key}", val
            yield from internal_values(val, f"{path}/{key}")
    elif isinstance(node, list):
        for i, val in enumerate(node):
            yield from internal_values(val, f"{path}[{i}]")


def main() -> int:
    bad = 0
    try:
        installed = version("transformers")
        status = "OK" if parse(installed) >= FIXED else "VULNERABLE"
        bad += status == "VULNERABLE"
        print(f"transformers {installed}: {status} (fixed in 5.3.0)")
    except PackageNotFoundError:
        print("transformers not installed in this interpreter")

    for cfg in cache_root().rglob("config.json"):
        try:
            data = json.loads(cfg.read_text())
        except (OSError, ValueError):
            continue
        for where, val in internal_values(data):
            # A repo-shaped value outside the trusted org is a lead, not proof.
            if isinstance(val, str) and "/" in val and val.split("/")[0] != TRUSTED_ORG:
                bad += 1
                print(f"REVIEW {cfg} {where} = {val}")
    return 1 if bad else 0


if __name__ == "__main__":
    sys.exit(main())

Anggap temuan sebagai petunjuk untuk direview, bukan bukti kompromi. Dari sumber yang ada saya tidak bisa memastikan apakah config sah pernah memuat key ini, jadi script hanya menandai nilai berbentuk repo dari luar kernels-community. Kalau ada yang ditandai, cari model repository asal cache itu dan siapa yang menerbitkan commit-nya. Script ini juga tidak melihat XDG_CACHE_HOME, yang menurut docs hanya berlaku kalau HF_HOME tidak diset.

Kegagalan klasiknya: mengaudit flag, bukan datanya

Kegagalan yang saya maksud adalah review yang lolos karena setiap call site bersih. Semua from_pretrained di codebase tidak memakai trust_remote_code, grep membuktikannya, dan host dinyatakan aman. Di bawah advisory ini grep itu tidak cukup, karena input berbahayanya adalah field di dalam file yang diambil saat load.

Ada trade-off kedua yang lebih sunyi pada pinning. Dokumentasi menyebut argumen revision menerima branch, tag atau commit id. Branch atau tag bisa bergeser, jadi config.json yang bersih saat review bisa diganti belakangan, sedangkan commit hash tidak. Docs instalasi menambahkan bahwa dengan commit hash Transformers memakai cache lokal untuk file commit itu, sementara branch atau tag butuh satu pengecekan ke Hub saat load. Pin ke hash menukar kenyamanan dengan config yang bisa Anda nalar.

Upgrade juga ada ongkosnya. Satu-satunya rilis perbaikan di advisory adalah 5.3.0, jadi tim di major version lebih lama harus melakukan upgrade major untuk menutup ini. Saya tidak memverifikasi apa lagi yang berubah di antara dua jalur itu, jadi jalankan test pemuatan model di staging dulu dan jangan menganggap lompatannya gratis.

Checklist loading untuk inference self-hosted

Pola yang akan saya pakai memisahkan loading menjadi tahap build online dan runtime offline. Di tahap build, job CI terautentikasi mengunduh snapshot pada commit hash yang sudah direview manusia. Di runtime, container memakai HF_HUB_OFFLINE=1, yang menurut docs mencegah panggilan HTTP ke Hub, ditambah local_files_only supaya load gagal dengan jelas kalau ada yang kurang, bukan menghubungi luar.

# --- build stage (CI, online): download once, at a commit a human reviewed ---
from huggingface_hub import snapshot_download

REVIEWED = {
    # model repo -> full commit hash recorded in version control, never "main"
    "meta-llama/Llama-3.2-1B": "PASTE_THE_REVIEWED_40_CHAR_COMMIT_HASH",
}
for repo_id, commit in REVIEWED.items():
    snapshot_download(repo_id=repo_id, revision=commit)

# --- runtime stage (container, offline) ---
# Dockerfile, not Python: huggingface_hub reads its variables at import time,
# so os.environ["HF_HUB_OFFLINE"] set after the import does nothing.
#   ENV HF_HUB_OFFLINE=1 USE_HUB_KERNELS=0 HF_HOME=/models/hf

from transformers import AutoModelForCausalLM

MODEL_ID = "meta-llama/Llama-3.2-1B"
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    revision=REVIEWED[MODEL_ID],   # a hash cannot move; a branch or tag can
    local_files_only=True,         # fail loudly instead of reaching out
    attn_implementation="sdpa",    # built-in PyTorch attention, no Hub kernel
    # trust_remote_code stays at its default False. Do not add allow_all_kernels.
)

Dua detail penting. Docs menyatakan huggingface_hub membaca environment variable saat import, jadi mengatur HF_HUB_OFFLINE di dalam Python setelah import tidak berpengaruh; taruh di environment container. Dan docs offline mencatat bahwa branch atau tag masih bisa ter-resolve lewat cache, alasan lain untuk pin hash. Saya tidak memastikan apakah offline mode juga memblokir library kernels mengambil kernel, jadi checklist ini juga mematikan Hub kernel dengan USE_HUB_KERNELS=0, yang menurut panduan kernels menonaktifkannya di semua tempat tanpa mengubah kode.

Tambahkan pengecekan CI yang menggagalkan build jika ada trust_remote_code=True atau allow_all_kernels=True secara literal di repository Anda. Ini tidak menangkap nilai yang datang lewat data, tetapi mencegah dua opt-in terdokumentasi itu ditambahkan diam-diam lewat template config.

Apa yang dihentikan tiap kontrol, dan apa yang masih terbuka

Tidak ada satu kontrol yang menutup semua jalur, dan beberapa saling tumpang tindih. Tabel ini adalah pembacaan saya atas dokumentasi, lengkap dengan batasnya, supaya Anda bisa memilih mana yang dipakai dan celah mana yang diterima.

KontrolYang dihentikanYang masih terbukaDasar
Upgrade ke 5.3.0 atau lebih baruJalur config.json yang dijelaskan di CVE-2026-4372Weights pickle dan jalur lain yang belum ditemukanAdvisory
Pin revision ke commit hash yang sudah direviewRepository yang mengubah config setelah Anda reviewCommit yang direview asal-asalanDocs auto classes dan instalasi Transformers
Unduh dulu, lalu HF_HUB_OFFLINE=1 dan local_files_onlyFetch tak terduga dari Hub saat startupArtefak buruk yang sudah masuk image. Fetch kernel belum terkonfirmasiDocs instalasi dan huggingface_hub
USE_HUB_KERNELS=0Hub kernel di semua tempat, menurut panduan kernelsApa pun yang bukan kernel, dan kecepatan yang diberikan kernel ituPanduan loading kernels
Jangan pernah mengirim allow_all_kernels atau trust_remote_code sebagai TrueDua opt-in terdokumentasi menuju kode pihak ketigaData yang sampai ke jalur kode tanpa opt-in, yaitu CVE iniPanduan loading kernels dan docs auto classes
Publisher tepercaya dan signed commit untuk weightsPemakaian sembarangan checkpoint pickle tak dikenalTanda tangan membuktikan asal, bukan bahwa file amanDok pickle scanning Hub

Satu kontrol tambahan berasal dari saya, bukan dari sumber: batasi akses jaringan keluar dari host inference ke artifact store yang benar-benar Anda pakai. Kode yang diunduh saat load harus datang dari suatu tempat, dan egress allow-list mengubah fetch diam-diam menjadi yang terblokir dan tercatat. Saya belum mengujinya terhadap CVE ini, jadi hitung sebagai defence in depth, bukan fix.

Yang dikerjakan hari Senin

Kerjakan berurutan. Dua langkah pertama hanya butuh beberapa menit dan memberi tahu apakah sisanya mendesak.

  1. Jalankan script audit di setiap host yang memuat model, termasuk laptop yang membangun image, dan catat versi library yang dilaporkan.
  2. Upgrade yang di bawah 5.3.0 di host staging, jalankan test pemuatan model dan generation, lalu promosikan. Tambahkan batas bawah versi di file constraints supaya resolve berikutnya tidak mundur.
  3. Ganti referensi branch dan tag di loader dengan commit hash yang sudah direview, dan simpan hash itu di version control di samping kode.
  4. Pindahkan proses download ke CI, masukkan snapshot ke image atau volume, dan set HF_HUB_OFFLINE=1, USE_HUB_KERNELS=0 serta local_files_only di runtime.
  5. Tambahkan grep CI untuk dua opt-in itu, dan tentukan siapa yang memegang review sebuah model repository sebelum hash-nya boleh masuk daftar.
# 1. What is installed, and is it below 5.3.0?
python -m pip show transformers | grep -i '^version'
python audit_transformers_cve.py ; echo "exit=$?"

# 2. Hold the floor so a later resolve cannot go backwards.
echo 'transformers>=5.3.0' >> constraints.txt
python -m pip install -c constraints.txt -U transformers

# 3. CI guard: fail the build on the two documented opt-ins to third-party code.
if grep -rnE 'trust_remote_code *= *True|allow_all_kernels *= *True' src/ ; then
  echo "third-party code opt-in found" >&2 ; exit 1
fi

Kalau audit menemukan config cache yang mencurigakan, anggap host berpotensi terdampak: cari repository asalnya, rotasi kredensial yang bisa dibaca host itu, dan periksa apa saja yang boleh dijangkau prosesnya. Itu praktik insiden yang umum, bukan sesuatu yang diminta advisory.

Aturan yang saya ambil: flag keamanan hanya melindungi jalur yang dibuatnya. Upgrade ke 5.3.0 atau lebih baru, muat snapshot yang di-pin dari runtime offline, dan audit data yang dibaca loader seteliti argumen yang Anda berikan.

FAQ

Pertanyaan Umum

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

Apa itu CVE-2026-4372 di Hugging Face transformers?

Ini celah remote code execution di transformers versi sebelum 5.3.0. config.json berbahaya mengisi field attention internal dengan ID repository Hub milik penyerang, lalu from_pretrained mengunduh dan menjalankan kode Python darinya. Advisory menilainya CVSS 7,8 (High) dan menyebut ia melewati trust_remote_code.

Apakah trust_remote_code=False melindungi saya dari CVE-2026-4372?

Tidak di versi yang terdampak. Advisory menyebut celah ini melewati mekanisme trust_remote_code pada pemakaian standar, jadi nilai default bukan pertahanan. Perbaikan yang terdokumentasi adalah upgrade ke transformers 5.3.0 atau lebih baru.

Versi transformers mana yang memperbaiki CVE-2026-4372?

Advisory GitLab dan GitHub sama-sama mencantumkan 5.3.0 sebagai versi perbaikan dan semua versi sebelumnya terdampak. Tidak ada rilis perbaikan di jalur lama, jadi host di 4.x perlu upgrade. Uji pemuatan model di staging dulu, karena saya tidak memverifikasi apa lagi yang berubah di antara jalur itu.

Bagaimana cara mem-pin model Hugging Face agar config.json-nya tidak berubah?

Berikan argumen revision berupa commit hash penuh, karena docs menyebut ia menerima branch, tag atau commit id dan hanya hash yang tidak bisa bergeser. Unduh snapshot itu di CI, lalu jalankan host dengan HF_HUB_OFFLINE=1 dan local_files_only supaya tidak ada yang diambil saat runtime.

Apakah Hub kernel dan weights pickle adalah risiko eksekusi kode yang terpisah?

Ya. Docs terbaru menyebut kernel di luar kernels-community butuh allow_all_kernels=True karena memuat kernel bisa menjalankan kode sembarang, dan dokumentasi pickle Hub menyebut checkpoint pickle bisa mengeksekusi kode saat dimuat serta scan-nya tidak mutlak. Anggap keduanya jalur terpisah dengan kontrol terpisah.