- Seattle Times, Shai-Hulud 2.0 saldırısından tesadüfen kurtuldu; ancak şansın bir güvenlik stratejisi olamayacağı varsayımıyla istemci tarafı savunmaları devreye aldı
- npm’deki Trusted publishing / provenance / ayrıntılı tokenlar gibi iyileştirmeler “yayınlama” tarafını güçlendiriyor, ancak “kurulum·güncelleme” anındaki kötü amaçlı kod çalıştırılmasını engelleyemeyen bir boşluk bırakıyor
- pnpm, npm kayıt defterini aynen kullanırken tüketim (install/update) aşamasında kötü amaçlı paketlerin çalıştırılmasını zorlaştıran denetimler ekleyen bir yapıya sahip
- Pilot uygulamada pnpm’in 3 denetimi uygulanarak lifecycle script çalıştırma, en son sürümün anında kurulması, güven seviyesinin düşürülmesi gibi vektörlerin her birini engelleyecek şekilde tasarlandı
- İstisnalar bir başarısızlık değil, tasarımın bir parçası olarak görülüyor; istisnalar belgelenirken diğer katmanların korumaya devam ettiği bir defense-in-depth işletimi hedefleniyor
Olayın arka planı ve varsayımlar
- 2025 Kasım’ında kendi kendini kopyalayan bir npm solucanı 796 paketi enfekte etti ve aylık 132 milyon indirme ölçeğinde yayıldı
- Saldırı, preinstall scriptini kullanarak kimlik bilgisi hırsızlığına, kalıcı arka kapı kurulumuna ve bazı ortamlarda geliştirme ortamının silinmesine kadar vardı
- Kuruluşumuzun etkilenmemesinin nedeni güçlü savunmalar değil, saldırı süresince
npm install/npm update çalıştırılmamış olması gibi bir tesadüftü
- Haber kuruluşlarında güven esastır; tedarik zinciri ihlali müşteri verilerini, çalışan kimlik bilgilerini, production altyapısını ve kaynak kodunu açığa çıkarabilir, ayrıca kurtarma ve bildirim maliyetleri yüksektir
Ekip ve benimseme bağlamı
- Seattle Times uzun süredir varsayılan paket yöneticisi olarak npm kullanıyordu; Yarn denemeleri oldu ancak kalıcı hale gelmedi
- pnpm’in benimsenme nedeni, kayıt defteri düzeyindeki iyileştirmeleri tamamlayan istemci tarafı güvenlik denetimleri sunması
- pnpm, aynı kayıt defteri·aynı komutlar·aynı iş akışıyla çalışan bir drop-in replacement olduğu için geçiş olasılığı yüksek görüldü
- Bu, tamamlanmış bir vaka çalışması değil; gerçek bir ekibin tedarik zinciri güvenliğine yeni başlarken yaşadığı sorunları ve düşünce sürecini paylaşmasıdır
Neden istemci tarafı denetimler gerekli
- npm’in güvenlik iyileştirmeleriyle hesap ele geçirildikten sonra kötü amaçlı paket yayınlamak daha zor hale geldi
- Bu iyileştirmeler “yayınlama (publishing)” tarafını koruyor, ancak “tüketim (consuming)” aşamasında kötü amaçlı paketin kurulmasını bizzat engellemiyor
npm install/npm update sırasında lifecycle scripts (preinstall/postinstall vb.), paket güvenliği değerlendirilmeden önce geliştirici yetkileriyle rastgele kod çalıştırabiliyor
- Bu scriptler npm/GitHub/AWS/DB kimlik bilgilerine, kaynak koda, bulut altyapısına ve tüm dosya sistemine erişebiliyor
- Shai-Hulud benzeri saldırılar bu yapıyı kötüye kullanıyor; bakım sorumlusu hesabı ele geçirilirse kötü amaçlı script kurulum anında çalışıyor ve topluluk fark etmeden önce zarar oluşabiliyor
- npm’in yayınlama tarafı iyileştirmeleri + pnpm’in tüketim tarafı denetimleri, birbirini tamamlayan savunmalar olarak birleştirilip “defense-in-depth” şeklinde kurgulanıyor
Uygulanan 3 katman
- Pilotta farklı saldırı vektörlerini hedefleyen 3 denetim birlikte kullanıldı
- Her denetim, gerçekçi istisnalar için bir çıkış yolu içeriyor; tasarım baştan itibaren gerçek ortamda istisnaların gerekeceği kabulüyle yapıldı
Denetim 1: Lifecycle Script Yönetimi
- pnpm varsayılan olarak lifecycle scriptlerini engelleyebilir ve kurulum uyarıyla devam edebilir
- Uyarıların göz ardı edilebileceği kaygısıyla
strictDepBuilds: true seçildi; böylece script varsa kurulum anında başarısız olacak şekilde zorlandı
- Yapılandırma örneği
pnpm-workspace.yaml içinde şu alanlardan oluşuyor
strictDepBuilds: true
onlyBuiltDependencies: gerekli build scriptlerine sahip paketler için izin listesi
ignoredBuiltDependencies: gereksiz build scriptlerine sahip paketler için engelleme (veya yok sayma) listesi
- “Gerekli scriptler”, native eklenti derleme veya platforma bağımlı kütüphaneleri bağlama gibi işlemler olarak tanımlandı
- “Gereksiz scriptler” ise optimizasyon ya da isteğe bağlı ayar niteliğinde olup, ekibin kullanım biçiminde işlevi etkilemeyen durumlar olarak tanımlandı
- Kurulumun başarısız olması şu sonraki adımları zorunlu kılıyor
- pnpm, hangi pakette script olduğunu açıkça gösteriyor
- Script davranışı araştırılıp anlaşılıyor
- İzin/verme veya engelleme kararı insan değerlendirmesiyle bilinçli olarak alınıp belgeleniyor
- pnpm ekibi, v11’de
strictDepBuilds: true seçeneğini varsayılan yapmayı değerlendiriyor ve allow/deny söz dizimi adlarını iyileştirmeyi de inceliyor
Denetim 2: Sürüm Bekleme Süresi
- Yakın zamanda yayımlanmış sürümlerin kurulumu belirli bir bekleme süresi boyunca engellenerek, topluluğa kötü amaçlı paketleri tespit edip kaldırması için zaman tanınıyor
- Yapılandırma örneği
pnpm-workspace.yaml içinde şu alanlardan oluşuyor
minimumReleaseAge: <duration-in-minutes>
minimumReleaseAgeExclude: acil hotfix gibi istisnalar için izin listesi
- “En yeni en iyidir” alışkanlığından vazgeçip, tedarik zinciri perspektifinde biraz daha eski olanın daha güvenli olabileceği düşüncesine geçmek gerekiyor
- Eylül 2025 saldırısında (debug, chalk dahil 16 paket) kaldırma yaklaşık 2,5 saat sürdü; Kasım 2025’teki Shai-Hulud 2.0’da ise yaklaşık 12 saat gerekti
- Kuruluşun risk toleransına göre bekleme süresi saat/gün/hafta olabilir; hangi biçimde olursa olsun bu saldırıyı engellemiş olurdu
- Kuruluşun zaten her zaman en son sürümü kullanamaması gerçeğiyle iyi örtüşüyor; bu yüzden bekleme süresi işi ciddi biçimde aksatmıyor
- Güvenlik yaması veya kritik hata gibi gerçekten gerekli durumlarda inceleme sonrası istisna verilebilir
Denetim 3: Güven Politikası
- Önceki sürümden daha zayıf kimlik doğrulamayla yayımlanmış bir sürüm ortaya çıkarsa kurulumu engelliyor
- Bunun, bakım sorumlusu hesabı ele geçirilip resmi CI/CD yerine saldırganın makinesinden yayın yapıldığına dair bir sinyal olduğu açıklanıyor
- Yapılandırma örneği
pnpm-workspace.yaml içinde şu alanlardan oluşuyor
trustPolicy: no-downgrade
trustPolicyExclude: CI/CD geçişi gibi istisnalar için izin listesi
- npm’in paket yayını için 3 aşamalı bir güven seviyesi izlediği anlatılıyor (güçlü→zayıf)
- Trusted Publisher: GitHub Actions + OIDC + npm provenance tabanlı
- Provenance: CI/CD’de imzalı attestation
- No Trust Evidence: kullanıcı adı/şifre veya token tabanlı yayın
- Yeni sürümün güven seviyesi öncekinden düşükse kurulum başarısız oluyor
- Ağustos 2025’teki s1ngularity saldırısında, saldırganın CI/CD erişimi olmadan yerelden kötü amaçlı sürüm yayınladığı ve provenance bulunmadığı olayda bu denetim kurulumu engellemiş olurdu
- Meşru düşüş örnekleri olarak yeni bir bakım sorumlusunun katılması, CI/CD geçişi veya CI/CD arızası sırasında manuel hotfix veriliyor; inceleme sonrası istisna listesine ekleniyor
- Bu özelliğin, 2025 Kasım’ında pnpm’e eklenen yeni bir özellik olduğu ve meşru düşüşlerin pratikte ne kadar sık yaşanacağının hâlâ öğrenildiği belirtiliyor
Katman birleşiminin çalışma örneği: React güvenlik açığı yaması
- 2025 Aralık’ta duyurulan React Server Components içindeki kritik güvenlik açığı yamasının hemen uygulanması gereken senaryoda
- Normalde bekleme süresi “az önce yayımlanmış sürümün kurulmasını” engeller, ancak kritik bir güvenlik yamasında beklemek mümkün değildir
- Bu durumda
minimumReleaseAgeExclude içine belirli React sürümü eklenir; ancak istisna, güvenlik duyurusu ve yamanın meşruiyeti incelendikten sonra uygulanır
- İstisna uygulansa bile diğer katmanlar korumaya devam eder
- React’te genellikle lifecycle script yoktur; bu yüzden yama sürümünde script ortaya çıkarsa hemen şüphe sinyali olur ve engellenebilir
- Saldırgan kimlik bilgilerini ele geçirip yerelden bir “yama” yayımlarsa güven seviyesindeki düşüş nedeniyle engellenebilir
- İstisna, “güvenlik başarısızlığı” olarak değil; bir katman aşılmış olsa da diğer katmanların kaldığı ve tek hata noktasını ortadan kaldıran bir tasarım olarak görülüyor
Pilot uygulama sonuçları
- Tek bir backend servisine 3 denetimin tamamı uygulanarak PoC yapıldı
- Araştırma, anlama ve yaklaşımı tanımlama dahil toplam hazırlık süresi birkaç saat düzeyindeydi
- pnpm, lifecycle script içeren 3 paket tespit etti
- esbuild: CLI başlangıcını milisaniye düzeyinde optimize ediyor, ancak ekip yalnızca JS API kullandığı için gereksiz görüldü
- @firebase/util: istemci SDK’sını otomatik yapılandırıyor, ancak ekip yalnızca sunucu SDK’sını kullandığı için gereksiz görüldü
- protobufjs: şema uyumluluğu kontrolü yapıyor, ancak yalnızca transitif bağımlılık olarak kullanıldığı ve ekibin kullanım senaryosunda gereksiz olduğu değerlendirildi
- Dokümantasyon incelemesi ve script analizi (script yorumlamada yapay zeka desteği dahil) sonrasında, üç scriptin de ekibin kullanım senaryosu için gerekli olmadığı sonucuna varılıp engellendi
- İşlevsel bir etki olmadı
- Sürtünme (friction), ortamda çalışan koda örtük olarak güvenilmemesini sağlayan kasıtlı bir zorlayıcı mekanizma olarak değerlendiriliyor
- Yeni bir bağımlılıkta script varsa, inceleme ve belgelemenin yaklaşık 15 dakika süreceği tahmin ediliyor
Operasyon sırasında edinilen dersler
- İstemci tarafı katmanlar + npm’in yayınlama tarafı iyileştirmeleri birleştiğinde defense-in-depth’in gerçekten işe yaradığı hissedildi
- İstisna uygulansa bile diğer katmanların kalması, istisnalarla ilgili kaygıyı azaltıyor
- Önce kolaylık yaklaşımından önce güvenlik yaklaşımına geçişte zihinsel modelin değişmesi zaman alıyor; ancak alışınca doğal geliyor
- Ayrı bir güvenlik ekibi olmayan orta ölçekli kuruluşlarda da pratik biçimde uygulanabilir
- Trust policy, kullanıma alınalı sadece birkaç hafta olmuş bir özellik; bu nedenle meşru güven düşüşlerinin sıklığı ve operasyonel hissiyat daha fazla öğrenilmeli
- Yakın dönemde başka kod tabanlarına da genişletilmesi planlanıyor; farklı bağımlılık grafiğine sahip uygulamalardan daha fazla veri toplanacak
Diğer ekipler için uygulama ipuçları
- Önce tek bir projede başlayıp iş akışını ve sürtünme noktalarını öğrenmek öneriliyor
- Lifecycle scriptler, sürüm bekleme süresi ve güven düşüşü senaryolarının tümünde istisna gerekebileceğinden, baştan itibaren istisnaları varsayarak tasarlayın
- Uyarı tabanlı yaklaşım yerine, kurulumu başarısızlığa zorlayan
strictDepBuilds: true seçeneğinin ilk günden kullanılması tavsiye ediliyor
- Tüm istisnaları belgeleyerek denetim izi bırakın ve ileride temizlemeyi kolaylaştırın
- Bir katmandaki istisnanın, diğer katmanların korumasını ortadan kaldırmadığını aklınızda tutun
1 yorum
pnpm! pnpm! pnpm! Yine de güvenilir olduğunu düşünüyorum