NuGet Package Pruning no .NET 10: Fim dos falsos positivos no NuGet Audit

O package pruning no .NET 10 é a resposta do NuGet para um incômodo antigo: avisos de vulnerabilidade em pacotes transitivos que você nunca instalou. Se você já rodou o NuGet Audit ou outro scanner em um projeto .NET, provavelmente já viu esses alertas.

Na maioria dos casos, eles são falsos positivos gerados por pacotes como System.Text.Json ou System.Text.Encodings.Web. Afinal, as bibliotecas de runtime do .NET já fornecem versões mais recentes desses mesmos pacotes.

Com o pruning habilitado por padrão, o restore elimina esses pacotes redundantes do grafo de dependências. Neste artigo, você vai ver o que mudou em relação ao .NET 9, como configurar o recurso e quais avisos novos aparecem.

O problema das dependências transitivas

Muitas bibliotecas do nuget.org ainda miram o netstandard2.0 para garantir compatibilidade com diferentes versões do .NET. Essa estratégia, porém, tem um custo para quem consome o pacote.

Essas bibliotecas carregam dependências explícitas em System.Memory, System.Text.Json e System.IO.Pipelines. Contudo, esses pacotes já fazem parte das bibliotecas de runtime do .NET moderno.

Considere um projeto net10.0 que depende de uma biblioteca netstandard2.0. Essa biblioteca pode puxar o System.Text.Json 8.0.0 como dependência transitiva, mesmo que o runtime do .NET 10 já entregue uma versão mais nova.

O pacote antigo continua no nuget.org, o NuGet ainda o resolve no grafo e, quando surge uma CVE contra ele, os scanners disparam alertas. Isso acontece mesmo que o aplicativo não use aquela versão vulnerável.

Desse cenário decorrem três problemas que o package pruning no .NET 10 ataca. Primeiro, os falsos positivos, já que o NuGet Audit reporta uma vulnerabilidade transitiva que o runtime já corrigiu.

Em seguida, vem a inflação do grafo de restore, com mais downloads, mais entradas e mais ruído no build. Por fim, há a permanência de referências obsoletas, que dificultam a leitura e a manutenção das dependências.

O que é o package pruning no .NET 10

O package pruning no .NET 10 é o processo que remove do grafo do NuGet, durante o restore, os pacotes fornecidos pela plataforma. Assim, o grafo resolvido passa a refletir apenas o que o aplicativo realmente usa.

O .NET SDK inclui uma lista de pacotes fornecidos por cada target framework, junto com a versão máxima que aquele framework oferece. Essa lista vem dos frameworks compartilhados referenciados diretamente no projeto.

Quando uma dependência transitiva está dentro desse intervalo, o NuGet a retira do grafo. Por exemplo, o net8.0 inclui o System.Text.Json 8.0.x, então uma dependência transitiva em System.Text.Json 9.0.0 continua no grafo ao mirar net8.0.

Isso acontece porque a plataforma não fornece aquela versão mais alta. Em outras palavras, o pruning nunca troca uma versão exigida por uma versão inferior.

Se você quer uma visão mais conceitual do recurso, o artigo sobre o NuGet package pruning detalha o funcionamento geral e os ganhos de auditoria.

Como o restore trata cada referência

O comportamento do pruning muda conforme o tipo de referência envolvida. Pacotes transitivos e pacotes diretos recebem tratamentos distintos.

Para pacotes transitivos dentro do intervalo, o package pruning no .NET 10 faz uma remoção completa. Eles não são baixados, não aparecem nos artefatos do restore e não entram na auditoria como dependências transitivas.

Para pacotes diretos dentro do intervalo, o NuGet aplica implicitamente PrivateAssets="all" e IncludeAssets="none". Dessa forma, fica explícito que o build vai usar a versão do runtime e que o pacote não deve fluir para o .nuspec publicado.

A referência continua no arquivo de projeto até que alguém a remova. Quando ela pode sair de todos os target frameworks, o NuGet emite o aviso NU1510 para sinalizar que a linha é desnecessária.

O que mudou com o package pruning no .NET 10

O recurso estreou como opt-in no .NET SDK 9.0.200, junto com a versão 6.13 do cliente NuGet. Nessa primeira iteração, apenas pacotes transitivos eram afetados.

A partir do .NET 10 SDK, o pruning passou a afetar também os pacotes diretos. Com isso, o escopo de limpeza do grafo ficou bem maior.

Além disso, o package pruning no .NET 10 faz parte da experiência padrão. Qualquer projeto que mire net10.0 ou superior recebe o recurso sem configuração adicional.

Se qualquer target framework de um projeto multialvo for net10.0 ou posterior, o pruning se aplica a todos os frameworks daquele projeto. As notas do NuGet 7.0, que acompanha o .NET 10 SDK e o Visual Studio 2026, registram essa decisão.

Mudança no nuspec gerado

A documentação de breaking changes do .NET 10 descreve uma consequência importante para autores de bibliotecas. Antes, referências diretas podáveis ainda apareciam na lista de dependências do .nuspec.

Agora, com PrivateAssets="all" aplicado automaticamente, o pacote some do grupo do framework que já o fornece. Por exemplo, um projeto com alvos net10.0 e net472 mantém o System.Text.Json apenas no grupo do .NET Framework.

O aviso NU1510 e sua importância prática

Entre os avisos ligados ao package pruning no .NET 10, o NU1510 é especialmente útil em projetos que evoluíram ao longo de várias versões do .NET. Imagine um projeto que referenciava System.Text.Json porque o framework alvo não incluía essa biblioteca.

Depois da migração para o .NET 10, essa referência se torna redundante, mas pode ficar no arquivo indefinidamente. Nesse caso, o NU1510 atua como um auditor passivo que orienta a remoção.

É um aviso, não um erro, então não interrompe o build. Ainda assim, quem usa TreatWarningsAsErrors vai ver o build falhar até resolver a referência.

Para projetos multialvo, a recomendação oficial é condicionar a referência aos frameworks que realmente precisam dela:

<ItemGroup>
  <!-- Referencia o System.Text.Json 8.0 apenas em alvos anteriores ao .NET 8 -->
  <PackageReference Include="System.Text.Json" Version="8.0.5"
    Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))" />
</ItemGroup>

Quando o FrameworkReference vem de outro projeto

A lista de pruning considera apenas os FrameworkReference diretos do projeto atual. Se o Microsoft.AspNetCore.App chega por meio de um ProjectReference, o NuGet não sabe que pacotes como Microsoft.Extensions.Caching.Memory são podáveis.

A solução é adicionar o FrameworkReference diretamente no projeto consumidor. Em seguida, o NU1510 aparece normalmente e você pode remover o PackageReference desnecessário.

Os avisos NU1511 e NU1509

O NU1511 surge quando o ID de um ProjectReference coincide com o ID de um pacote da lista de pruning. Como a poda de projetos não é suportada, a saída é renomear o projeto.

Já o NU1509 indica itens PrunePackageReference duplicados para o mesmo pacote. Muitas vezes, a duplicata está em arquivos importados, como Directory.Build.props ou Directory.Packages.props.

Antes e depois do package pruning no .NET 10

Para ilustrar o impacto, considere um projeto net10.0 que referencia Microsoft.Extensions.AI e NuGet.Protocol. Sem o pruning, o restore traz um pacote transitivo vulnerável e emite um aviso semelhante ao seguinte:

Sample.csproj : warning NU1903: Package 'System.Formats.Asn1' 6.0.0 has a known high severity vulnerability

Transitive Package                                           Resolved
> System.Diagnostics.DiagnosticSource                        10.0.0
> System.Formats.Asn1                                        6.0.0
> System.Security.Cryptography.Pkcs                          6.0.4
> System.Security.Cryptography.ProtectedData                 4.4.0
> System.Text.Json                                           10.0.0
> System.Threading.Channels                                  10.0.0

Com o package pruning no .NET 10 habilitado, o System.Formats.Asn1 sai do grafo transitivo e o aviso desaparece. Do mesmo modo, saem System.Diagnostics.DiagnosticSource, System.Text.Json e System.Threading.Channels, redundantes no net10.0.

O resultado é um grafo bem mais enxuto. Projetos com árvores mais profundas, ou que usam bibliotecas que puxam muitos pacotes da plataforma, tendem a ver uma redução ainda maior.

NuGetAuditMode igual a all

Outra mudança que acompanha o package pruning no .NET 10 é o novo padrão do NuGetAuditMode. Em projetos que miram net10.0 ou superior, o valor passa a ser all, enquanto os demais continuam com direct.

Isso significa que, por padrão, o NuGet audita as dependências transitivas, e não apenas os pacotes referenciados diretamente. Em projetos multialvo, basta um framework selecionar all para que a auditoria use esse valor em todos.

As duas mudanças se complementam. O pruning tira do grafo os pacotes da plataforma, e a auditoria ampliada cobre as dependências transitivas restantes, que são as que o aplicativo de fato usa.

Segundo a telemetria da equipe do NuGet, projetos com esses padrões apresentam 70% menos relatórios de vulnerabilidades transitivas. Portanto, cada alerta restante merece atenção real, como reforça o guia de boas práticas de segurança em APIs ASP.NET Core.

Impacto do package pruning no .NET 10 sobre o restore

Os benefícios do package pruning no .NET 10 vão além da auditoria. Com menos pacotes no grafo, há menos versões transitivas para conciliar e menos chances de conflito ou falha de resolução.

A equipe do NuGet registrou que restores com o recurso habilitado têm uma taxa de sucesso mensuravelmente maior. Além disso, em nível de projeto, o pruning pode reduzir o tempo de restore em até 50%.

Há um ponto de atenção para quem usa arquivo de lock. O packages.lock.json regenerado pode listar menos pacotes do que antes, o que produz um diff maior do que o normal no primeiro commit.

A verificação do modo travado, por sua vez, já conhece o pruning. Dessa forma, um restore com --locked-mode não falha apenas porque pacotes podados deixaram de aparecer.

Como configurar o package pruning no .NET 10

Para projetos que miram o .NET 10, nenhuma configuração é necessária, porque o recurso já vem ligado. Ainda assim, é possível desligá-lo definindo RestoreEnablePackagePruning como false no projeto ou no Directory.Build.props:

<PropertyGroup>
  <!-- Desliga o pruning neste projeto -->
  <RestoreEnablePackagePruning>false</RestoreEnablePackagePruning>
</PropertyGroup>

Para projetos no .NET 9 que desejam adotar o recurso antes da migração, basta habilitá-lo explicitamente junto com a auditoria ampliada:

<PropertyGroup>
  <!-- Antecipa no .NET 9 SDK os padrões do .NET 10 -->
  <NuGetAuditMode>all</NuGetAuditMode>
  <RestoreEnablePackagePruning>true</RestoreEnablePackagePruning>
</PropertyGroup>

A propriedade RestoreEnablePackagePruning é configurável por target framework. Isso dá flexibilidade a projetos multialvo que precisam de comportamentos diferentes por alvo.

Lembre também que o recurso depende do resolvedor de dependências padrão do NuGet 6.12. Quem ativa RestoreUseLegacyDependencyResolver fica sem o pruning.

Listas customizadas com PrunePackageReference

Para cenários avançados, o package pruning no .NET 10 aceita listas próprias, definidas com o item PrunePackageReference. O atributo Version indica a versão máxima a remover.

Por exemplo, a versão 1.0.0 poda todos os pacotes até a 1.0.0 inclusive, mas mantém a 1.0.1. O trecho abaixo aplica a mesma regra a um pacote fictício:

<ItemGroup>
  <!-- Poda todas as versões do pacote até a 2.5.0, inclusive -->
  <PrunePackageReference Include="Legacy.Package" Version="2.5.0" />
</ItemGroup>

Na maioria dos casos, porém, isso não é necessário. O .NET SDK já predefine a lista com base nos frameworks compartilhados do projeto.

Package pruning no .NET 10 e o arquivo deps.json

O package pruning no .NET 10 também afeta o arquivo *.deps.json gerado durante o build. Com o recurso habilitado, esse arquivo pode ter menos entradas.

Isso ocorre porque os pacotes fornecidos pelo runtime e removidos do grafo não precisam constar como dependências da aplicação. Assim, o arquivo reflete com mais fidelidade o que o aplicativo carrega em tempo de execução.

Na prática, ferramentas de análise e auditorias que leem esse artefato passam a trabalhar sobre dados mais precisos. Quem distribui aplicações em containers, como no artigo sobre publicar aplicações .NET como containers, também ganha um inventário mais limpo.

Package pruning no .NET 10 em projetos multialvo

Projetos que miram vários frameworks merecem atenção especial com o package pruning no .NET 10. Se ao menos um alvo for net10.0 ou posterior, o pruning vale para todos, inclusive net8.0 ou netstandard2.0.

Essa escolha mantém o grafo consistente entre os frameworks. Ela também evita que o mesmo pacote seja tratado de formas diferentes em cada alvo, o que geraria ambiguidade nos resultados de auditoria.

Por outro lado, frameworks como o net48 não fornecem pacotes como System.Text.Json. Nesses alvos, a referência continua necessária, e o NU1510 não aparece.

Mais pacotes devem entrar no runtime

As equipes do NuGet e do .NET indicam que mais pacotes podem virar parte das bibliotecas de runtime com o tempo. O System.IO.Pipelines, que começou como pacote independente e depois entrou na plataforma, é o exemplo clássico.

À medida que essa lista cresce, o pruning reduz automaticamente grafos que acumularam referências redundantes ao longo dos anos. Por isso, o package pruning no .NET 10 funciona como manutenção contínua do grafo nas próximas versões do .NET.

Conclusão

O package pruning no .NET 10 muda a forma como o NuGet gerencia as dependências de projetos modernos. Somado ao NuGetAuditMode=all, ele entrega uma auditoria muito mais precisa, com menos falsos positivos e restores mais rápidos.

Para equipes com soluções grandes, a queda de 70% nos relatórios transitivos e o ganho de até 50% no restore chegam sem esforço de configuração. Já para quem ainda está no .NET 9, a recomendação para antecipar o package pruning no .NET 10 é ativar as duas propriedades desde já.

FAQ

O pruning pode remover um pacote que meu código usa diretamente?

Não. Ele só atua em versões cobertas pelo runtime, e referências diretas recebem PrivateAssets="all" com o aviso NU1510, sem remoção automática da linha.

Preciso alterar meu projeto para usar o pruning no .NET 10?

Não. Projetos que miram net10.0 ou superior já recebem o recurso habilitado por padrão.

Como desabilitar o pruning se necessário?

Defina RestoreEnablePackagePruning como false no arquivo de projeto ou no Directory.Build.props.

O pruning resolve todos os avisos do NuGet Audit?

Não. Ele elimina os falsos positivos de pacotes da plataforma, mas vulnerabilidades em pacotes realmente usados continuam aparecendo, como deve ser.

O pruning está disponível no .NET 9?

Sim, como opt-in a partir do .NET SDK 9.0.200. Basta definir RestoreEnablePackagePruning como true.

O NU1510 é um erro?

Não. É um aviso que sinaliza uma referência removível, e só quebra o build se você tratar avisos como erros.