Al momento de mantener proyectos con npm, yarn o pnpm, las vulnerabilidades suelen ser un aspecto ignorado en el ciclo de mantenimiento de software. Debido a esto, al momento de mitigar vulnerabilidades, acabamos en situaciones donde el software está plagado de vulnerabilidades y, en casos muy drásticos, puede volverse un refactor-hell debido al code-debt que esto causa.
Las impliaciones de tener vulnerabilidades son varias, entre ellas:
- Vulnerar usuarios de tu software, por medio de malware, troyanos o remote-code-execution.
- Filtraciones de datos.
- Pérdida de confianza de parte de usuarios e inversionistas.
- Compromiso de tu infraestructura, por ejemplo, backdoors, DNS hijacks, filtración de tokens, etc.
Todo esto puede ser evitado con buenas practicas de programacion y conocimiento de herramientas que nos incluye nuestros package-manager.
Dando un ejemplo práctico con UELI: https://github.com/oliverschwendener/ueli, que es un lanzador de teclas. Estos son los comandos que utilizaremos y están disponibles en todos los package managers.
- npm list: Provee una lista de los paquetes instalados en nuestro proyecto.
- npm audit: Provee una explicación detallada de las vulnerabilidades presentes en el proyecto.
- npm why <package>: Provee una visualización de qué está requiriendo este paquete.
- npm audit fix: Realiza las actualizaciones de seguridad que no contengan breaking-changes.
- npm audit fix –force: Realiza breaking-changes, asumiendo que tú, como desarrollador, implementarás los cambios necesarios para que el programa funcione.
Ejemplo práctico
Empezamos utilizando npm install para instalar las dependencias de UELI.
> npm install
added 597 packages, and audited 598 packages in 28s
109 packages are looking for funding
run `npm fund` for details
2 high severity vulnerabilities
To address all issues, run:
npm audit fix
Run `npm audit` for details.
Inmediatamente observamos que se nos reportan 2 vulnerabilidades de serveridad high.
Las vulnerabilidades se clasifican en Critical > High > Medium > Low. Una vulnerabilidad crítica usualmente implica remote-code-execution, CISA KEV, infostealers, etc., y debe ser mitigada y resuelta en menos de 48 horas. Severidad high, suelen tener una índole similar, pero por lo general comprometen al servicio, como permitir DDoS. Medium y Low suelen darse plazos de 1-2 meses para solucionarlos, ya que son más difíciles de explotar o bajo condiciones muy específicas.
Ahora utilizaremos npm audit para ver de qué forma nos están vulnerando.
> npm audit
# npm audit report
react-router 7.0.0 - 7.14.2
Severity: high
React Router vulnerable to DoS via unbounded path expansion in __manifest endpoint - https://github.com/advisories/GHSA-8x6r-g9mw-2r78
fix available via `npm audit fix`
Vemos que tenemos un caso de Denial of Service (DoS) que es típico de una vulnerabilidad de severidad alta. Y se nos indica que puede ser resuelta de forma automática utilizando npm audit fix.
Antes de eso, utilicemos npm why react-router-dom, para ver el origen de este paquete. Recomiendo hacer esto, ya que en proyectos de muchos años es posible que estemos acarreando paquetes que no utilizamos ya.
> npm why react-router-dom
[email protected]
node_modules/react-router-dom
react-router-dom@"^7.12.0" from the root project
Obviamente, esto es parte del core de React y tenemos que conservarlo. Entonces vamos a resolver la vulnerabilidad utilizando npm audit fix.
> npm audit fix
changed 2 packages, and audited 598 packages in 3s
109 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Con esto hemos resuelto las vulnerabilidades en UELI. 🥳
Después de este proceso, recuerda revisar tu build y correr tus tests para verificar que todo esté en orden.
Build:
> npm run build
> [email protected] build
> vite build
vite v8.0.10 building client environment for production...
✓ 2340 modules transformed.
computing gzip size...
dist-renderer/settings.html 1.17 kB │ gzip: 0.49 kB
dist-renderer/search.html 1.19 kB │ gzip: 0.50 kB
dist-renderer/assets/search-9LK6AzB7.js 53.03 kB │ gzip: 18.81 kB
dist-renderer/assets/settings-Qma3fLX6.js 119.58 kB │ gzip: 30.70 kB
dist-renderer/assets/client-Fp6Dr0P0.js 1,006.29 kB │ gzip: 279.51 kB
✓ built in 441ms
vite v8.0.10 building client environment for production...
✓ 515 modules transformed.
computing gzip size...
dist-main/createEmptyInstantSearchResult-BXd1XI1b.js 0.65 kB │ gzip: 0.37 kB
dist-main/index.js 2.92 kB │ gzip: 0.94 kB
dist-main/Core-DQyQ8j0k.js 76.06 kB │ gzip: 18.80 kB
dist-main/Extensions-BbrEHj29.js 157.96 kB │ gzip: 38.01 kB
✓ built in 68ms
vite v8.0.10 building client environment for production...
✓ 2 modules transformed.
computing gzip size...
dist-preload/index.js 3.20 kB │ gzip: 0.77 kB
✓ built in 12ms
Tests:
> npm run test
> [email protected] test
> vitest run
RUN v4.1.5 /home/isaac/repositories/ueli/srcTest Files 149 passed (149)
Tests 697 passed (697)
Start at 20:26:11
Duration 2.37s (transform 19.98s, setup 0ms, import 27.96s, tests 1.23s, environment 11ms)
Dependabot
Cabe mencionar que una herramienta para estar al día es Dependabot, que automáticamente provee de PRs en tus repositorios que mitigan vulnerabilidades que pueden ser resueltas sin cambios al código. El repositorio de UELI tiene Dependabot implementado y se refleja en el bajo número de vulnerabilidades que encontramos; ¡por lo general, los proyectos pueden llegar a tener cientos de vulnerabilidades!
Revisa los PRs para verlo en acción: https://github.com/oliverschwendener/ueli/pull/1523 (Aquí se está solucionando la vulnerabilidad del artículo).
Aprende más sobre Dependabot aquí: https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/dependabot-quickstart
Glosario
- code-debt: Es el costo futuro oculto de rehacer el trabajo debido a la elección de soluciones de software rápidas a corto plazo en lugar de las óptimas a largo plazo. Al igual que la deuda financiera, acumula “intereses”, haciendo que el desarrollo futuro sea más lento y costoso si los atajos no se corrigen con el tiempo.
- refactor: Es el proceso de reestructurar el código existente para mejorar su diseño interno y legibilidad sin alterar su comportamiento externo. Es la herramienta clave para pagar los “intereses” de la deuda técnica, transformando soluciones improvisadas en código limpio, mantenible y preparado para el futuro.
- breaking-changes: Cambios en dependencias que requieren intervencion del desarollador. Por ejemplo cambio en interfaces y parametros de funciones que son parte del paquete.
Dejar un comentario