A maioria das organizações instala pacotes de dezenas de sítios: cada portátil, cada runner, cada Dockerfile, todos a falar diretamente com um registry público. Isso torna as políticas impossíveis, porque não há onde as pôr.
A solução é um único ponto de controlo. Todas as instalações passam por ele, e ele aplica algumas regras simples. Este guia cobre as quatro que fazem a maior parte do trabalho.
Passo 1: um único proxy de registry
Corra um proxy com cache para cada ecossistema que usa e aponte todos os clientes para ele. Vários repository managers open source e self-hosted conseguem fazer isto. O proxy vai buscar ao registry público no primeiro pedido, guarda o resultado em cache e serve os seus pacotes internos a partir do mesmo endereço.
# .npmrc (repo root, committed)
registry=https://packages.internal.example/npm/
# pip.conf
[global]
index-url = https://packages.internal.example/pypi/simple
Depois torne-o o único caminho. Bloqueie o egress direto dos runners de CI para registries públicos, para que um Dockerfile esquecido falhe de forma ruidosa em vez de contornar o ponto de controlo em silêncio. Nos portáteis, distribua a configuração pela gestão de dispositivos ou por um script de bootstrap.
O proxy também lhe dá algo que raramente tem hoje: um log de cada pacote e versão que a sua organização realmente puxa. É esse log que transforma o próximo advisory público de uma correria numa pesquisa.
Passo 2: fazer as novas versões esperar
As versões maliciosas tendem a ser detetadas e retiradas depressa. Um cooldown, por vezes chamado idade mínima de release, significa que uma versão tem de estar pública há alguns dias antes de os seus builds a aceitarem. Perde quase nada, porque muito poucas correções são tão urgentes que alguns dias façam diferença, e essas podem ser permitidas à mão.
Pode aplicá-lo no proxy, no gestor de pacotes ou no bot de atualizações:
# pnpm-workspace.yaml (value in minutes: 3 days)
minimumReleaseAge: 4320
# .github/dependabot.yml
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 5
O Renovate tem uma definição equivalente, minimumReleaseAge. Escolha um sítio para a impor e documente o processo de exceção, para que as pessoas não a contornem quando estão com pressa.
Passo 3: bloquear scripts de instalação
Os scripts de instalação são onde a maioria dos pacotes maliciosos faz o seu trabalho. Desative-os por omissão e permita a pequena lista de pacotes que realmente compilam alguma coisa.
# npm: .npmrc
ignore-scripts=true
# pnpm: package.json (only these may run build scripts)
{
"pnpm": {
"onlyBuiltDependencies": ["esbuild", "sharp"]
}
}
As versões recentes do pnpm já recusam build scripts de dependências, a menos que estejam numa allowlist. Com o npm, ignore-scripts também impede que os scripts do seu próprio projeto corram na instalação, por isso teste primeiro num repositório e corra explicitamente os poucos builds de que precisa. Em Python, prefira wheels a source distributions sempre que possível; pip install --only-binary :all: recusa tudo o que correria setup.py.
Passo 4: o lockfile decide
Um gate só é útil se o resolver não puder escolher discretamente algo novo por trás dele. Faça commit dos lockfiles e use os comandos de instalação que se recusam a alterá-los:
npm ci # fails if package-lock.json is out of sync
pnpm install --frozen-lockfile
pip install --require-hashes -r requirements.txt
Requirements com hashes fixados significam que, mesmo que uma versão seja substituída no registry, a instalação falha em vez de aceitar bytes diferentes. Trate as alterações ao lockfile como código: devem aparecer num pull request, e diffs grandes sem explicação merecem uma pergunta.
Implementação
Faça-o por esta ordem, porque cada passo torna o seguinte mais seguro:
- Ponha o proxy a funcionar e aponte o CI para ele. Acompanhe os logs durante uma semana para perceber o que realmente puxa.
- Aponte os portáteis para ele e depois bloqueie o egress direto para o registry a partir dos runners.
- Adicione o cooldown numa camada, com uma forma escrita de pedir uma exceção.
- Desligue os scripts de instalação num repositório, corrija o que partir e depois torne-o o padrão.
- Passe todos os pipelines para instalações frozen ou com hash.
O resultado é aborrecido da melhor forma. Os pacotes novos continuam a chegar, mas por uma só porta, com uns dias de atraso, sem correr código à entrada e apenas nas versões exatas que alguém reviu.
O que fazer na segunda-feira
- Procure nos seus Dockerfiles e na configuração de CI URLs de registries públicos e
npm installsemci. - Verifique se algum repositório não tem lockfile nenhum.
- Liste os pacotes da sua aplicação principal que declaram install scripts e decida quais precisam mesmo deles.
- Escolha o dono do proxy. Um gate sem dono torna-se um gate que todos contornam.
Onde isto fica na linha
Na torre (em desenvolvimento):