Dependency Scanning Derinlemesine: NuGetAudit, npm audit, Dependabot ve Bulguları Önceliklendirmek
Dependency bulgularını CVSS skoruna göre sıralamak yetmez: aciliyeti KEV'deki sömürü kanıtı, EPSS'in 30 günlük sömürülme olasılığı ve kodun zafiyetli fonksiyona gerçekten ulaşıp ulaşmadığı belirler. Bookstore'da NuGetAudit'i repo seviyesinde ayarlıyor, .NET 10 package pruning'in false positive'i nasıl kaldırdığını, süreli NuGetAuditSuppress'i, EPSS/KEV sorgulayan bir triage aracını ve Dependabot'ta security ile version update'leri ayırmayı gerçek çıktılarla gösteriyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10 (SDK 10.0.402) ile derlenip denenmiştir; web tarafı Angular 22.2, Node.js 24 ve npm 11.19 ile, EPSS (bir zafiyetin istismar edilme olasılığını tahmin eden skor) ve KEV (CISA'nın aktif olarak istismar edilen zafiyetler listesi) verileri 13 Kasım 2026 tarihli kayıtlarla sorgulanmıştır.
Önce Tahmin Et
Bookstore'un main branch'inde dokuz farklı CVE'ye (yayımlanmış güvenlik açıklarının standart kimlik numarası) karşılık gelen dependency bulgusu var: beşi .NET tarafında (biri iki ayrı pakette birden görünüyor), dördü web tarafındaki tek bir npm paketinde. Hepsini aynı gün düzeltecek vaktin yok. CVSS (zafiyetin ciddiyetini 0-10 arası puanlayan sistem) skoru en yüksek olan 8.8'lik bulguyla mı başlarsın, yoksa son 30 günde sömürülme olasılığı en yüksek görünen bulguyla mı? Bir de şu: kodun o paketin zafiyetli fonksiyonunu hiç çağırmıyorsa sıralama değişir mi?
SRI (Subresource Integrity) ile birlikte işlediğimiz OWASP (web uygulama güvenliği üzerine çalışan açık topluluk) serisinin "OWASP A03: Software Supply Chain Failures — NuGetAudit, npm audit ve SRI" yazısında NuGetAudit'in nasıl çalıştığını, .NET 10'da transitive paketlerin varsayılan olarak denetlendiğini, dotnet package list --vulnerable'ı ve npm audit'in temelini gördük. Bu yazı onların üstüne kuruluyor: denetimi repo seviyesinde tutarlı hale getirmek, false positive'i azaltmak, süreli istisna yazmak ve en önemlisi bulguları CVSS'in ötesinde önceliklendirmek.
Repo Seviyesinde NuGetAudit
Bookstore'a, eski bir .NET Framework istemcisiyle de paylaşılan netstandard2.0 hedefli bir Bookstore.Contracts kütüphanesi ekledik. İçinde configuration okumak için Microsoft.Extensions.Configuration.Json 8.0.0 var. Restore temiz geçti. Sorun şu ki temiz geçmesi gerekmiyordu: .NET 10'daki all varsayılanı yalnızca net10.0 ve üstünü hedefleyen projelere uygulanıyor, netstandard2.0 hâlâ direct modda ve transitive paketlere bakmıyor. Bu yüzden ayarları proje proje değil, repo kökündeki Directory.Build.props'ta tutuyoruz:
<Project>
<PropertyGroup>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAuditLevel>low</NuGetAuditLevel>
<NuGetAuditCodes>NU1903;NU1904</NuGetAuditCodes>
<WarningsAsErrors Condition="'$(CI)' == 'true'">$(WarningsAsErrors);$(NuGetAuditCodes)</WarningsAsErrors>
<Today>$([System.DateTime]::UtcNow.ToString('yyyyMMdd'))</Today>
</PropertyGroup>
</Project>
NuGetAuditLevel'ı low'da bırakıyoruz çünkü görünürlük ucuz; kırma eşiğini ayrı tutuyoruz. GitHub Actions runner'ları CI=true environment variable'ını set ettiği için high (NU1903) ve critical (NU1904) yalnızca pipeline'da hataya dönüyor, geliştirici makinesinde uyarı olarak kalıyor. Bu ayrım önemli: yeni yayımlanan bir advisory, acil bir bug fix yapan geliştiricinin yerel build'ini kırmamalı. Bu yaklaşımı NuGet dokümantasyonu da "ayrı audit pipeline'ı" örneğiyle öneriyor. Restore şimdi beklenen uyarıları veriyor (yollar kısaltıldı):
src/Bookstore.Contracts/Bookstore.Contracts.csproj : warning NU1903: Package 'System.Text.Json' 8.0.0 has a known high severity vulnerability, https://github.com/advisories/GHSA-8g4q-xg66-9fp4
src/Bookstore.Contracts/Bookstore.Contracts.csproj : warning NU1903: Package 'System.Text.Json' 8.0.0 has a known high severity vulnerability, https://github.com/advisories/GHSA-hh2w-p6rv-4g7w
Aynı restore'u CI=true ile çalıştırınca satırlar error NU1903: Warning As Error olarak geliyor ve exit code 1 dönüyor. Kurumsal ağlarda paketler çoğu zaman nuget.org yerine iç bir feed'den gelir; o feed vulnerability verisi sunmuyorsa audit sessizce boş döner. NuGet 6.12'den beri nuget.config'teki auditSources ile vulnerability verisini ayrıca nuget.org'dan alabilirsin; dokümantasyon yalnız vulnerability verisi sunan https://data.nuget.org/v3/index.json adresini de bu senaryo için veriyor. Bir audit kaynağı veri sunmuyorsa NU1905 uyarısı gelir; onu da görmezden gelme.
Package Pruning: False Positive'in .NET 10 Çözümü
Aynı Bookstore.Contracts'ı referans eden Bookstore.Api (net10.0) ise bu uyarıları hiç vermiyor:
dotnet nuget why src/Bookstore.Contracts System.Text.Json
dotnet nuget why src/Bookstore.Api System.Text.Json
Project 'Bookstore.Contracts' has the following dependency graph(s) for 'System.Text.Json':
[netstandard2.0]
└── Microsoft.Extensions.Configuration.Json (v8.0.0)
└── System.Text.Json (v8.0.0)
Project 'Bookstore.Api' does not have a dependency on 'System.Text.Json'.
Sebep .NET 10 SDK'nın package pruning'i. System.Text.Json net10.0'da zaten shared framework'ün parçası; framework'ün sunduğu bir paketin daha eski bir sürümü graph'a girdiğinde runtime'da kullanılmayacağı için restore onu graph'tan çıkarıyor. Önceden bu durum klasik bir false positive'ti: uyarı paket graph'ındaki 8.0.0 için geliyordu, uygulama ise framework'ün yamalı sürümüyle çalışıyordu. Aynı denemeyi ayrı bir projede net8.0 hedefiyle yaptığımda iki NU1903 uyarısı geri geldi; pruning bu SDK'da net10.0 hedeflerinde varsayılan olarak devrede. Yan etkisi de var: net10.0 projesinde framework'ün zaten sunduğu bir paketi doğrudan referans edersen NU1510 uyarısı alırsın, çünkü o referans artık gereksiz.
Bookstore.Contracts ise netstandard2.0 olduğu için gerçekten 8.0.0'ı taşıyor ve onu .NET Framework'te kullanan istemci için risk gerçek. Düzeltirken NuGet dokümantasyonunun önerdiği sırayı izliyoruz: önce doğrudan referansa en yakın paketi güncelle. Microsoft.Extensions.Configuration.Json 8.0.1, System.Text.Json 8.0.5'e bağımlı:
dotnet add src/Bookstore.Contracts package Microsoft.Extensions.Configuration.Json --version 8.0.1
dotnet nuget why src/Bookstore.Contracts System.Text.Json
[netstandard2.0]
└── Microsoft.Extensions.Configuration.Json (v8.0.1)
└── System.Text.Json (v8.0.5)
Transitive paketi doğrudan referansla sabitlemek OWASP serisinde gördüğümüz son çare; burada ona gerek kalmadı.
Süreli ve Gerekçeli İstisna
Her bulgunun hemen bir fix'i olmaz. Fix olmadığında ya da yükseltme bu sprint'e sığmadığında NuGetAuditSuppress kullanılır; ama bu item'ın kendi başına ne gerekçesi ne de bitiş tarihi var. Bir kez eklenen suppression, kimse hatırlamadığı için yıllarca kalır. MSBuild'in property function'larıyla istisnaya bir son kullanma tarihi koyabiliyoruz:
<ItemGroup>
<!-- GHSA-hh2w-p6rv-4g7w: Bookstore.Contracts DeserializeAsyncEnumerable çağırmıyor.
Sahibi: Ada. Kayıt: SEC-151. Bitiş: 2026-12-15. -->
<NuGetAuditSuppress Include="https://github.com/advisories/GHSA-hh2w-p6rv-4g7w"
Condition="$(Today) < 20261215" />
</ItemGroup>
$(Today) yukarıda yyyyMMdd biçiminde bir sayı olarak üretiliyor, bu yüzden MSBuild iki sayıyı karşılaştırabiliyor. Bugün (14 Kasım) restore yalnız GHSA-8g4q-xg66-9fp4'ü raporladı; tarihi 20261101 yapınca, yani istisnanın süresi dolmuş gibi davranınca, ikinci advisory de geri geldi. Süresi dolan istisna sessizce kalmıyor, build'i yeniden kırmaya başlıyor ve sahibine "karar ver" diyor. Gerekçe yorumu da önemli: CVE-2024-30105, JsonSerializer.DeserializeAsyncEnumerable güvenilmeyen girdiyle çağrıldığında ortaya çıkan bir DoS (Denial of Service); kodun o method'u çağırmıyorsa risk düşer ama sıfırlanmaz. Bu örnekte doğru hamle yukarıdaki gibi paketi yükseltmekti; istisna mekanizmasını fix'i olmayan durumlar için göstermek adına aynı advisory'yi kullandım. Bütün istisnaları ve sürelerini tek yerde yönetmeyi serinin son yazısında kuracağız.
Bulguları Önceliklendirmek: CVSS Yetmez
Şimdi açılıştaki soruya dönelim. Bookstore.Api, yıllar önce eklenmiş Microsoft.Data.SqlClient 5.1.1'i taşıyor:
dotnet package list --project src/Bookstore.Api --vulnerable --include-transitive
Top-level Package Requested Resolved Severity Advisory URL
> Microsoft.Data.SqlClient 5.1.1 5.1.1 High https://github.com/advisories/GHSA-98g6-xh36-x2p7
Transitive Package Resolved Severity Advisory URL
> Azure.Identity 1.7.0 Moderate https://github.com/advisories/GHSA-m5vv-6r4h-3vj9
Moderate https://github.com/advisories/GHSA-wvxc-855f-jvrv
High https://github.com/advisories/GHSA-5mfx-4wcx-rv27
> Microsoft.IdentityModel.JsonWebTokens 6.24.0 Moderate https://github.com/advisories/GHSA-59j7-ghrg-fj52
> System.IdentityModel.Tokens.Jwt 6.24.0 Moderate https://github.com/advisories/GHSA-59j7-ghrg-fj52
Web tarafında ise lodash-es 4.17.20 var ve npm audit tek pakette dört advisory gösteriyor:
lodash-es <=4.17.23
Severity: high
Command Injection in lodash - https://github.com/advisories/GHSA-35jh-r3h4-6jhm
Regular Expression Denial of Service (ReDoS) in lodash - https://github.com/advisories/GHSA-29mw-wpgm-hmr9
lodash vulnerable to Code Injection via `_.template` imports key names - https://github.com/advisories/GHSA-r5fr-rjxr-66jc
Lodash has Prototype Pollution Vulnerability in `_.unset`, `_.omit`, and `_.pullAt` functions - https://github.com/advisories/GHSA-xxjr-mmjv-4gpg
fix available via `npm audit fix --force`
Will install lodash-es@4.18.1, which is outside the stated dependency range
node_modules/lodash-es
1 high severity vulnerability
CVSS bir zafiyetin teknik ağırlığını anlatır: ağdan mı sömürülür, yetki ister mi, etkisi ne. Ama "bu hafta bize karşı kullanılır mı" sorusuna cevap vermez. 2023'te yayımlanan CVSS v4.0 bu ayrımı adlandırmaya kadar götürdü: yalnız base metric'lerden gelen skor CVSS-B diye anılıyor, threat ve environmental metric'ler eklendikçe CVSS-BT, CVSS-BE, CVSS-BTE oluyor. Advisory'lerde gördüğün sayı neredeyse her zaman CVSS-B'dir. Önceliklendirmeye üç sinyal daha ekliyoruz.
EPSS, FIRST'ün (Forum of Incident Response and Security Teams) her gün her CVE için yayımladığı, o CVE'nin önümüzdeki 30 günde sahada sömürülme olasılığının tahmini. KEV, CISA'nın (Cybersecurity and Infrastructure Security Agency) sahada sömürüldüğü bilinen CVE'ler kataloğu; 13 Kasım 2026 sürümünde 1781 kayıt vardı. Biri olasılık, öbürü kanıt. Üçüncü sinyal reachability: senin kodun zafiyetli fonksiyona gerçekten ulaşıyor mu? Bunu dışarıdan hiçbir veritabanı söyleyemez, kendi kodundan çıkarırsın.
İlk iki sinyali sorgulayan küçük bir .NET 10 file-based app yazdım; iki isteği paralel atıyor, file-based app'ler varsayılan olarak Native AOT (uygulama çalışmadan önce makine koduna derleme) ile yayımlanmaya göre ayarlandığı için JSON'ı (JavaScript Object Notation) source generator ile okuyor:
using System.Globalization;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
string[] cves = args;
using HttpClient http = new();
http.DefaultRequestHeaders.UserAgent.ParseAdd("bookstore-triage/1.0");
Task<EpssResponse?> epssTask = http.GetFromJsonAsync(
$"https://api.first.org/data/v1/epss?cve={string.Join(',', cves)}", TriageJson.Default.EpssResponse);
Task<KevCatalog?> kevTask = http.GetFromJsonAsync(
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json", TriageJson.Default.KevCatalog);
await Task.WhenAll(epssTask, kevTask);
EpssResponse epss = await epssTask ?? throw new InvalidOperationException("EPSS response was empty");
KevCatalog kev = await kevTask ?? throw new InvalidOperationException("KEV catalog was empty");
HashSet<string> exploited = kev.Vulnerabilities.Select(v => v.CveId).ToHashSet();
Console.WriteLine($"KEV catalog {kev.CatalogVersion}, EPSS date {epss.Data.FirstOrDefault()?.Date}");
foreach (EpssScore score in epss.Data.OrderByDescending(s => s.Probability))
{
string inKev = exploited.Contains(score.Cve) ? "YES" : "no";
Console.WriteLine($"{score.Cve,-16} EPSS {score.Probability,7:P2} percentile {score.PercentileValue:F3} KEV {inKev}");
}
record EpssResponse(List<EpssScore> Data);
record EpssScore(string Cve, string Epss, string Percentile, string Date)
{
public double Probability => double.Parse(Epss, CultureInfo.InvariantCulture);
public double PercentileValue => double.Parse(Percentile, CultureInfo.InvariantCulture);
}
record KevCatalog(string CatalogVersion, List<KevEntry> Vulnerabilities);
record KevEntry([property: JsonPropertyName("cveID")] string CveId);
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(EpssResponse))]
[JsonSerializable(typeof(KevCatalog))]
partial class TriageJson : JsonSerializerContext;
dotnet run triage.cs -- CVE-2024-0056 CVE-2023-36414 CVE-2024-35255 CVE-2024-29992 CVE-2024-21319 CVE-2021-23337 CVE-2020-28500 CVE-2026-4800 CVE-2025-13465
KEV catalog 2026.11.13, EPSS date 2026-11-13
CVE-2021-23337 EPSS 20.16 % percentile 0.975 KEV no
CVE-2020-28500 EPSS 7.42 % percentile 0.945 KEV no
CVE-2024-21319 EPSS 2.89 % percentile 0.871 KEV no
CVE-2026-4800 EPSS 2.81 % percentile 0.867 KEV no
CVE-2023-36414 EPSS 2.38 % percentile 0.841 KEV no
CVE-2025-13465 EPSS 1.82 % percentile 0.791 KEV no
CVE-2024-0056 EPSS 1.18 % percentile 0.682 KEV no
CVE-2024-35255 EPSS 0.83 % percentile 0.581 KEV no
CVE-2024-29992 EPSS 0.72 % percentile 0.544 KEV no
Sıralama CVSS'inkinden çok farklı. CVSS'e göre en ağır bulgu 8.8 ile Azure.Identity'deki CVE-2023-36414; EPSS listesinde beşinci. EPSS'te açık ara birinci olan lodash'in template fonksiyonundaki command injection (CVE-2021-23337) ise CVSS'te 7.2. Hiçbiri KEV'de değil. Şimdi reachability'ye bakalım.
Reachability: Kod Gerçekten Çağırıyor mu?
Web tarafında soru basit: lodash-es'ten ne import ediyoruz?
grep -rn "lodash-es" src --include='*.ts'
src/app/books/book-search.ts:2:import { debounce } from 'lodash-es';
Yalnızca arama kutusundaki debounce. İki injection advisory'si _.template'e, prototype pollution _.unset, _.omit ve _.pullAt'e ait; Bookstore bunları çağırmıyor. ReDoS advisory'si ise toNumber, trim ve trimEnd'i kapsıyor ve debounce bekleme süresini içeride toNumber'dan geçiriyor; ama o değer bizim kodumuzda sabit 300, kullanıcı girdisi değil. Sonuç: zafiyetli kod paket içinde duruyor ama bizim çağrı yollarımızdan ulaşılmıyor. Bundler'ın tree shaking ile lodash-es'ten yalnız kullanılan fonksiyonları bundle'a koyması da bunu destekliyor, ama reachability kararını bundle analizine değil kodun kendisine dayandır.
.NET tarafında dotnet nuget why yolu gösteriyor: Azure.Identity'yi SqlClient 5.1.1 getiriyor. SqlClient bu paketi Microsoft Entra ID authentication modları için kullanıyor; Bookstore'un connection string'i SQL login kullandığı için o kod yolu çalışmıyor. SqlClient'ın kendi advisory'si (CVE-2024-0056, Microsoft'un sınıflandırmasıyla bir security feature bypass) ise doğrudan bizim bağlantı yolumuzda: her istek SqlClient üzerinden veritabanına gidiyor.
Bu üç sinyali Bookstore'un politikasında bir matrise koyuyoruz. Eşikler bizim kararımız, bir standart değil; senin ekibin kendi eşiğini seçer:

Matrisin okunuşu şöyle: KEV'de olan ya da EPSS'i yüzde 10'u geçen ve kodun ulaştığı bir zafiyet 72 saat içinde kapanır; Bookstore'da bugün böyle bir bulgu yok. Kodun ulaştığı ama sinyali düşük olan SqlClient bulgusu bu sprint'e girer. EPSS'i yüksek ama ulaşılmayan lodash bulgusu 30 gün içinde planlı güncellemeyle kapanır; ulaşılabilirlik bir gün değişebilir, bu yüzden "hiç" değil "planlı". İkisi de düşük olan Azure.Identity bulguları toplu güncellemeye girer; fix'i olmayan böyle bir bulgu için "etkilenmiyoruz, çünkü..." beyanını serinin yedinci yazısında VEX (bir zafiyetin üründe gerçekten istismar edilebilir olup olmadığını bildiren doküman) ile makine okunur hale getireceğiz.
Düzeltmek: En Yakın Paketten Başla
.NET 10 SDK'sı dotnet package update --vulnerable komutunu getirdi. Ne yaptığına bakalım:
dotnet package update --vulnerable --project src/Bookstore.Api
Updating packages with security advisories in .../src/Bookstore.Api
Bookstore.Api:
Updating Azure.Identity 1.7.0 to 1.11.4.
Updating Microsoft.Data.SqlClient 5.1.1 to 5.1.3.
Updating Microsoft.IdentityModel.JsonWebTokens 6.24.0 to 6.34.0.
Updating System.IdentityModel.Tokens.Jwt 6.24.0 to 6.34.0.
Updated 4 packages in 23 scanned packages.
Uyarılar sıfıra indi, ama .csproj'a bak: komut her paketi zafiyetsiz ilk sürüme çekti ve üç transitive paketi doğrudan PackageReference olarak ekledi. Artık onların sahibi sensin; SqlClient bir gün başka sürümler isterse bu sabitlemeler çatışma ya da gereksiz referans olarak geri döner. Hızlı bir yama için işe yarar, kalıcı çözüm değil. Doğrudan referansa en yakın paketi, yani SqlClient'ı güncel sürüme taşımak daha temiz:
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.1.2" />
</ItemGroup>
Project 'Bookstore.Api' does not have a dependency on 'Azure.Identity'.
SqlClient 7, Azure.Identity'ye bağımlı değil; transitive bulgular paketle birlikte graph'tan çıktı. Major sürüm yükseltmesi olduğu için testlerin geçmesi şart; yükseltmenin kendi riski bu yüzden ayrı bir PR'da ele alınır. Web tarafında lodash-es'i 4.18.1'e çekince npm audit "found 0 vulnerabilities" dedi. Pipeline'da npm audit --audit-level=high --omit=dev kullanıyoruz: yalnız production bağımlılıklarındaki high ve critical bulgular kırıyor, düzeltmeden önceki denemede exit code 1 döndü.
Dependabot: Security ve Version Update'leri Ayırmak
Bulguyu bulmak işin yarısı; güncellemeyi düzenli getiren bir bot diğer yarısı. Dependabot'ta iki ayrı akış var: security update'ler bir advisory'ye karşılık PR açar, version update'ler ise takvime göre güncel sürümleri getirir. İkisini aynı gruba koymak, acil bir yamayı on beş sıradan güncellemenin arasında kaybetmek demek:
version: 2
updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: weekly
groups:
nuget-security:
applies-to: security-updates
patterns: ["*"]
nuget-minor-patch:
applies-to: version-updates
update-types: [minor, patch]
cooldown:
default-days: 3
- package-ecosystem: npm
directory: "/web"
schedule:
interval: weekly
groups:
npm-security:
applies-to: security-updates
patterns: ["*"]
angular:
applies-to: version-updates
patterns: ["@angular/*", "@angular-devkit/*"]
cooldown:
default-days: 7
semver-major-days: 30
Dosyayı SchemaStore'daki Dependabot şemasına karşı doğruladım; applies-to'ya geçersiz bir değer yazınca doğrulama "'security' is not one of ['version-updates', 'security-updates']" hatasıyla durdu. Dependabot'u bir repository'de çalıştırmadım. cooldown, Frontend Güvenliği serisinin "npm Supply Chain'i Sertleştirmek: Lifecycle Script'leri, Release Cooldown, Provenance ve Trusted Publishing" yazısında anlattığımız bekleme süresi; Dependabot dokümantasyonuna göre yalnız version update'lere uygulanıyor, security update'ler beklemiyor. İki kural birbiriyle çelişmiyor: yeni sürümü birkaç gün beklet, ama yamayı bekletme.
Auto-merge'e gelince sınırları net tut. Dev bağımlılıklarındaki patch sürümleri, testler geçtikten sonra otomatik merge edilebilir. Production bağımlılıklarında ve major sürümlerde bir insan review'u şart; security update de bir istisna değil, çünkü yamayı getiren sürüm başka bir şeyi bozabilir. Auto-merge'ü açan workflow'un kendisi de bir yetki taşır, kurarken GitHub Actions serisinin güvenlik yazısındaki kuralları uygula.
Lisanslara Bir Bakış
Bağımlılık taraması yalnız zafiyet değil, lisans da bulur. GPL (türev yazılımın da açık kaynak olmasını şart koşan lisans) gibi copyleft bir lisansla gelen bir paket, dağıttığın yazılımın lisans yükümlülüklerini değiştirebilir; bir web servisi ile müşteriye dağıtılan bir masaüstü uygulaması arasında bu etki farklıdır. Pratik yaklaşım, izin verilen lisansları (MIT, Apache-2.0 ve BSD (kısıtlaması az bir açık kaynak lisansı) gibi) bir liste olarak tanımlamak ve listenin dışında kalan her yeni lisansı review'a düşürmek. Paketlerin lisans bilgisini en temiz biçimde SBOM'dan (yazılımın içerdiği bileşenlerin listesi) okursun; bunu serinin yedinci yazısında üreteceğiz. Bu bir hukuki tavsiye değil: hangi lisansın senin dağıtım modelinde ne anlama geldiğine hukuk ekibin karar verir.
Üretimde Dikkat
Advisory veritabanı her gün değişir, bu yüzden main'de dün yeşil olan build bugün kırmızı olabilir. Bunu bir hata değil, sistemin çalıştığının kanıtı say; ama release'i bloklamaması için audit'i ayrı ve zamanlanmış bir job olarak da koştur, bulgusunu backlog'a ve matrisin SLA'sına (sağlayıcının taahhüt ettiği hizmet seviyesi sözleşmesi) bağla. PR'da kıran kural yalnızca o PR'ın eklediği ya da yükselttiği bağımlılıklar için geçerli olmalı; birinci yazıdaki "yeni bulguyu engelle" ilkesi burada da aynı.
Veri kaynaklarını bil. nuget.org'un vulnerability verisi GitHub Advisory Database'den geliyor; npm audit de aynı veritabanını kullanıyor. OSV (açık kaynak zafiyet veritabanı) birçok kaynağı ekosistem ve paket adlarıyla birleştiriyor, NVD (ABD'nin ulusal zafiyet veritabanı) ise CVE'lere CVSS ve ürün mapping'i ekliyor ama paket ekosistemlerinin adlarıyla her zaman birebir eşleşmiyor. İki araç aynı paket için farklı sonuç verirse ilk bakacağın yer kullandıkları veri kaynağıdır.
EPSS bir olasılıktır ve her gün değişir. Triage aracını haftalık çalıştır, bir bulgunun kadranı değiştiğinde (örneğin KEV'e girdiğinde) SLA'sı da değişsin.
Sık Düşülen Tuzaklar
Birincisi, net10.0 dışındaki projeleri unutmak. netstandard2.0 ve net8.0 projeleri hâlâ direct modda; NuGetAuditMode'u Directory.Build.props'ta açıkça all yap.
İkincisi, önceliği yalnız CVSS ile vermek. CVSS-B skoru bağlam içermez; KEV ve EPSS olmadan acil olanla ağır görüneni ayıramazsın.
Üçüncüsü, "reachable değil"i "düzeltmeye gerek yok" diye okumak. Reachability aciliyeti düşürür; kod değişir, yarın biri _.template'i çağırabilir.
Dördüncüsü, dotnet package update --vulnerable'ın eklediği transitive sabitlemeleri kalıcı bırakmak. Üst paket güncellendiğinde bu referansları temizle.
Beşincisi, süresiz suppression. Gerekçesi, sahibi ve bitiş tarihi olmayan istisna, kapatılmış bir alarmdır.
Küçük Bir Deneme
★☆☆ Kendi çözümünde netstandard2.0 ya da net8.0 hedefli bir proje bul, -p:NuGetAuditMode=all ile restore et ve varsayılan moda göre kaç yeni uyarı çıktığını say.
★★☆ Projendeki dependency bulgularının CVE listesini çıkar, yukarıdaki triage aracıyla EPSS ve KEV durumunu sorgula ve her bulguyu matriste bir kadrana yerleştir. CVSS sıralamasıyla farkı not et.
★★★ Bir NuGetAuditSuppress için bitiş tarihi koşulu yaz, sonra tarihi geçmişe çekip pipeline ayarıyla (CI=true) restore'un gerçekten kırıldığını doğrula. Ardından süresi dolmak üzere olan istisnaları listeleyen küçük bir script yaz.
Bağımlılıkları bulduk ve önceliklendirdik; sıra kendi yazdığımız koda geldi. Bir sonraki yazıda SAST'ı (kaynak kodu çalıştırmadan tarayan güvenlik testi) CodeQL, Semgrep ve .NET security analyzer'larıyla Bookstore'a kuracak, source'tan sink'e taint analizinin neyi bulup neyi bulamadığını göreceğiz.