Se hai mai eseguito dotnet list package --vulnerable su un progetto .NET, probabilmente hai visto avvisi per pacchetti transitivi che non hai mai installato esplicitamente — come System.Text.Json o System.Formats.Asn1. In molti casi, questi pacchetti sono già forniti in una versione più recente dal runtime .NET, e gli avvisi sono falsi positivi. Con .NET 10, NuGet risolve questo problema in modo elegante: il package pruning.
Il problema: dipendenze transitorie fantasma
Molte librerie su NuGet.org hanno come target netstandard2.0 per massima compatibilità, e portano con sé dipendenze da pacchetti come System.Memory, System.Text.Json o System.IO.Pipelines — pacchetti che nel frattempo sono diventati parte delle .NET Runtime Libraries.
Il risultato pratico: un progetto .NET 10 che dipende da una di queste librerie si trova a risolvere durante il restore System.Text.Json 8.0.0 come dipendenza transitiva, anche se il runtime .NET 10 già include una versione più recente. Quando viene pubblicata una CVE contro quella versione, i vulnerability scanner la segnalano — anche se la tua applicazione non la usa affatto.
Questo genera tre problemi concreti:
- Falsi positivi nelle vulnerability scan: avvisi per pacchetti già coperti dal runtime
- Grafi di restore più grandi: più pacchetti da risolvere e scaricare
- Riferimenti obsoleti: entry vecchie nel dependency graph che non rispecchiano cosa usa davvero l’app
Come funziona il Package Pruning
Il package pruning rimuove dal grafo di dipendenze NuGet i pacchetti già forniti dalle .NET Runtime Libraries al momento del restore. Il .NET SDK include una lista dei pacchetti forniti da ciascun target framework, con la versione massima disponibile. Se una dipendenza transitiva rientra in quel range, NuGet la elimina.
Ad esempio, net10.0 include System.Text.Json 10.x: una dipendenza transitiva su System.Text.Json 9.0.0 verrebbe pruned; una su System.Text.Json 11.0.0 no (perché il runtime non copre quella versione).
Prima del pruning
Sample.csproj : warning NU1903: Package 'System.Formats.Asn1' 6.0.0 has a
known high severity vulnerability
Project 'Sample' has the following package references
[net10.0]:
Top-level Package Resolved
> Microsoft.Extensions.AI 10.0.1
> NuGet.Protocol 6.9.1
Transitive Package Resolved
> System.Diagnostics.DiagnosticSource 10.0.0
> System.Formats.Asn1 6.0.0 ← VULNERABILE
> System.Text.Json 10.0.0
> System.Threading.Channels 10.0.0
...
Dopo il pruning
Project 'Sample' has the following package references
[net10.0]:
Top-level Package Resolved
> Microsoft.Extensions.AI 10.0.1
> NuGet.Protocol 6.9.1
Transitive Package Resolved
> Microsoft.Extensions.AI.Abstractions 10.0.1
> Newtonsoft.Json 13.0.3
> NuGet.Common 6.9.1
...
(System.Formats.Asn1, System.Text.Json, System.Threading.Channels rimossi)
System.Formats.Asn1, System.Text.Json e System.Threading.Channels non compaiono più come dipendenze transitorie perché sono ridondanti su net10.0. L’avviso di vulnerabilità scompare.
Le novità in .NET 10
Il package pruning era disponibile come opt-in dal .NET SDK 9.0.200. Con .NET 10 diventa il comportamento predefinito per i progetti che hanno come target net10.0 o versioni successive.
In parallelo, NuGetAuditMode ora vale all per default, estendendo l’audit alle dipendenze transitorie. Pruning e audit lavorano insieme: il pruning rimuove i pacchetti ridondanti, l’audit si concentra sulle dipendenze che la tua app usa davvero.
I risultati misurati dal team NuGet:
- 70% di report di vulnerabilità transitorie in meno rispetto ai default precedenti
- Fino al 50% di riduzione del tempo di restore a livello di singolo progetto
- Tasso di successo del restore misurabilmente più alto
Gestione dei PackageReference diretti
Se hai un riferimento diretto a un pacchetto che rientra nel range del pruning, NuGet non lo rimuove automaticamente dal tuo .csproj, ma lo “privatizza” aggiungendo implicitamente PrivateAssets='all' e IncludeAssets='none'. Quando un riferimento diretto può essere rimosso completamente, NuGet emette il warning NU1510:
<!-- Prima: riferimento diretto ridondante -->
<PackageReference Include="System.Text.Json" Version="10.0.0" />
<!-- NuGet emetterà NU1510: puoi rimuovere questo riferimento -->
Come attivare manualmente (per progetti precedenti)
Per progetti che non usano ancora i default di .NET 10, è possibile attivare pruning e audit esplicitamente:
<PropertyGroup>
<NuGetAuditMode>all</NuGetAuditMode>
<RestoreEnablePackagePruning>true</RestoreEnablePackagePruning>
</PropertyGroup>
In un progetto multi-target, se almeno un target framework è net10.0 o successivo, il pruning si applica a tutti i target framework del progetto.
Impatto su pipeline CI/CD e security scanning
Per i team che usano tool come OWASP Dependency-Check, Snyk, GitHub Dependabot o dotnet-outdated, il package pruning riduce significativamente il rumore nei report. I vulnerability report post-pruning riflettono le dipendenze reali dell’applicazione, rendendo le decisioni di remediation più precise e il triage più veloce.
Il comando per verificare lo stato attuale del tuo progetto:
dotnet list package --include-transitive --vulnerable
Con .NET 10 e pruning attivo, l’output dovrebbe essere significativamente più corto rispetto a .NET 8/9 con gli stessi package di primo livello.
Conclusione
Il package pruning in .NET 10 è uno di quei miglioramenti silenziosi che hanno un impatto concreto quotidiano: meno rumore nei report di sicurezza, restore più veloci, e un dependency graph che rispecchia davvero cosa usa la tua applicazione. Se stai ancora su .NET 9, vale la pena attivarlo subito con le due property nella sezione precedente.
Fonte: NuGet Package Pruning: Cleaner Dependencies and Actionable Vulnerability Reports — .NET Blog