DevGuideTK
← Trilha Backend

Dependências e pacotes

Versionamento semântico, arquivos de trava e o risco de segurança das dependências transitivas.

Os resultados desta página vêm de testes em containers, com npm 11, pip, Go 1.25 e os registros públicos de cada ecossistema.

Instalação reproduzível no Dockerfile e no CI

# Node: npm ci falha se o package-lock.json não bater com o package.json
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts
# Python: versões exatas e hashes gerados a partir de requirements.in
pip-compile --generate-hashes requirements.in      # gera requirements.txt
pip install --require-hashes -r requirements.txt   # recusa pacotes diferentes

# Go: o go.sum é conferido em todo download
go mod download
go mod verify      # confere os módulos já baixados contra o go.sum

# C#: restaura exatamente o packages.lock.json
dotnet restore --locked-mode

O que cada um faz quando algo não bate, nos testes:

SituaçãoResultado
npm ci com uma dependência no package.json que não está na travaParou com erro (EUSAGE), sem instalar nada
go run com o hash de uma dependência alterado no go.sumchecksum mismatch e SECURITY ERROR
pip install --require-hashes com um hash erradoTHESE PACKAGES DO NOT MATCH THE HASHES

Verificar vulnerabilidades

npm audit --omit=dev                 # Node, só dependências de produção
pip-audit -r requirements.txt        # Python
govulncheck ./...                    # Go, só vulnerabilidades alcançáveis
dotnet list package --vulnerable --include-transitive   # C#

No teste, um projeto com o Express fixado na versão 4.17.0 mostrou no npm audit 7 vulnerabilidades, 4 de severidade alta. As versões atuais da linha 4 corrigem todas. Esse é o retrato de uma dependência que ficou anos sem atualização.

No CI, faça a verificação falhar só a partir de uma severidade escolhida, para não bloquear o trabalho por falhas de baixa gravidade:

npm audit --omit=dev --audit-level=high

Atualização automática com Renovate

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "minimumReleaseAge": "3 days",
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "automerge": true
    },
    {
      "matchDepTypes": ["devDependencies"],
      "matchUpdateTypes": ["minor", "patch"],
      "groupName": "dependências de desenvolvimento",
      "automerge": true
    },
    {
      "matchUpdateTypes": ["major"],
      "dependencyDashboardApproval": true
    }
  ],
  "vulnerabilityAlerts": {
    "minimumReleaseAge": null
  }
}

Com essa configuração, correções e atualizações de pacotes de desenvolvimento entram sozinhas se os testes passarem, versões maiores esperam aprovação, e toda versão nova espera três dias antes de ser proposta, exceto as correções de segurança.

Isolar uma dependência atrás de uma interface própria

Trocar uma biblioteca, ou atualizá-la para uma versão maior, fica muito mais barato quando o código da aplicação não a usa diretamente. O padrão é criar uma interface própria, com os termos do seu domínio, e uma única implementação que conhece a biblioteca.

O exemplo isola o envio de e-mails. O resto do código só conhece EnviadorDeEmail; trocar de provedor, ou testar sem enviar nada, mexe num único lugar.

export interface EnviadorDeEmail {
  enviar(para: string, assunto: string, corpo: string): Promise<void>;
}

// A única parte do código que conhece o SDK do provedor
export class EnviadorDoProvedor implements EnviadorDeEmail {
  private readonly cliente: ClienteDoProvedor;

  constructor(cliente: ClienteDoProvedor) {
    this.cliente = cliente;
  }

  async enviar(para: string, assunto: string, corpo: string) {
    await this.cliente.messages.send({ to: para, subject: assunto, text: corpo });
  }
}

// Para testes: guarda os e-mails em vez de enviar
export class EnviadorFalso implements EnviadorDeEmail {
  readonly enviados: { para: string; assunto: string }[] = [];

  async enviar(para: string, assunto: string) {
    this.enviados.push({ para, assunto });
  }
}

ClienteDoProvedor representa o SDK de um provedor de e-mail qualquer. Os nomes dos métodos dele variam; o ponto é que só a classe que implementa a interface os conhece.

O que muda de uma linguagem para outra

Como a interface é declaradaComo a implementação é escolhida
Nodeinterface do TypeScript, verificada só na compilaçãoPassada no construtor de quem usa, ou por um contêiner de injeção
PythonProtocol: qualquer classe com os mesmos métodos serve, sem herançaPassada no construtor; frameworks como FastAPI têm injeção de dependências
Gointerface, satisfeita implicitamente por quem tem os métodosPassada como campo ou parâmetro
Javainterface com implements explícitoInjeção de dependências do Spring ou passagem manual
C#interface com implementação explícitaContêiner de injeção do ASP.NET (AddScoped<IEnviadorDeEmail, EnviadorDoProvedor>())

Não vale a pena fazer isso para todas as bibliotecas: um framework web ou um ORM permeiam o sistema inteiro, e envolvê-los cria mais problemas do que resolve. Vale para dependências com um papel claro e limitado, que podem ser trocadas ou que falam com serviços externos: e-mail, pagamentos, armazenamento de arquivos, mensageria, provedores de IA.

Lista de revisão

Ao acrescentar

  • O pacote existe, tem o nome exato e é mantido?
  • A biblioteca padrão não resolveria?
  • Quantas dependências indiretas ele traz?
  • A licença é compatível com o projeto?

Ao instalar

  • O arquivo de trava está no repositório?
  • O CI e o Dockerfile usam a instalação exata (npm ci, --require-hashes, --locked-mode)?
  • Os scripts de instalação estão desligados no CI, onde possível?

Ao manter

  • Há atualização automática, com testes que a validem?
  • Vulnerabilidades são verificadas no CI, com um limite de severidade?
  • Dependências com papel limitado e serviços externos ficam atrás de uma interface própria?

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Ver os riscos das dependências na prática

    Quero ver na prática como funcionam as dependências e os riscos delas, num container.
    
    Pergunte qual ecossistema eu uso.
    
    Vamos em etapas:
    1. Instalar um framework popular num projeto vazio e contar quantos pacotes vieram.
    2. Declarar um intervalo de versões, instalar sem arquivo de trava e ver qual versão veio.
    3. Mudar o arquivo de dependências sem atualizar a trava e rodar a instalação exata.
    4. Adulterar um hash no arquivo de trava e tentar instalar.
    5. Criar um pacote local com script de instalação e ver o que ele consegue fazer; depois instalar com os scripts desligados.
    6. Instalar uma versão antiga de um pacote popular e rodar a verificação de vulnerabilidades.
    
    Em cada etapa, peça que eu preveja o resultado antes.
  • Atualizar uma dependência para uma versão maior

    Preciso atualizar uma dependência importante para uma nova versão maior e quero fazer isso com segurança.
    
    Pergunte qual dependência, de qual versão para qual, e qual linguagem eu uso.
    
    Me guie por:
    1. Ler as notas de versão e o guia de migração, e listar as mudanças que afetam o meu código.
    2. Encontrar no código todos os pontos que usam a biblioteca.
    3. Fazer a atualização numa branch, corrigindo um ponto por vez com os testes rodando.
    4. Conferir as dependências indiretas que mudaram junto.
    5. Planejar o deploy e a forma de reverter.
    
    Se a biblioteca for usada em muitos lugares, sugira como isolá-la atrás de uma interface própria antes.
  • Revisar as dependências do projeto

    Vou colar abaixo o arquivo de dependências do meu projeto e, se tiver, o Dockerfile e o arquivo do CI. Revise com foco em reprodutibilidade e segurança.
    
    Verifique, e para cada item diga se está correto, incorreto ou ausente:
    1. Há arquivo de trava, e o CI e o Dockerfile usam a instalação exata?
    2. Há dependências que a biblioteca padrão substituiria?
    3. Há dependências desatualizadas em várias versões maiores?
    4. Há dependências de desenvolvimento declaradas como de produção?
    5. Os scripts de instalação estão desligados no CI?
    6. Há verificação de vulnerabilidades no CI?
    7. Há algum nome suspeito, parecido com um pacote popular?
    
    Para cada problema, explique o risco e mostre a correção.
    
    Arquivos:
    [cole aqui]