12k
All articles

Cómo evitar publicar archivos equivocados en npm

Evita publicar archivos o secretos no deseados en npm. Comprende cómo interactúan .npmignore, .gitignore y files en package.json, y verifica con npm pack --dry-run.

OpenReplay Team
OpenReplay Team
Cómo evitar publicar archivos equivocados en npm

Si tu paquete tiene un archivo .npmignore, npm deja de leer .gitignore al decidir qué excluir del tarball. Esto ocurre incluso cuando el archivo .npmignore está vacío.

La mayoría lo descubre cuando un tarball resulta mucho más grande de lo esperado, o cuando un dotfile que nunca pensaron publicar aparece en el registro. Las reglas de exclusión parecían correctas, y lo eran, hasta que alguien añadió un segundo archivo de exclusión.

Este artículo repasa las trampas que hacen que acaben archivos equivocados en tu paquete: el uso de .gitignore como respaldo, la interacción entre files y .npmignore, las listas fijas de inclusión y exclusión de npm, y qué hacer cuando ya hay una versión defectuosa publicada. Se da por hecho que has leído How to Create and Publish an npm Package.

Puntos clave

  • Un archivo .npmignore, aunque esté vacío, desactiva todas las reglas de .gitignore que npm aplicaría en otro caso a tu tarball.
  • npm lee .npmignore y .gitignore en todos los subdirectorios, así que un archivo de exclusión enterrado en el árbol puede cambiar lo que se publica.
  • Las exclusiones integradas de npm no cubren los archivos .env, las claves privadas ni los certificados.
  • Si un secreto llega al registro, lo primero es rotarlo. Despublicar no recupera las copias que ya se descargaron.
  • La procedencia (provenance) registra qué repositorio, commit y workflow generaron una versión. No demuestra que el código sea seguro.

La trampa de npmignore: un archivo nuevo anula todas las reglas de gitignore

npm usa .npmignore o .gitignore para decidir qué excluir de un paquete, nunca ambos. Las reglas de npm para excluir archivos de un paquete son breves. Si no hay .npmignore, npm aplica .gitignore. En cuanto existe cualquier .npmignore, .gitignore deja de tenerse en cuenta. Los dos archivos nunca se combinan.

El desencadenante habitual es la salida del build. dist/ está en .gitignore, así que npm lo deja fuera del tarball. Añades un .npmignore para que dist/ se publique. Ahora dist/ se publica, y también todo lo que .gitignore ocultaba antes: .env.local, coverage/ y test/fixtures/.

La carpeta raíz no es el único lugar donde busca npm. Detecta ambos tipos de archivo de exclusión en cualquier carpeta del paquete, de modo que un .npmignore olvidado en una carpeta anidada puede cambiar lo que se publica sin que nadie toque la configuración de la raíz.

Antes y después: la lista de archivos cambia

El siguiente script crea un paquete en el que .gitignore se encarga de todo el filtrado:

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

npm muestra lo siguiente (solo rutas, recortado):

package.json
src/index.js

Falta dist/, así que añades un .npmignore de una sola línea para publicar el build y excluir el código fuente:

echo 'src/' > .npmignore
npm pack --dry-run
  package.json
- src/index.js
+ dist/index.js
+ .env.local
+ coverage/lcov.info
+ test/fixtures/big.json

La solución es copiar en .npmignore todas las reglas que sigas necesitando:

printf 'src/\n.env*\ncoverage/\ntest/\n*.map\n' > .npmignore

files frente a .npmignore: lista de permitidos o lista de exclusión

El campo files de package.json es una lista de permitidos (allow-list) y .npmignore es una lista de exclusión (deny-list). Si usas ambos, ningún archivo por sí solo te dice qué contiene tu paquete.

MecanismoModeloUn archivo nuevo que olvidas
filesLista de permitidosQueda fuera
.npmignoreLista de exclusiónSe publica
.gitignore (respaldo)Lista de exclusiónSe publica, salvo que también esté en .gitignore

El solapamiento entre files y .npmignore es donde empiezan los problemas. Según la documentación de package.json, un .npmignore en la raíz del paquete no puede quitar nada de lo que files ha añadido. Un .npmignore dentro de una subcarpeta sí puede:

{ "name": "my-lib", "version": "1.0.0", "main": "dist/index.js", "files": ["dist"] }
# dist/.npmignore
*.map

Aquí files incluye dist/ y el archivo de exclusión anidado filtra los sourcemaps. Ahora son dos archivos los que definen una sola lista. Las herramientas de build que generan sus propias carpetas de salida empeoran la situación, así que revisa lo que acaba en dist/ si sigues nuestra guía de pnpm. La regla más sencilla es elegir un mecanismo y eliminar el otro.

Qué incluye y qué excluye npm siempre

Algunos archivos se incluyen o se excluyen independientemente de lo que configures. Según la documentación de package.json, npm siempre incluye package.json, el README, LICENSE o LICENCE, el archivo indicado en main y los archivos indicados en bin. npm siempre excluye las carpetas de control de versiones, .npmrc, node_modules y los archivos residuales de los editores. Los secretos no están en esa lista.

Siempre incluidosSiempre excluidos (lista no exhaustiva)No excluidos salvo que lo indiques
package.json, README (y variantes), LICENSE / LICENCE, el archivo de main, el archivo o archivos de bin.git, .npmrc, node_modules (salvo dependencias empaquetadas), .DS_Store, npm-debug.log, config.gypi, .*.swp, *.orig, package-lock.json.env, .env.local, *.pem, *.key, fixtures, coverage

Añade una comprobación para la columna de la derecha:

npm pack --dry-run 2>&1 | grep -E '\.env|\.pem$|\.key$' && { echo "STOP: secrets in tarball"; exit 1; }

Ejecuta npm pack —dry-run antes de cada publicación

npm pack --dry-run muestra la lista exacta de archivos que npm publicaría sin generar ningún tarball, por lo que detecta todas las trampas anteriores. Ejecútalo cada vez que cambies un archivo de exclusión, el campo files o la configuración del build, e inclúyelo en CI antes de npm publish. El flujo de trabajo básico se explica en How to Create and Publish an npm Package.

npm pack --dry-run

Qué hacer si ya publicaste archivos equivocados

Si un secreto llega al registro, rótalo primero y considéralo público para siempre. Despublicar una versión no elimina las copias que ya se descargaron o se replicaron en mirrors. Después, limpia el registro:

  1. Rota la credencial con su proveedor.
  2. Elimina o marca la versión defectuosa. Según la política de despublicación de npm, puedes despublicar en las 72 horas siguientes a la publicación, siempre que ningún otro paquete público dependa de ella. Pasado ese plazo, solo pueden despublicarse los paquetes que cumplan los criterios de la política. En caso contrario, usa npm deprecate.
  3. Publica un parche limpio. Un número de versión que ya se ha usado no puede reutilizarse nunca, ni siquiera después de despublicarlo.
# 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

La procedencia demuestra el origen, no la seguridad

Una declaración de procedencia (provenance) de npm registra qué repositorio, commit y workflow de CI generaron una versión del paquete. No demuestra que el código de ese commit sea seguro ni que el tarball contenga solo los archivos que pretendías. En GitHub Actions, el workflow necesita 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 da por hecho que has configurado la publicación de confianza (trusted publishing). La publicación de confianza requiere npm CLI 11.5.1 o posterior y Node 22.14.0 o posterior, y genera la procedencia automáticamente, por lo que el job no necesita ni --provenance ni NODE_AUTH_TOKEN. Si publicas con un token, npm publish --provenance requiere npm 9.5.0 o posterior. En ambos casos, el campo repository de package.json debe coincidir exactamente con el repositorio desde el que se publica (distingue mayúsculas de minúsculas), y el repositorio debe ser público. Si npm ci falla por conflictos de peer dependencies, consulta How to Fix npm ERESOLVE Errors. Para configurar la 2FA y la publicación de confianza, consulta npm Security Best Practices.

Conclusión

Las reglas de exclusión de npm no fallan con un error. Añadir un solo archivo cambia silenciosamente qué archivos se publican. Elige files o .npmignore y elimina el otro, mantén los archivos de exclusión fuera de los subdirectorios, y coloca npm pack --dry-run junto con un grep de secretos antes de cada npm publish en CI.

Preguntas frecuentes

¿Qué diferencia hay entre npm pack --dry-run y npm publish --dry-run?

Ambos indican qué archivos irían en el tarball sin subir nada, así que cualquiera de los dos detecta errores en los archivos de exclusión. npm pack --dry-run es la comprobación más sencilla del contenido. npm publish --dry-run ensaya el propio comando de publicación. En las versiones actuales de npm, además consulta al registro si esa versión ya existe y falla en ese caso, pero la falta de sesión iniciada solo genera una advertencia. Que la ejecución se complete sin errores no garantiza que la subida vaya a funcionar, porque la sesión y la 2FA solo se comprueban realmente en el momento de la subida.

¿Cómo compruebo qué archivos contiene una versión ya publicada?

Ejecuta npm pack con una especificación del registro, por ejemplo npm pack my-lib@1.4.2. npm descarga el tarball publicado, lo guarda en el directorio actual como my-lib-1.4.2.tgz y muestra los nombres de sus archivos. Lista el contenido con tar -tzf my-lib-1.4.2.tgz. Para obtener la URL del tarball y usarla con curl u otra herramienta, ejecuta npm view my-lib@1.4.2 dist.tarball.

¿npm deprecate impide que se descargue una versión con un archivo filtrado?

No. npm deprecate añade un mensaje de advertencia a la entrada del registro, y npm se lo muestra a cualquiera que instale esa versión, pero el tarball sigue pudiéndose descargar. Quien fije la versión exacta seguirá obteniendo el archivo filtrado. Solo la despublicación elimina la versión del registro, y está limitada por la política de despublicación de npm. Para retirar una marca de obsolescencia más adelante, vuelve a ejecutar npm deprecate con una cadena vacía como mensaje.

¿El campo files de package.json admite patrones glob?

Sí. Puedes indicar nombres de archivo, carpetas o patrones glob como * y **/*, y una entrada de carpeta incluye todo su contenido. La sintaxis es la de .gitignore a la inversa: un patrón que coincide añade el archivo al tarball en lugar de excluirlo. Los archivos que npm incluye siempre, como package.json, el README, el LICENSE y el archivo de main, se publican aparezcan o no en el array.

DevTools for the frontend

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

We use cookies to improve your experience. By using our site, you accept cookies.