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.