Dependencias npm: cómo evaluar una librería antes de meterla en producción
En 2005, cuando administraba redes en un cyber café a los 16, aprendí algo que no estaba en ningún manual: cada cable que conectabas era deuda. Si el proveedor de ese cable desaparecía o cambiaba el conector, el problema era tuyo. No del proveedor, no del cliente. Tuyo. Hoy, cuando miro un package.json con 180 dependencias directas en un proyecto TypeScript, pienso exactamente lo mismo. Cada entrada en ese archivo es un cable que alguien va a tener que mantener. Y en la mayoría de los casos, ese alguien sos vos.
Mi tesis es directa: agregar una dependencia npm no es solo instalar código — es asumir su mantenimiento, su historial de CVEs, sus dependencias transitivas y el costo de salida cuando la librería quede abandonada. La pregunta no es "¿funciona?". La pregunta es "¿qué pasa cuando deje de funcionar en seis meses?".
Por qué evaluar dependencias npm es una decisión de mantenimiento, no solo de seguridad
La documentación oficial de npm define un paquete como "un archivo o directorio descrito por un package.json" (npm docs). Eso es todo lo que garantiza npm como plataforma: que el archivo existe y tiene metadatos. Nada sobre si el autor sigue activo, si tiene tests, si los tipos son correctos o si vas a poder actualizar en dos años sin romper la mitad del sistema.
Lo que la doc oficial no dice — y donde la gente se quema — es que un paquete publicado puede quedarse congelado en el tiempo. El autor puede no tener tiempo, puede abandonar el proyecto o puede simplemente no enterarse de un CVE relevante. Y en ese momento, la deuda es tuya.
Hay tres dimensiones que importan antes de instalar algo en un proyecto TypeScript con pnpm:
- Mantenimiento activo: ¿Cuándo fue el último commit? ¿Hay PRs abiertas sin respuesta hace meses? ¿Tiene releases en el último año?
-
Superficie de ataque y tipos: ¿El paquete tiene tipos propios (
@types/) o los genera? ¿Cuántas dependencias transitivas arrastra? - Costo de salida: Si mañana necesitás sacarlo, ¿cuánto código propio cambiás?
Cómo auditar una dependencia antes de pnpm add
La receta habitual es: buscar en npm, ver si tiene estrellas en GitHub, instalarlo y listo. El problema es que eso mide popularidad, no calidad ni longevidad. Popularidad y mantenimiento activo no son lo mismo.
Acá está el proceso que uso, paso a paso y reproducible:
1. Revisar el estado real del repositorio
Antes de instalar, abrí el repo en GitHub y mirá:
-
Último commit en
main: si tiene más de 12 meses sin actividad y no es una librería de utilidad estable (tipolodash), es una señal. - Issues abiertas: ¿Hay bugs sin respuesta desde hace meses? ¿CVEs mencionados y no parchados?
- CHANGELOG o releases: un proyecto serio tiene historial de versiones. Si no lo tiene, la superficie de riesgo sube.
2. Analizar las dependencias transitivas con pnpm why
# Instalá en un proyecto de prueba aislado
pnpm add <nombre-del-paquete>
# Mirá qué trajo consigo
pnpm why <nombre-del-paquete>
# O un árbol completo de dependencias
pnpm list --depth=3
Una dependencia que parece chica puede arrastrar 40 paquetes transitivos. Eso no es automáticamente malo, pero si dos de esos 40 tienen CVEs activos, el problema es tuyo aunque el código propio no los llame directamente.
3. Correr una auditoría de seguridad desde el inicio
# Auditoría básica con npm (funciona también en proyectos pnpm)
npm audit
# Para ver solo vulnerabilidades críticas y altas
npm audit --audit-level=high
# Si querés el JSON para procesarlo
npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical")'
npm audit usa la base de datos del npm Advisory Database para cruzar versiones instaladas contra CVEs conocidos. No es infalible — hay vulnerabilidades que aún no tienen advisory — pero es el piso mínimo razonable antes de comprometerse con una dependencia.
4. Verificar los tipos TypeScript
En un proyecto TypeScript, una dependencia sin tipos es fricción garantizada. Revisá:
# ¿El paquete tiene tipos propios?
cat node_modules/<paquete>/package.json | grep '"types"'
# ¿Hay @types/ disponibles?
npm info @types/<paquete>
Si el paquete no tiene tipos propios y los @types/ son mantenidos por la comunidad (no por el autor original), tenés dos fuentes distintas de desfasaje. Cuando el paquete actualiza y @types/ no, el compilador falla de formas que no son obvias.
5. Evaluar el costo de salida con una interfaz propia
Este es el paso que más se saltea. La pregunta no es solo "¿funciona hoy?" sino "¿cuánto código cambio si mañana lo saco?".
// Patrón que reduce el costo de salida:
// Wrapeá la dependencia detrás de una interfaz propia
// ❌ Usar la dependencia directamente en toda la codebase
import { parse } from 'alguna-lib-de-fechas'
const fecha = parse('2025-01-15')
// ✅ Abstraer detrás de un módulo propio
// lib/fechas.ts
import { parse as _parse } from 'alguna-lib-de-fechas'
export function parsearFecha(input: string): Date {
return _parse(input) // un solo punto de entrada
}
Si la librería está en 40 archivos distintos sin abstracción, sacarla cuesta una refactorización mayor. Si está en un módulo propio, sacarla cuesta un reemplazo de implementación interno.
Los errores más comunes al evaluar dependencias npm
Error 1: Confundir descargas semanales con estabilidad
Los números de descargas en npm incluyen mirrors automáticos, CIs y pipelines. Una librería con 2M de descargas semanales puede tener un mantenedor que no mergea PRs hace un año. Las descargas son lagging indicator de popularidad pasada, no garantía de soporte futuro.
Error 2: Ignorar las devDependencies en proyectos con build steps
Si una devDependency vulnerada está involucrada en el build (babel, webpack, esbuild, tsx), el código que genera puede estar comprometido. El campo devDependencies en package.json separa intención, no riesgo. Si pasa por el compilador, importa.
Error 3: No mirar el peerDependencies
# Mirá qué versiones de React/Node espera la lib
npm info <paquete> peerDependencies
Una librería que pide React 17 como peer en un proyecto React 19 puede funcionar, o puede generar bugs silenciosos de contexto duplicado. Los peer conflicts son uno de los costos ocultos más frecuentes en actualizaciones de stack.
Error 4: Asumir que un paquete pequeño es seguro
La superficie de ataque no es proporcional al tamaño. El incidente de event-stream en 2018 mostró que un paquete de utilidad pequeño, transferido a un mantenedor nuevo, puede convertirse en vector de ataque. Que sea pequeño no lo hace inofensivo. (Fuente: npm blog sobre el incidente)
Este tipo de riesgo conecta directamente con lo que escribí sobre OAuth Scope Creep: la superficie de ataque se acumula en los bordes, no en el centro.
Matriz de decisión: ¿agrego esta dependencia o no?
| Criterio | Verde (agregar) | Amarillo (evaluar más) | Rojo (evitar o wrappear) |
|---|---|---|---|
| Último release | < 6 meses | 6-18 meses | > 18 meses sin actividad |
| Tipos TypeScript | Incluidos en el paquete |
@types/ activos y alineados |
Sin tipos o @types/ desactualizados |
| CVEs activos | Ninguno | Bajos sin exploit público | Críticos o altos sin parche |
| Deps transitivas | < 10 | 10-40 | > 40 o con deps con CVEs |
| Costo de salida | Fácil de wrappear | Acoplamiento moderado | Invasivo en múltiples módulos |
| Mantenedor activo | Responde issues/PRs | Lento pero responde | Sin actividad visible |
Si una dependencia cae en "Rojo" en más de dos criterios, la pregunta correcta es: ¿realmente necesito esta abstracción o puedo implementar la lógica específica que necesito en 50 líneas propias?
Cuando trabajo con proyectos usando pnpm workspaces — como describí en el post sobre pnpm workspaces y CI en Railway — esta evaluación importa el doble: una dependencia problemática en un paquete compartido del monorepo la heredan todos los apps del workspace.
Lo que esta checklist no puede garantizar
Siendo honesto sobre los límites:
- No predice el abandono futuro: una librería con releases recientes puede quedar abandonada mañana. La checklist mide el estado actual, no el futuro.
-
npm auditno cubre todos los vectores: las vulnerabilidades de lógica de negocio, los supply chain attacks sofisticados y los CVEs no reportados no aparecen en la auditoría estándar. Es el piso, no el techo. - El costo de salida real solo se mide en práctica: estimar el costo de remover una dependencia es una heurística. Hasta que no lo hacés, es una proyección. Si el proyecto ya tiene la dependencia profundamente integrada, la evaluación retrospectiva es más costosa que la prospectiva.
- Las métricas de GitHub son indicadores, no pruebas: un repositorio archivado puede ser estable porque llegó a feature-complete. Un repo con muchos commits puede ser inestable por refactorizaciones constantes. El contexto importa.
FAQ: Evaluar dependencias npm en proyectos TypeScript
¿Cuántas dependencias directas es "demasiado" en un proyecto TypeScript?
No hay un número universal. Lo que sí es señal de alerta es tener más de 50-60 dependencias directas sin haber evaluado activamente cuáles son reemplazables por implementaciones propias. El criterio no es el conteo sino si cada entrada en dependencies tiene una razón clara que no pueda resolverse en menos de 100 líneas de código propio.
¿pnpm tiene ventajas de seguridad sobre npm o yarn para este tipo de auditoría?
pnpm tiene un modelo de almacenamiento distinto (content-addressable store) que evita duplicación y hace más predecible el árbol de dependencias. Pero para auditorías de CVEs, npm audit sigue siendo la herramienta estándar y funciona con cualquier lockfile. La ventaja de pnpm en este contexto es más de predictibilidad del árbol que de seguridad intrínseca.
¿Qué hago si una dependencia tiene un CVE pero no hay fix disponible?
Primero, evaluá si el CVE aplica a cómo la usás. Muchos CVEs tienen condiciones de explotación específicas que pueden no aplicar en el contexto propio. Si aplica, buscá un fork con el fix, reemplazá la dependencia o implementá la funcionalidad mínima necesaria vos mismo. Quedarse con la dependencia vulnerable y "anotarlo para después" es el camino más cómodo y el más costoso a mediano plazo.
¿Tiene sentido evaluar las devDependencies con el mismo rigor?
Menos rigor, pero no cero. Las herramientas de build, linters y compiladores que pasan por el pipeline de CI merecen revisión básica. Una devDependency que solo se usa en la máquina local tiene menos urgencia que una que participa en generar el artefacto que va a producción.
¿Cómo evalúo una dependencia cuando no tiene repositorio público visible?
Si un paquete npm no tiene un repositorio público linkado y tiene más de un par de meses de existencia, el criterio por defecto es no instalarlo en un proyecto serio. La ausencia de fuente pública no implica malicia, pero elimina la posibilidad de auditoría de código. Sin source visible, el análisis se limita a lo que el paquete declara en su package.json, que es información incompleta.
¿Cómo afecta esto al mantenimiento de un monorepo con múltiples apps?
Una dependencia problemática en un paquete shared/ del monorepo se propaga a todos los consumers automáticamente. Eso hace que la evaluación previa sea más importante, no menos. El costo de un CVE o una breaking change en una dependencia compartida se multiplica por la cantidad de apps del workspace. Vale la pena dedicar más tiempo a las dependencias de los paquetes compartidos que a las específicas de una sola app.
Mi postura y el próximo paso concreto
Agregar una dependencia npm es una decisión técnica con consecuencias que se extienden mucho más allá del sprint actual. No estoy diciendo que haya que evitar librerías — eso sería absurdo en un ecosistema donde la composición es el modelo. Estoy diciendo que la evaluación previa cuesta media hora y puede evitar semanas de deuda de mantenimiento.
Lo que no compro es la idea de que la popularidad de un paquete sea suficiente evidencia para instalarlo sin más análisis. Las estrellas en GitHub no pagan el costo de una migración cuando la librería queda sin soporte.
Lo que sí compro: implementar la lógica propia cuando la alternativa de dependencia arrastra 30 transitivas, no tiene tipos o tiene un mantenedor que no responde desde hace un año. En esos casos, 80 líneas propias bien testeadas son una inversión más honesta que delegar en un paquete que no podés controlar.
El próximo paso concreto: abrí el package.json del proyecto más activo en el que estés trabajando. Elegí las cinco dependencias que menos conocés en detalle. Corré pnpm why <paquete> en cada una y mirá el repositorio en GitHub. En al menos una vas a encontrar algo que merece una conversación sobre si sigue valiendo la pena.
Fuente original:
- npm package documentation: https://docs.npmjs.com/about-packages-and-modules
- npm blog — event-stream incident (2018): https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident
Este artículo fue publicado originalmente en juanchi.dev
Top comments (0)