İmza neyi kanıtlar: geçerli provenance taşıyan bir tedarik zinciri saldırısı

Provenance bir paketin hangi committen çıktığını kanıtlıyor, o commitin yetkili olduğunu değil.

4 Ağustos 2026 sabahı npm üzerinde kendi kendine yayılan bir zararlı yazılım dolaştı. Bir olay raporu dört saatten kısa sürede 444 paket ve 2212 zehirli sürüm sayıyor; bağımsız ikinci bir analiz 400 üzeri paket diyor. İlgi çekici olan sayılar değil. Saldırının başlangıç paketleri geçerli imza ve geçerli provenance taşıyordu, yani ekosistemin son üç yılda kurduğu doğrulama katmanından temiz geçtiler.

Provenance neyi kanıtlar

provenance proves which commit was built. It cannot prove the commit was authorized.

Cümle olay raporunun kendisinden, ve bu yazının tamamını özetliyor. Provenance bir paketin hangi kaynak commitinden ve hangi iş akışıyla üretildiğini kriptografik olarak bağlar. Bağlamadığı tek şey o commitin yetkili biri tarafından atılıp atılmadığıdır. Bu saldırıda bir bakımcının hesabı ele geçirildi, kod doğrudan ana dala itildi, ve paketleri o deponun kendi meşru iş akışları yayımladı. Ortaya çıkan zincir gerçekten doğru üretilmişti; yalnızca yanlış şeyi taşıyordu.

İkinci analiz aynı mekanizmayı başka bir yerden tarif ediyor: saldırgan kaynak deposunu kontrol ettiği ve meşru iş akışları kodu çalıştırdığı için SLSA Build Level 3 seviyesinde geçerli provenance üretildi. Yayılma da aynı mantıkla ilerledi: bulaşan makineden çalınan npm token’ı ile o token’ın yazma yetkisi olan bütün paketler numaralandırılıp yeniden yayımlandı. Yani zincirin her halkası, tek tek bakıldığında, kurallara uygun.

İyi haber: kimlik katmanı gerçekten sertleşti

Bu, tedarik zinciri savunmasının işe yaramadığı anlamına gelmiyor, ve bunu yazmak önemli. npm’in trusted publishing özelliği yayımlamayı uzun ömürlü token’lardan çıkarıp OIDC ile kurulan kısa ömürlü ve iş akışına özgü kimliklere bağlıyor; dokümanın kendi ifadesiyle bu token’lar dışarı çıkarılamıyor ve yeniden kullanılamıyor. Desteklenen üç sağlayıcı var, GitHub Actions, GitLab CI/CD ve CircleCI, ve kendi barındırdığınız koşucular şimdilik listede yok.

Bu yönün tarihi de belli. GitHub, 2025 sonbaharındaki saldırı dalgasının ardından npm yayımlama güvenliği için bir plan yayımlamıştı: klasik token’ların kaldırılması, granular token’larda yedi günlük ömür ve yayımlama izni taşıyanlarda bundan da kısası, zaman tabanlı tek kullanımlık kodlar yerine FIDO tabanlı iki adımlı doğrulama, ve varsayılan olarak token’la yayımlamanın kapatılması.

Bunların hepsi gerçek iyileşme, ve hepsi aynı sınıfı kapatıyor: çalınmış uzun ömürlü bir anahtarla yayımlama. 4 Ağustos’ta olan şey o sınıfta değildi. Saldırı token’ı değil hesabı ve depoyu hedefledi, yani savunmanın bir katman üstüne çıktı. Başarılı bir güvenlik önlemi çoğu zaman böyle görünür: kapattığı kapı gerçekten kapanır, saldırgan bir sonraki kapıya geçer, ve dışarıdan bakan biri hiçbir şeyin değişmediğini sanır.

Elde kalan iki ucuz kaldıraç

Olay raporunun azaltım listesindeki ilk iki madde, ilginç biçimde, kriptografiyle hiç ilgili değil.

  • Kurulum betiklerini kapatmak. --ignore-scripts ile kurulan bir ağaçta preinstall aşamasındaki damlatıcı hiç çalışmıyor, yani zincirin ilk halkası bir yapılandırma satırıyla kopuyor.
  • Sürüm yaşı. Yeni yayımlanmış bir sürüm için birkaç günlük bir bekleme, bu saldırının bütün zehirli sürümlerini otomatik benimsemenin dışında bırakırdı, çünkü hepsi aynı dört saat içinde yayımlandı.

İkincisi artık bir paket yöneticisi ayarı. pnpm’de minimumReleaseAge dakika cinsinden bir bekleme süresi tanımlıyor ve geçişli olanlar dahil bütün bağımlılıklara uygulanıyor; v10.16.0’da eklendi ve v11’den itibaren varsayılanı 1440, yani bir gün. Bir paket yöneticisinin varsayılan olarak bir gün beklemeye başlaması, bu tartışmanın en somut sonucu: güvenlik bir eklenti olmaktan çıkıp kurulumun varsayılan davranışı hâline geliyor. minimumReleaseAgeExclude de aynı kararın kaçınılmaz ikinci yarısı, çünkü acil bir yamanın beklemesi gerekmediğini söyleyebilmek gerekiyor.

Bu bir tekil olay değil, bir dizinin devamı

Saldırının ilk yayımlanan paketleri, sürüm sayılarından çok daha fazlasını anlatan bir yerde duruyor: olay raporu üç paketin haftalık indirme sayısını 153,7 milyon, 149,9 milyon ve 147,6 milyon olarak veriyor. Bunlar uygulamaların doğrudan kurduğu paketler değil, başka paketlerin bağımlılıkları; yani bulaşma yüzeyi kimsenin bilerek seçmediği bir katmandan geçiyor. Bir bağımlılık ağacında en çok yer kaplayan şey, çoğu zaman hiç kimsenin adını bilmediği şeydir.

İkinci analiz olayı 2025 Eylül’ünde başlayan bir kampanya dizisinin devamı olarak tanımlıyor, yani aynı hedefe farklı bir vektörle yapılan tekrarlı bir deneme. Okunma biçimi önemli: tek seferlik bir kaza değil, savunmanın her yeni katmanına göre yeniden ayarlanan bir yöntem. Her turda kapanan kapı gerçekten kapanıyor ve saldırı bir üst katmana çıkıyor; token’dan hesaba, hesaptan iş akışına, iş akışından geliştiricinin makinesindeki yapılandırma dosyalarına.

Bu yüzden “hangi araç bizi korur” yanlış soru; doğrusu “bir sonraki tur hangi katmana çıkar”. Bugünkü cevapların üçü de aynı yöne bakıyor: yayımlama kimliğini kısa ömürlü hâle getirmek, kurulumun kod çalıştırmasını varsayılan olarak kapatmak, ve yeni yayımlanmış bir sürümü hemen benimsememek. Üçü de saldırganın belirli bir tekniğini değil, saldırının işe yaraması için gereken iki şeyi hedefliyor: zaman ve otomatiklik.

Bu depoda karşılığı

Bu depo pnpm 9.15.0’a sabit, yani minimumReleaseAge burada bugün yok. Bunu yazmak ölçmenin bir parçası: bir savunmadan söz edip onu kurmamış olmak, en kötü kombinasyon. Var olan şey kapının sekizinci adımı, pnpm audit --prod --audit-level=high. Ve o adımın ne olduğu deponun kendi notlarında zaten yazılı, bir kanıt değil bir taban: yayımlanmış danışmalardan haberi var, yayımlanmamış olandan yok. 4 Ağustos’ta zehirli sürümler dakikalar yaşındaydı, yani o gün bu adım rahatça yeşil geçerdi.

İkinci bir gözlem daha var, ve rahatsız edici olduğu için yazmaya değer: yükün hedefleri arasında editör ve asistan yapılandırma dosyaları da vardı, kalıcılık kancası bir kaynak dosyasına değil oraya konuyordu. Bu dosyaları ayar sayıp kod incelemesinin dışında bırakmak artık savunulabilir bir ayrım değil, çünkü bir görev tanımı da çalıştırılabilir bir şeydir.

Pratik karşılığı üç maddede toplanıyor. Birincisi, kurulum betiklerini varsayılan olarak kapatmak ve gerçekten gereken paketleri tek tek açmak. İkincisi, sürüm yaşını bir ayara bağlamak; bedeli birkaç saatlik gecikme, karşılığı otomatik benimsemenin dışında kalmak. Üçüncüsü, imzayı yanlış soruya cevap olarak kullanmamak: geçerli bir provenance bu paket bu depodan çıktı der, bu depoya doğru şey girdi demez, ve ikinci soruyu cevaplayan şey hâlâ insan incelemesi.

Etiketler

  • Security
  • Node.js