Dependências transitivas: o risco oculto na cadeia de suprimentos
Dependências transitivas são o código que você nunca escolheu, mas ainda é seu. Entenda por que são arriscadas, como encontrá-las e como corrigi-las e monitorá-las.
Dependências transitivas são os pacotes indiretos que seu software traz por meio de suas dependências diretas, e elas são a maioria silenciosa de toda base de código moderna. Quando você adiciona uma biblioteca ao seu projeto, faz um punhado de escolhas deliberadas. Essas bibliotecas então dependem de outras bibliotecas, que dependem de ainda outras, e em poucos níveis a árvore se abre em centenas ou até milhares de pacotes que você nunca nomeou, nunca avaliou e, em muitos casos, dos quais nunca ouviu falar. Esse é o risco oculto da cadeia de suprimentos: a maior parte do que você entrega é código que você não escolheu, e uma falha em qualquer ponto dessa árvore é uma falha que é sua. Este mergulho aprofundado faz parte do nosso guia mais amplo sobre software composition analysis, e foca especificamente na camada transitiva, por que ela é tão fácil de ignorar e o que fazer a respeito.
Vale a pena declarar a distinção com clareza. Uma direct dependency é aquela que você adicionou deliberadamente, a entrada que você pode apontar no seu manifesto. Uma transitive dependency, às vezes chamada de indirect dependency ou dependência aninhada, é aquela que foi instalada porque algo que você adicionou precisava dela. Você escolheu um framework web; ele escolheu um motor de templates; o motor de templates escolheu um utilitário de parsing de strings; e esse utilitário escolheu mais outra coisa. Cada um desses pacotes executa dentro do seu processo, com o mesmo acesso à memória, ao ambiente e aos segredos que o seu próprio código tem. O runtime não distingue entre o código que você escreveu, o código que você escolheu e o código que simplesmente veio de carona.
Por que a maior parte da sua árvore é transitiva
Surpreende muitas equipes a primeira vez que elas realmente contam. Em termos de proporção, as dependências diretas são uma pequena fração do total; a esmagadora maioria dos pacotes resolvidos em uma aplicação típica é transitiva. Um projeto com duas dúzias de dependências diretas pode facilmente resolver em muitas centenas ou alguns milhares de pacotes no total, uma vez que a árvore esteja totalmente expandida. Isso não é sinal de um projeto inchado; é simplesmente como o reúso se acumula. Cada mantenedor, com razão, constrói sobre o trabalho de outros, e é exatamente essa alavancagem que torna o open source tão produtivo.
A consequência para a segurança é que sua superfície de ataque é definida muito mais pelo que você herda do que pelo que você instala. Você pode ser escrupuloso com o punhado de bibliotecas que adiciona, ler o código delas, verificar seus mantenedores e ainda assim acabar executando milhares de linhas que nunca viu. O risco oculto não é que as dependências transitivas sejam inerentemente piores que as diretas. É que elas são invisíveis por padrão. Não aparecem no seu manifesto, raramente aparecem na revisão de código e, sem ferramentas, nunca aparecem no modelo mental que alguém tem da aplicação.
A vulnerabilidade ainda é sua
Aqui está a parte desconfortável. Quando um CVE é publicado contra um pacote cinco níveis abaixo na sua árvore, ele é tão problema seu quanto uma falha em código que você mesmo escreveu. Se esse pacote faz parsing de entradas não confiáveis, desserializa dados ou lida com uma requisição de rede em um caminho que sua aplicação exercita, um atacante consegue alcançá-lo. O exploit não verifica quem adicionou a dependência.
O que torna as vulnerabilidades transitivas singularmente incômodas é que você não tem relação direta com o pacote afetado. Você não pode simplesmente subir a versão dele no seu manifesto, porque ele não está no seu manifesto. A versão que você está executando foi escolhida por uma das suas dependências diretas, ou por uma das dependências delas. Você está, na prática, esperando que uma cadeia de mantenedores que você nunca conheceu atualize cada um por sua vez. É por isso que as falhas na cadeia de suprimentos de software estão hoje entre as categorias mais prementes em segurança de aplicações, refletidas no OWASP Top 10 2025 sob software supply chain failures (A03). O risco não é hipotético nem raro; é estrutural, e cresce a cada pacote que você herda.
Como os gerenciadores de pacotes resolvem a árvore
Para gerenciar o risco transitivo, você precisa entender, ao menos de forma aproximada, como ele é construído. Todo ecossistema tem um resolver cuja tarefa é pegar suas dependências declaradas, ler o que cada uma exige e produzir um conjunto concreto e instalável de pacotes em versões específicas. Os detalhes diferem, mas o formato é consistente entre as principais cadeias de ferramentas: o mundo JavaScript com npm, yarn e pnpm; a JVM com Maven e Gradle; Python com pip e poetry; Go com seu sistema de módulos (Go modules); e seus equivalentes em outros lugares.
As decisões do resolver importam enormemente para a segurança. Quando dois pacotes na sua árvore pedem versões sobrepostas, mas diferentes, da mesma dependência, o resolver precisa escolher, e sua escolha determina se você acaba executando uma versão corrigida ou uma versão vulnerável. Alguns ecossistemas achatam a árvore e tentam satisfazer todos com uma única versão compartilhada; outros permitem que múltiplas versões do mesmo pacote coexistam mais fundo na árvore. De um jeito ou de outro, a versão que de fato aterrissa muitas vezes não é a versão que qualquer pacote individual pediu explicitamente. É um resultado negociado, e pode mudar na próxima vez que você instalar, a menos que você a tenha fixado.
Por que os lockfiles importam
É exatamente isso que um lockfile faz. Um manifesto declara o que você quer, frequentemente como um intervalo: qualquer versão compatível a partir de alguma base. Um lockfile registra o que você de fato obteve: a versão exata de cada pacote na árvore totalmente resolvida, direta e transitiva, geralmente acompanhada de um hash de integridade para cada um. O manifesto é a intenção; o lockfile é a realidade.
Sem um lockfile, duas instalações do mesmo manifesto em dias diferentes podem produzir árvores diferentes, porque novas versões de pacotes transitivos são publicadas constantemente e intervalos flutuantes as trazem silenciosamente. Isso é ruim para a reprodutibilidade e pior para a segurança, porque significa que o código que você testou não é necessariamente o código que você entregou. Com um lockfile versionado no controle de versão, todos, inclusive seu pipeline de CI/CD e sua build de produção, resolvem a mesma árvore toda vez. Os hashes de integridade acrescentam uma segunda garantia: os bytes que você instala são exatamente os bytes que foram registrados, de modo que um pacote não pode ser trocado por baixo dos panos, mesmo que uma entrada de registro seja adulterada. Lockfiles são o hábito mais importante para controlar o risco transitivo, e não custam nada além da disciplina de versioná-los.
Corrigindo uma vulnerabilidade em uma dependência transitiva
Quando um scanner sinaliza um pacote transitivo vulnerável, você tem uma escada de opções, e vale a pena tentá-las em ordem.
A correção mais limpa é atualizar o direct parent. Descubra qual das suas dependências diretas traz o pacote vulnerável e veja se uma versão mais nova desse parent depende de uma versão corrigida. Na maioria das vezes, essa é a resposta certa: você atualiza uma linha no seu manifesto, o resolver faz o resto, e a versão vulnerável desaparece da sua árvore. Isso mantém você em combinações de pacotes suportadas e testadas.
Quando o parent ainda não acompanhou, a próxima alavanca é uma versão forçada. Todo ecossistema importante oferece uma maneira de dizer "onde quer que este pacote apareça na minha árvore, use ao menos esta versão", seja isso chamado de override, resolution, dependency management ou uma diretiva replace. Isso sobe cirurgicamente o pacote transitivo sem esperar que o parent atualize. É poderoso, então use com deliberação: uma versão forçada pode, em princípio, quebrar um parent que esperava a API mais antiga, então combine-a com testes e trate-a como uma ponte temporária que você remove assim que o parent lançar sua própria correção.
Se nenhuma delas funcionar, você escala. Você pode substituir a direct dependency problemática por uma alternativa mais bem mantida que não carregue o problema, o que dá mais trabalho, mas frequentemente é a jogada mais saudável no longo prazo. Como um verdadeiro último recurso, você pode fazer um fork e corrigir a dependência você mesmo, aceitando que agora é dono da manutenção desse fork até que o upstream se recupere. Fazer um fork é uma opção real quando nada mais está disponível, mas seu custo é contínuo, então reserve-a para casos em que o risco realmente justifique.
Os ataques que cavalgam a camada transitiva
Dependências transitivas não são apenas uma fonte passiva de bugs herdados; elas são um alvo ativo, porque quanto mais fundo um pacote se encontra, menos escrutínio ele recebe. Vários padrões de ataque à cadeia de suprimentos exploram exatamente isso.
Dependency confusion engana um resolver para que puxe um pacote público malicioso no lugar de um privado interno, publicando um pacote com o mesmo nome e um número de versão mais alto em um registro público. Como os resolvers podem preferir a versão mais alta, o código do atacante se infiltra como se fosse seu. Typosquatting depende de um nome digitado errado ou parecido, de modo que um único caractere errado em um manifesto, ou no manifesto de uma das suas dependências, substitui silenciosamente por código hostil. Pacotes maliciosos e sequestrados assumem projetos legítimos e amplamente usados, seja por um mantenedor que se corrompe, seja por um atacante que compromete a conta de um mantenedor, e então lançam uma atualização envenenada que flui rio abaixo para a árvore de todos como uma rotineira subida de versão transitiva. Protestware é o caso em que um mantenedor deliberadamente sabota ou degrada seu próprio pacote para fazer um protesto, e os usuários herdam o dano.
O que une tudo isso é que esses ataques chegam pelo mesmo canal de atualização confiável do qual você depende para correções legítimas, e na maioria das vezes aterrissam na camada transitiva, onde ninguém está olhando. Fixar versões e impor hashes de integridade é sua defesa mais forte: se você instala versões exatas e registradas com hashes verificados, um lançamento malicioso inesperado não consegue entrar silenciosamente na sua build, e você ganha um momento revisável antes que qualquer código novo entre. Intervalos flutuantes, por outro lado, são um convite aberto para que a próxima versão publicada, seja lá o que ela contenha, se torne parte da sua aplicação automaticamente. Para um tratamento mais completo sobre governar todo o patrimônio de open source, veja nosso guia sobre gerenciamento de vulnerabilidades em open source.
Enxergando o grafo inteiro com SCA e um SBOM
Você não consegue gerenciar o que não consegue ver, e a característica definidora do risco transitivo é que ele é invisível sem ferramentas. Essa é a função da software composition analysis. Uma ferramenta de SCA capaz resolve sua dependency tree da mesma forma que seu gerenciador de pacotes faz, percorre-a até a profundidade total e mapeia cada pacote, direto e transitivo, para vulnerabilidades conhecidas, obrigações de licença e sinais de manutenção. Fundamentalmente, ela mostra o caminho: não apenas que um pacote vulnerável está presente, mas qual das suas dependências diretas o introduziu, que é precisamente a informação de que você precisa para escolher uma correção.
Esse inventário completo é também o que uma software bill of materials captura. Um SBOM é uma lista formal, legível por máquina, de tudo que há na sua aplicação, transitivas incluídas, e está rapidamente se tornando uma expectativa básica de clientes, auditores e reguladores. Quando uma vulnerabilidade importante estoura, a diferença entre as equipes que respondem "estamos afetados?" em minutos e as equipes que passam dias fazendo grep pelas builds é quase sempre se elas tinham ou não um SBOM preciso e o grafo por trás dele.
Por que o monitoramento contínuo é inegociável
Um scan de dependências é um instantâneo, e a árvore não fica parada. Novas vulnerabilidades são divulgadas todos os dias contra pacotes que você já entrega e não tocou. Código que estava limpo quando você o lançou pode se tornar vulnerável da noite para o dia sem que uma única linha mude do seu lado, simplesmente porque um pesquisador publicou uma descoberta contra algo lá no fundo da sua árvore.
O monitoramento contínuo é o que transforma isso de uma crise em uma rotina. Ao manter uma visão ao vivo do seu grafo de dependências resolvido e cruzá-lo com os avisos à medida que aparecem, você fica sabendo de uma vulnerabilidade transitiva recém-divulgada quando ela é divulgada, e não quando é explorada. Essa é a extensão natural de embutir segurança em todo o ciclo de vida, a mesma filosofia que descrevemos em desenvolvimento seguro de software: desloque a descoberta do risco de dependências para a esquerda, para dentro do desenvolvimento e do CI/CD, e continue observando em produção para que nada que surja mais tarde passe despercebido.
Como a Rainforest ajuda
A Rainforest fornece software composition analysis que trata a camada transitiva como uma preocupação de primeira classe, e não como algo secundário. Ela resolve seu grafo de dependências completo, direto e indireto, amarra cada pacote sinalizado de volta ao direct parent que o introduziu, para que a remediação seja óbvia, e gera um SBOM que você pode compartilhar com confiança. Como ela roda no CI/CD e monitora sua árvore resolvida continuamente, vulnerabilidades recém-divulgadas vêm à tona mesmo em código que você não alterou, e os problemas são capturados nos pull requests antes de chegarem à produção. E porque ela é parte de uma única plataforma, e não um silo isolado, seu risco de open source fica ao lado do resto do panorama de segurança da sua aplicação, em vez de em um relatório separado que ninguém lê. Você pode explorar os detalhes em nossa página de software composition analysis.
O risco oculto das dependências transitivas não é que elas sejam perigosas por natureza; é que são fáceis de ignorar até o momento em que uma delas está na primeira página. Fixe suas versões, versione seus lockfiles, mapeie o grafo inteiro e observe-o continuamente, e a árvore que você nunca escolheu se torna algo que você pode de fato ver e defender. Se você quiser ver seu próprio grafo de dependências, com transitivas e tudo, mapeado e monitorado, agende uma demonstração.
Perguntas frequentes
O que é uma dependência transitiva?
Uma transitive dependency, também chamada de indirect dependency ou dependência aninhada, é um pacote que é instalado não porque você o adicionou, mas porque uma das suas dependências diretas precisa dele. Quando você adiciona uma biblioteca, ela traz suas próprias dependências, que trazem as delas, e assim por diante. O resultado é uma árvore que se abre por vários níveis de profundidade, e os pacotes abaixo do nível de topo são todos transitivos. Eles rodam com os mesmos privilégios que o seu próprio código, mesmo que você nunca os tenha escolhido.
Por que as dependências transitivas são um risco de segurança?
Porque a maior parte do código de uma aplicação típica chega de forma transitiva, a maior parte do seu risco vive em código que você nunca selecionou nem revisou. Uma vulnerabilidade lá no fundo da árvore ainda é sua, e um atacante consegue alcançá-la se sua aplicação exercitar o caminho afetado. A dificuldade é que você não tem relação direta com o pacote, então não pode simplesmente atualizá-lo no seu manifesto e, sem ferramentas, essas dependências são invisíveis na revisão de código e no seu modelo mental da aplicação.
Como corrijo uma vulnerabilidade em uma dependência transitiva?
Comece atualizando o direct parent que trouxe o pacote vulnerável, já que um parent mais novo frequentemente depende de uma versão corrigida e o resolver faz o resto. Se o parent ainda não atualizou, force uma versão segura usando o mecanismo de override, resolution, dependency management ou replace do seu ecossistema, respaldado por testes. Se isso não for viável, substitua a direct dependency por uma alternativa mais bem mantida e, como último recurso, faça um fork e corrija o pacote você mesmo, aceitando o custo contínuo de manutenção.
O que é dependency confusion?
Dependency confusion é um ataque à cadeia de suprimentos que engana um resolver de pacotes para que instale um pacote público malicioso em vez de um privado, interno e legítimo. O atacante publica um pacote em um registro público usando o mesmo nome do seu pacote interno, mas com um número de versão mais alto. Como os resolvers podem preferir a versão mais alta, o pacote hostil é puxado para a sua build como se fosse o verdadeiro. Colocar pacotes internos em escopos (scoping) e controlar a resolução de registros são defesas fundamentais.
Por que os lockfiles importam para a segurança?
Um manifesto declara as versões que você quer, frequentemente como um intervalo, enquanto um lockfile registra as versões exatas que você de fato resolveu em toda a árvore, direta e transitiva, geralmente com um hash de integridade para cada uma. Sem um lockfile, intervalos flutuantes podem puxar pacotes transitivos diferentes e mais novos a cada instalação, de modo que o código que você testou pode não ser o código que você entregou. Um lockfile versionado torna as builds reproduzíveis, e os hashes garantem que os bytes que você instala correspondam ao que foi registrado, bloqueando a substituição silenciosa.

Escrito por
Bruno Baldo
CMO
Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Software Composition Analysis (SCA): O Guia Completo
A Software Composition Analysis (SCA) encontra dependencies open source vulneráveis e arriscadas. Veja como a SCA funciona, priorização, SBOMs e CI/CD.

Gerenciando vulnerabilidades de código aberto nas suas dependências
Guia prático de gestão de vulnerabilidades open source: encontre dependências vulneráveis, priorize com CVSS, EPSS, KEV e reachability, e remedie CVEs.

SBOM (Software Bill of Materials): O Que É e Por Que Você Precisa de Um
Um software bill of materials (SBOM) é um inventário completo dos componentes do seu app. Conheça formatos de SBOM, geração, VEX e por que você precisa.
