Como evitar publicar os arquivos errados no npm
Evite publicar arquivos ou segredos indesejados no npm. Entenda como .npmignore, .gitignore e files no package.json interagem e confira com npm pack --dry-run.
Se o seu pacote tem um arquivo .npmignore, o npm deixa de ler o .gitignore na hora de decidir o que excluir do tarball. Isso vale mesmo quando o .npmignore está vazio.
A maioria das pessoas descobre isso quando um tarball aparece muito maior do que o esperado, ou quando um dotfile que nunca deveria ter sido publicado surge no registry. As regras de ignore pareciam corretas, e de fato estavam, até alguém adicionar um segundo arquivo de ignore.
Este artigo aborda as armadilhas que colocam os arquivos errados no seu pacote: o fallback para o .gitignore, a forma como files e .npmignore interagem, as listas fixas de inclusão e exclusão do npm e o que fazer quando uma versão problemática já foi publicada. Ele pressupõe que você já leu How to Create and Publish an npm Package.
Principais conclusões
- Um arquivo
.npmignore, mesmo vazio, desativa todas as regras do.gitignoreque o npm aplicaria ao seu tarball. - O npm lê
.npmignoree.gitignoreem todos os subdiretórios, então um arquivo de ignore escondido no fundo da árvore pode alterar o que é publicado. - As exclusões nativas do npm não cobrem arquivos
.env, chaves privadas nem certificados. - Se um segredo chegar ao registry, faça a rotação dele primeiro. Despublicar não recupera as cópias que já foram baixadas.
- A proveniência registra qual repositório, commit e workflow geraram uma versão. Ela não prova que o código é seguro.
A armadilha do npmignore: um único arquivo novo descarta todas as regras do gitignore
O npm usa o .npmignore ou o .gitignore para decidir o que excluir de um pacote, nunca os dois. As regras do npm para manter arquivos fora de um pacote são curtas. Sem .npmignore, o npm aplica o .gitignore. Assim que existe qualquer .npmignore, o .gitignore deixa de valer. Os dois arquivos nunca são combinados.
O gatilho mais comum é a saída do build. O dist/ está no .gitignore, então o npm o deixa fora do tarball. Você adiciona um .npmignore para que o dist/ seja publicado. Agora o dist/ é publicado, e junto com ele tudo o que o .gitignore escondia: .env.local, coverage/ e test/fixtures/.
A pasta raiz não é o único lugar onde o npm procura. Ele considera os dois tipos de arquivo de ignore em qualquer pasta dentro do pacote, então um .npmignore perdido em uma pasta aninhada pode mudar o que é publicado sem que ninguém mexa na configuração da raiz.
Antes e depois: a lista de arquivos muda
O script abaixo cria um pacote em que o .gitignore faz toda a filtragem:
mkdir pkg-trap && cd pkg-trap
npm init -y >/dev/null
mkdir -p src dist test/fixtures coverage
echo 'module.exports = 1' > src/index.js
echo 'module.exports = 1' > dist/index.js
echo 'API_KEY=replace-me' > .env.local
echo '{}' > test/fixtures/big.json
echo 'x' > coverage/lcov.info
printf 'dist/\n.env.local\ncoverage/\ntest/fixtures/\n' > .gitignore
npm pack --dry-run
O npm lista o seguinte (apenas os caminhos, resumido):
package.json
src/index.js
O dist/ está faltando, então você adiciona um .npmignore de uma linha para publicar o build e descartar o código-fonte:
echo 'src/' > .npmignore
npm pack --dry-run
package.json
- src/index.js
+ dist/index.js
+ .env.local
+ coverage/lcov.info
+ test/fixtures/big.json
A correção é copiar para o .npmignore todas as regras de que você ainda precisa:
printf 'src/\n.env*\ncoverage/\ntest/\n*.map\n' > .npmignore
files vs. .npmignore: allow-list ou deny-list
O campo files do package.json é uma allow-list, e o .npmignore é uma deny-list. Se você usar os dois, nenhum arquivo isolado informa o que o seu pacote contém.
| Mecanismo | Modelo | Um arquivo novo que você esqueceu |
|---|---|---|
files | Allow-list | Fica de fora |
.npmignore | Deny-list | É publicado |
.gitignore (fallback) | Deny-list | É publicado, a menos que também esteja no .gitignore |
A sobreposição entre files e .npmignore é onde os problemas começam. De acordo com a documentação do package.json, um .npmignore na raiz do pacote não consegue remover nada que o files tenha adicionado. Já um .npmignore dentro de uma subpasta consegue:
{ "name": "my-lib", "version": "1.0.0", "main": "dist/index.js", "files": ["dist"] }
# dist/.npmignore
*.map
Aqui o files inclui o dist/, e o arquivo de ignore aninhado filtra os sourcemaps dele. Agora são dois arquivos definindo uma única lista. Ferramentas de build que geram suas próprias pastas de saída agravam o problema, então confira o que acaba no dist/ se você seguir nosso guia de pnpm. A regra mais simples é escolher um mecanismo e apagar o outro.
O que o npm sempre inclui e sempre exclui
Alguns arquivos são incluídos ou excluídos independentemente da sua configuração. Segundo a documentação do package.json, o npm sempre inclui o package.json, o README, o LICENSE ou LICENCE, o arquivo indicado em main e todos os arquivos indicados em bin. O npm sempre exclui pastas de controle de versão, .npmrc, node_modules e arquivos temporários de editores. Segredos não estão nessa lista.
| Sempre incluídos | Sempre excluídos (lista não exaustiva) | Não excluídos a menos que você especifique |
|---|---|---|
package.json, README (e variantes), LICENSE / LICENCE, o arquivo de main, o(s) arquivo(s) de bin | .git, .npmrc, node_modules (exceto dependências empacotadas), .DS_Store, npm-debug.log, config.gypi, .*.swp, *.orig, package-lock.json | .env, .env.local, *.pem, *.key, fixtures, coverage |
Adicione uma proteção para a coluna da direita:
npm pack --dry-run 2>&1 | grep -E '\.env|\.pem$|\.key$' && { echo "STOP: secrets in tarball"; exit 1; }
Execute npm pack —dry-run antes de toda publicação
O npm pack --dry-run exibe a lista exata de arquivos que o npm publicaria, sem gerar um tarball, então ele detecta todas as armadilhas acima. Execute-o depois de alterar qualquer arquivo de ignore, o campo files ou a configuração do build, e coloque-o no CI antes do npm publish. O fluxo básico é abordado em How to Create and Publish an npm Package.
npm pack --dry-run
O que fazer se você já publicou os arquivos errados
Se um segredo chegar ao registry, faça a rotação dele primeiro e considere-o público para sempre. Despublicar uma versão não remove as cópias que já foram baixadas ou espelhadas. Depois disso, faça a limpeza no registry:
- Faça a rotação da credencial junto ao provedor.
- Remova ou sinalize a versão problemática. Segundo a política de unpublish do npm, você pode despublicar em até 72 horas após a publicação, desde que nenhum outro pacote público dependa dela. Após esse prazo, apenas pacotes que atendam aos critérios da política podem ser despublicados. Caso contrário, use o
npm deprecate. - Publique um patch limpo. Um número de versão já utilizado nunca pode ser reutilizado, mesmo depois de despublicado.
# Within 72 hours, subject to npm's unpublish policy
npm unpublish my-lib@1.4.2
# Otherwise
npm deprecate my-lib@1.4.2 "Contains a leaked file. Upgrade to 1.4.3."
# Fix the ignore rules, confirm, republish
npm pack --dry-run
npm version patch
npm publish
A proveniência comprova a origem, não a segurança
Uma declaração de proveniência do npm registra qual repositório, commit e workflow de CI geraram uma versão do pacote. Ela não mostra que o código daquele commit é seguro, nem que o tarball contém apenas os arquivos que você pretendia. No GitHub Actions, o workflow precisa de id-token: write:
name: publish
on:
release:
types: [published]
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: https://registry.npmjs.org
- run: npm ci
- run: npm pack --dry-run
- run: npm publish
Este workflow pressupõe que você configurou o trusted publishing. O trusted publishing exige npm CLI 11.5.1 ou superior e Node 22.14.0 ou superior, e gera a proveniência automaticamente, então o job não precisa de --provenance nem de NODE_AUTH_TOKEN. Se, em vez disso, você publicar com um token, o npm publish --provenance exige npm 9.5.0 ou superior. Em ambos os casos, o campo repository do package.json deve corresponder exatamente ao repositório que faz a publicação (diferenciando maiúsculas de minúsculas), e o repositório precisa ser público. Se o npm ci falhar por conflitos de peer dependencies, consulte How to Fix npm ERESOLVE Errors. Para configurar 2FA e trusted publishing, consulte npm Security Best Practices.
Conclusão
As regras de ignore do npm não falham com um erro. Adicionar um único arquivo muda silenciosamente quais arquivos são publicados. Escolha entre files ou .npmignore e apague o outro, mantenha arquivos de ignore fora dos subdiretórios e coloque o npm pack --dry-run com um grep de segredos antes de todo npm publish no CI.
Perguntas frequentes
Qual é a diferença entre npm pack --dry-run e npm publish --dry-run?
Ambos informam quais arquivos iriam para o tarball sem enviar nada, então qualquer um deles detecta erros nos arquivos de ignore. O npm pack --dry-run é a verificação mais simples do conteúdo dos arquivos. O npm publish --dry-run simula o próprio comando de publicação. Nas versões atuais do npm, ele também consulta o registry para saber se aquela versão já existe e falha se existir, mas a ausência de login gera apenas um aviso. Uma execução bem-sucedida não prova que o upload vai funcionar, porque o login e o 2FA só são realmente testados no momento do upload.
Como verifico quais arquivos estão dentro de uma versão já publicada?
Execute o npm pack com uma especificação do registry, por exemplo npm pack my-lib@1.4.2. O npm baixa o tarball publicado, salva-o no diretório atual como my-lib-1.4.2.tgz e exibe os nomes dos arquivos. Liste o conteúdo com tar -tzf my-lib-1.4.2.tgz. Para obter a URL do tarball e usá-la com o curl ou outra ferramenta, execute npm view my-lib@1.4.2 dist.tarball.
O npm deprecate impede que as pessoas baixem uma versão com um arquivo vazado?
Não. O npm deprecate adiciona uma mensagem de aviso à entrada no registry, e o npm a exibe para quem instala aquela versão, mas o tarball continua disponível para download. Quem fixa a versão exata continua recebendo o arquivo vazado. Apenas o unpublish remove a versão do registry, e isso é limitado pela política de unpublish do npm. Para remover uma depreciação depois, execute o npm deprecate novamente com uma string vazia como mensagem.
O campo files do package.json aceita padrões glob?
Sim. Você pode listar nomes de arquivos simples, pastas ou padrões glob como * e **/*, e uma entrada de pasta inclui tudo o que estiver dentro dela. A sintaxe é a do .gitignore ao contrário: um padrão correspondente adiciona o arquivo ao tarball em vez de excluí-lo. Os arquivos que o npm sempre inclui, como package.json, o README, o LICENSE e o arquivo de main, são publicados independentemente de aparecerem no array.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k