Seu lockfile está escondendo vulnerabilidades?

Um lockfile é o documento mais honesto do seu repositório: ele lista a versão exata de cada pacote que o seu build realmente instala, sem intervalos, sem “latest”, sem surpresas. Ele também é, silenciosamente, uma lista de superfície de ataque — cada versão fixada é uma versão na qual alguém pode ter encontrado uma falha desde que você a fixou. Este artigo explica como um verificador transforma essa lista em respostas: como o banco de dados OSV compara versões com vulnerabilidades conhecidas, por que uma sinalização KEV da CISA importa mais do que uma pontuação CVSS e por que “nenhuma vulnerabilidade encontrada” é uma afirmação sobre um banco de dados, não sobre o seu código. Você pode fazer a verificação por conta própria no Verificador de vulnerabilidades SBOM, que analisa o seu lockfile no navegador e nunca o envia.

O que um lockfile fixa — e por que ele é a superfície de ataque

O software moderno é, em sua maior parte, código de outras pessoas. Uma aplicação web típica incorpora centenas de pacotes, e cada um deles incorpora mais: uma dependência direta de um framework web pode facilmente se tornar duzentos pacotes transitivos que você nunca escolheu e nunca revisou. O lockfile é o registro dessa árvore — package-lock.json para npm, um requirements.txt totalmente fixado para Python, go.mod e go.sum para Go — anotado para que cada instalação, em cada máquina, produza a mesma árvore. Sem ele, “funciona na minha máquina” é uma esperança, não um fato.

Essa mesma exatidão é o que torna o lockfile o lugar certo para procurar vulnerabilidades conhecidas. Um banco de dados de vulnerabilidades é indexado por nome de pacote, ecossistema e versão exata: lodash 4.17.20 é vulnerável, lodash 4.17.21 não é, e a diferença é uma única consulta. Um intervalo flutuante não pode ser julgado assim — o verificador teria que adivinhar qual versão você realmente instalou, e adivinhar não é uma prática de segurança. Então a primeira regra de higiene do lockfile é pouco glamorosa e inegociável: faça commit do lockfile, mantenha-o fixado e verifique a árvore fixada, não a lista de desejos do seu manifesto.

Como o OSV compara versões com vulnerabilidades conhecidas

O OSV.dev é um banco de dados público de vulnerabilidades que agrega avisos do GitHub, do NVD e dos ecossistemas de linguagens em um único índice consultável. Um verificador envia a ele um lote de trios de nome, ecossistema e versão — lodash, npm, 4.17.20 — e recebe os identificadores de vulnerabilidade que afetam exatamente aquela versão. Os identificadores são então buscados na íntegra: um resumo, os intervalos afetados com suas versões corrigidas, a gravidade como vetor CVSS e os aliases CVE. Nada sobre o seu projeto viaja com a consulta — apenas as coordenadas públicas de pacotes públicos.

A correspondência é deliberadamente simples, e essa é a sua força. Ela não lê o seu código, não julga se você chama a função vulnerável e não se importa com a forma como o pacote é usado. Se o aviso diz que as versões anteriores a 4.17.21 são afetadas e você fixa 4.17.20, há correspondência — mesmo que o seu código nunca toque a função vulnerável. Esse excesso de notificações é um recurso: a análise de alcançabilidade é uma pergunta mais difícil e mais lenta, e a pergunta barata (“esta versão é conhecida como ruim?”) já pega a dependência que você esqueceu de atualizar há três anos.

O que o verificador não consegue ver

Uma verificação por correspondência de versão tem um limite rígido, e vale a pena dizê-lo claramente: ela só consegue encontrar vulnerabilidades que alguém já relatou, para uma versão que o banco de dados conhece. Uma vulnerabilidade descoberta ontem e ainda não publicada é invisível. Um pacote que nunca foi vulnerável, mas é malicioso — um typosquat de um nome popular, uma versão comprometida, uma dependência que telefona para casa — não tem CVE nenhum, e nenhum verificador desse tipo vai sinalizá-lo. O mesmo vale para o seu próprio código: o verificador julga os pacotes que você instala, não o código que você escreve.

Esse limite é a razão pela qual o resultado “nenhuma vulnerabilidade encontrada” precisa ser lido com cuidado. Significa: para essas versões exatas, neste banco de dados, neste momento, nada está registrado. É um negativo útil — uma verificação limpa de um lockfile recente é genuinamente uma boa notícia — mas não é um certificado. As defesas para o que o verificador não consegue ver são outras ferramentas: fixar e revisar as dependências novas antes que entrem na árvore, acompanhar os novos avisos continuamente em vez de trimestralmente e tratar com suspeita uma nova versão repentina de um pacote silencioso. O verificador é o alarme de fumaça, não o corpo de bombeiros.

Por que uma sinalização KEV vale mais do que uma pontuação CVSS alta

Uma pontuação CVSS responde a uma hipótese: quão grave seria esta vulnerabilidade se alguém a explorasse? O catálogo de vulnerabilidades conhecidamente exploradas da CISA responde a um fato: alguém está explorando-a, na prática, agora mesmo? As duas respostas costumam discordar. Uma vulnerabilidade com pontuação 9,8 que ninguém transformou em arma pode esperar pela próxima atualização planejada; uma vulnerabilidade com pontuação 5,3 que está na lista KEV — com uma data limite de correção definida pela CISA — merece um patch esta semana, porque o exploit já existe e já está em uso.

Essa inversão é a razão pela qual o verificador marca as linhas KEV separadamente em vez de incorporá-las à ordenação por gravidade. A ordem diz o que é ruim; o selo KEV diz o que é urgente. Na prática, a triagem é breve: corrija primeiro os itens KEV, depois os críticos com uma versão corrigida publicada e então agende o restante com o próximo ciclo de atualização. O painel Bulk CVE Triage deste site coloca os mesmos CVEs no contexto de exploração — status KEV, probabilidade EPSS e CVSS juntos — quando um único selo não basta para justificar o trabalho a uma equipe.

Faça commit do lockfile, verifique-o no CI — não uma vez por trimestre

Um lockfile sem commit é um lockfile que não existe. Se ele vive apenas na máquina de um desenvolvedor, cada outra máquina instala o que “latest” resolver naquele dia, e a árvore que você verificou não é a árvore que você entrega. Fazer commit dele é o controle de cadeia de suprimentos mais barato que existe: torna as instalações reproduzíveis, torna as respostas do verificador válidas para todos e transforma uma atualização de dependência em um diff revisável em vez de uma deriva silenciosa.

A segunda metade é a cadência. As vulnerabilidades são publicadas todos os dias, então uma verificação feita uma vez por trimestre é um retrato que envelhece em dias. A forma útil é uma verificação a cada pull request que toque o lockfile, mais uma verificação agendada da branch principal — o mesmo ciclo de uma suíte de testes, com a mesma propriedade: um novo aviso transforma um build verde em vermelho no dia em que é publicado, quando a correção é uma mudança de versão, não uma revisão pós-incidente. O verificador de navegador deste site é a versão manual desse ciclo: cole o lockfile após uma atualização do dependabot, ou antes de uma release, e confirme a árvore que você está prestes a entregar.

Versões corrigidas, mudanças disruptivas e quando corrigir ou fixar

Cada correspondência em uma verificação traz uma versão corrigida — a primeira versão na qual a vulnerabilidade desaparece — ou um honesto “nenhuma publicada”. O caso comum é uma versão de patch ou secundária, e a atualização é uma mudança de uma linha no lockfile. Mas uma versão corrigida nem sempre é uma versão segura de adotar: um salto de versão principal pode mudar um comportamento do qual o seu código depende, e uma correção lançada ontem teve um dia de testes no mundo real. A decisão é a mesma de qualquer atualização de dependência: leia o changelog, rode os testes e atualize primeiro os pacotes que você realmente importa.

Quando não há versão corrigida, as opções se reduzem a três: remover a dependência se nada precisa dela, substituí-la por uma alternativa mantida ou mitigar o caminho de código vulnerável — parar de passar entrada não confiável para a função afetada, ou colocar a chamada atrás de uma verificação que o aviso descreve — e acompanhar o aviso para ver se é atualizado. O que você não deve fazer é fixar a versão vulnerável e esquecê-la: uma fixação é uma decisão, e merece um comentário no lockfile ou no rastreador de issues dizendo por quê, e uma data para revisitá-la. Verifique novamente após qualquer mudança; o verificador leva segundos, e uma segunda verificação é a única prova de que os achados da primeira desapareceram.

Fontes

Perguntas frequentes

Minha verificação não encontrou nada. Meu projeto está seguro?

É uma boa notícia, não uma garantia. Uma verificação limpa significa que nenhuma vulnerabilidade foi relatada para as suas versões fixadas exatas no banco de dados OSV. Não diz nada sobre vulnerabilidades não relatadas, pacotes maliciosos ou o seu próprio código. Mantenha o lockfile com commit, verifique-o novamente quando ele mudar e trate a verificação como uma camada de defesa — o Verificador de vulnerabilidades SBOM explica o seu próprio limite na página.

Por que o verificador precisa de versões exatas?

Porque os bancos de dados de vulnerabilidades são indexados por versão exata. Um intervalo como >=1.2 ou ~=2.0 não identifica uma única versão, então o verificador não pode dizer se a versão que você realmente instalou está afetada. Fixe versões exatas no lockfile — é para isso que os lockfiles servem — e o verificador poderá julgar cada entrada em uma única consulta.

Uma correção exigiria uma atualização de versão principal. Ainda devo corrigir?

Para uma vulnerabilidade listada na KEV, sim — corrija primeiro e lide com a quebra depois, porque o exploit já está em uso. Para todo o resto, pondere a gravidade contra a quebra: atualize os pacotes que o seu código realmente importa, rode os testes e, se o salto principal for arriscado demais, mitigue o caminho de código vulnerável e agende a atualização. Verifique novamente após a mudança para confirmar que o achado desapareceu.