Skip to main content

npm Shrinkwrap renovado: como bloquear dependências do npm com Package-Lock e Yarn.Lock

Escrito por
Headshot of Assaf Hefetz

Assaf Hefetz

10 de janeiro de 2018

0 minutos de leitura

Em 2016, o gerenciamento de dependências ganhou destaque no mundo todo quando um desenvolvedor desconhecido removeu do registro um pequeno pacote do Node.js chamado left-pad — o que quebrou milhares de projetos que dependiam dele e tirou do ar alguns dos maiores sites do mundo. A npm, Inc. precisou intervir e restaurar o pacote para acabar com o caos.

Essa história mostra o quanto as dependências são importantes. Se elas não estiverem disponíveis ou forem diferentes do esperado, problemas sérios podem acontecer.

Esqueça o ShrinkWrap: uma forma elegante de bloquear dependências no Node

Bloquear ou “fixar” dependências é uma prática recomendada amplamente adotada nos ecossistemas Ruby, Python e em outros. A ideia é congelar a versão de um pacote e de suas dependências para que, ao implantar um projeto, sempre seja instalada a mesma versão de cada dependência, tornando a instalação confiável e previsível.

No Node.js, essa prática era bem menos comum até recentemente. O npm Shrinkwrap era a solução, mas tinha vários problemas. Um deles é que o arquivo shrinkwrap.json, que contém informações confidenciais sobre suas dependências, pode ser incluído ao publicar um pacote. Outro é que o Shrinkwrap dificulta a adição de novas dependências. Além disso, o shrinkwrap apresenta um risco de segurança: pode ser vulnerável a ataques de execução remota de código quando não são usadas URLs HTTPS.

Recentemente, a comunidade npm comemorou uma solução mais nova e elegante, integrada ao npm 5: package-lock.json. Quem usa Yarn já contava com o yarn.lock, que oferece bloqueio de dependências integrado para Node. Vamos falar dessas duas soluções daqui a pouco.

Mas, antes, será que você deve bloquear as dependências?

Prós e contras de bloquear (fixar) suas dependências

Muitos profissionais, e até mesmo o guia do governo dos EUA para fabricantes de software, “Before you ship”, recomendam que os projetos fixem todas as dependências. O motivo:

  • Alterações incompatíveis — novas versões das dependências podem introduzir alterações incompatíveis com seu código ou com sua implementação específica.

  • Bugs ou problemas — uma nova versão pode apresentar problemas que passaram despercebidos pelos responsáveis pelo pacote. Como não é possível testar todas as dependências do seu código a cada atualização, é melhor usar por padrão uma versão que já tenha comprovado funcionar no seu ambiente.

  • Previsibilidade — o desenvolvimento ágil e a entrega contínua se baseiam na capacidade de implantar software automaticamente e de forma previsível. Atualizações de pacotes que podem causar comportamentos inconsistentes ou imprevisíveis vão contra essa previsibilidade.

Fixar pacotes também tem desvantagens:

  • Segurança — a Snyk oferece uma ferramenta que verifica vulnerabilidades em código aberto, por isso conhecemos bem os riscos inerentes às dependências. Nossos dados mostram que, pelo menos na comunidade Node.js, 76% dos projetos usam pacotes com vulnerabilidades conhecidas. Ao bloquear ou fixar suas dependências, você também mantém as vulnerabilidades. Se um problema de segurança for descoberto e o responsável pelo pacote lançar uma correção, você continuará usando a versão antiga e vulnerável.

  • Módulos de terceiros — se você é responsável por um módulo de terceiros que, por sua vez, é uma dependência de outros projetos, fixar suas dependências obrigará seus usuários a permanecer nas mesmas versões. A documentação de empacotamento do Python, por exemplo, diz que isso é restritivo demais e não é considerado uma boa prática. Agradecemos a Dustin Ingram por chamar nossa atenção para isso.

Embora o problema de segurança seja significativo, a maioria dos desenvolvedores não vai abrir mão dos benefícios de instalações e implantações previsíveis. Nossa recomendação: bloqueie as dependências, mas inclua a verificação de vulnerabilidades no seu processo de build.

Se você tiver testes e monitoramento de segurança em vigor, saberá quando uma dependência ficar desatualizada ou quando vulnerabilidades forem descobertas e poderá atualizá-la nesse momento. Em um mundo ideal, você atualizaria imediatamente todos os componentes de código aberto do seu projeto assim que novas versões fossem lançadas, para obter a máxima proteção contra falhas e vulnerabilidades. Atualizar pelo menos quando uma vulnerabilidade conhecida for descoberta parece ser uma segunda melhor opção aceitável.

Como bloquear dependências com package-lock

package-lock.json é um novo recurso do npm 5 que descreve a árvore de dependências exata gerada nas instalações anteriores. Assim, é possível gerar uma árvore de dependências idêntica nas instalações seguintes, independentemente de atualizações intermediárias das dependências.

O npm 5 cria automaticamente o arquivo package-lock, que deve ser incluído no controle de versão. Embora isso possa ser trabalhoso, há dois grandes benefícios:

  • Depois de adicionar package-lock ao seu repositório, você conta com uma camada extra de segurança. É possível usar valores de integridade SHA para confirmar que o que está sendo instalado é exatamente o que você esperava.

  • Adicionar package-lock ao repositório acelera o processo de instalação, pois o npm não precisa resolver os metadados dos pacotes instalados anteriormente.

O formato de package-lock.json é idêntico ao de npm-shrinkwrap.json. Se npm-shrinkwrap.json estiver presente, package-lock.json será completamente ignorado.

Estas são algumas das variáveis do arquivo package-lock.json:

  • name — package-lock.json define um bloqueio para um pacote específico; aqui, você informa o nome do pacote.

  • version — a versão que você quer bloquear.

  • lockfileVersion — versão do arquivo package-lock, começando em 1.

  • packageIntegrity — valor de integridade criado a partir de package.json. Pode ser gerado por módulos como ssri.

  • preserveSymlinks — indica se a instalação foi feita com a variável de ambiente NODE_PRESERVE_SYMLINKS.

  • dependencies — um mapeamento do nome do pacote para objetos de dependência. Os objetos de dependência têm as seguintes propriedades:

    • version — um identificador exclusivo deste pacote, que pode ser usado para buscar uma nova cópia dele. Consulte a documentação de package-lock para saber como especificar versões para diferentes fontes de pacotes.

    • bundled — indica se a dependência está incluída no pacote e, em caso afirmativo, se deve ser extraída do módulo pai em vez de instalada como uma dependência separada.

    • dev — indica se essa dependência é uma dependência de desenvolvimento ou uma dependência transitiva de uma delas.

    • Outras propriedades são integrity, resolved, optional e dependencies (subdependências desta dependência).

Acesse a documentação de package-lock.json para saber mais e confira o post de Jiří Pospíšil‘s com boas práticas para bloquear arquivos no npm 5.

Como bloquear dependências com yarn.lock

O Yarn já vem com bloqueio de dependências integrado, como no Ruby. Para garantir instalações consistentes em diferentes máquinas, o Yarn precisa saber exatamente quais versões de cada dependência foram instaladas.

O Yarn fornece um arquivo yarn.lock gerado automaticamente, localizado na raiz do projeto. Assim como package-lock do npm, yarn.lock deve ser incluído no controle de versão.

O arquivo é parecido com este (exemplo retirado da documentação do yarn.Lock).

       # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 
       # yarn lockfile v1 
       package-1@^1.0.0: 
           version "1.0.3" 
           resolved "https://registry.npmjs.org/package-1/-/package-1-1.0.3.tgz#a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" 
       package-2@^2.0.0: 
           version "2.0.1" 
           resolved "https://registry.npmjs.org/package-2/-/package-2-2.0.1.tgz#a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" 
           dependencies: 
               package-4 "^4.0.0" 
       ...

Quando você adiciona, atualiza ou remove dependências, o Yarn atualiza automaticamente o arquivo yarn.lock. Você não deve editá-lo diretamente. O Yarn considera apenas o arquivo yarn.lock no nível superior e ignora os arquivos yarn.lock dentro das dependências. O arquivo de nível superior inclui tudo o que é necessário para bloquear as versões de todos os pacotes na árvore de dependências.

Para saber mais, consulte a documentação do yarn.lock ou este guia detalhado da Pluralsight, que explica o Yarn, incluindo o recurso yarn.lock.

Conclusão

As dependências são muito importantes. Para evitar surpresas — e porque cada vez que você instala um pacote pode receber versões diferentes das dependências aninhadas —, a maioria dos desenvolvedores front-end bloqueia ou fixa suas dependências. Os usuários do npm ficaram para trás, mas isso mudou com o novo recurso package-lock do npm 5.

Agora há pelo menos duas maneiras práticas de bloquear pacotes no npm: o arquivo package-lock.json do npm 5, que substitui o shrinkwrap como uma solução mais elegante, e o mecanismo integrado de bloqueio de dependências do Yarn, conhecido como yarn.lock.

Nossa recomendação: bloqueie as dependências, mas não deixe de acompanhá-las. Configure um processo para monitorar continuamente as vulnerabilidades nas suas dependências usando uma ferramenta como a Snyk. De tempos em tempos, revise também sua árvore de dependências e atualize ativamente as que estiverem desatualizadas, para garantir a máxima proteção contra falhas e vulnerabilidades futuras.