Un equipo de desarrollo revisa archivos de configuración impresos y una pantalla con un árbol de dependencias en una sala de trabajo.
Volver al blog

El punto ciego de seguridad en configs

Config Files That Run Code expone un punto ciego de seguridad en configs que afecta a devs y equipos de plataforma: archivos que parecen inocentes, pero ejecutan lógica y amplían la cadena de suministro. Aquí ves dónde mirar, cómo detectarlo y cómo reducir el riesgo.

Hay un tipo de archivo que casi siempre pasa debajo del radar porque parece “solo configuración”. Lo abres, cambias una variable, guardas y sigues con tu día. El problema es que, en varios ecosistemas modernos, ese archivo no solo describe comportamiento: también puede ejecutar código, cargar plugins, disparar hooks o activar transformaciones que terminan en la cadena de suministro.

Ese es el punto ciego. Mucha gente revisa dependencias, contenedores, secretos y pipelines, pero deja fuera los configs que tienen capacidad de ejecución. Y cuando eso ocurre, el atacante no necesita tocar tu aplicación principal. Le basta con meter mano en un archivo que tu tooling ya confía, como package.json, .npmrc, .yarnrc.yml, .prettierrc, vite.config.ts, eslint.config.js, babel.config.js, jest.config.js o archivos de CI que interpretan scripts.

Qué significa que un config ejecute código

No todos los archivos de configuración son pasivos. Algunos son solo datos, como un YAML con parámetros. Otros son ejecutables por diseño: JavaScript en configs de build, scripts en package managers, hooks de linters, transformaciones de test runners o loaders que se evalúan durante la instalación o el arranque.

En la práctica, eso quiere decir que el config tiene dos roles al mismo tiempo. Por un lado define comportamiento. Por otro, puede introducir lógica arbitraria que se ejecuta con los permisos del proceso que lo lee. Si ese proceso corre en tu laptop, en tu CI o dentro del runner de un release, el alcance cambia mucho.

Un ejemplo sencillo: un archivo de configuración de ESLint en formato JavaScript puede importar módulos, leer variables de entorno y construir reglas dinámicamente. Eso es útil para monorepos y setups complejos. Pero también significa que, si alguien logra modificar ese archivo o una dependencia que él importa, puede ejecutar código cuando el linter arranca.

Dónde aparece el riesgo de verdad

El riesgo no está solo en el archivo en sí. Aparece cuando el archivo se evalúa automáticamente y confías en contenido que puede venir de fuera: una dependencia nueva, un plugin, una plantilla, una contribución de un tercero o una actualización aparentemente menor.

En seguridad de la cadena de suministro, ese detalle importa porque el atacante busca el camino de menor resistencia. A veces no ataca el binario final. Ataca el paso previo, donde un config ejecutable le da una puerta de entrada más silenciosa.

Configs que suelen sorprender

Estos son algunos casos típicos donde el equipo cree que está viendo “config” y en realidad está ejecutando lógica:

  • package.json con scripts, preinstall, postinstall o prepare
  • .yarnrc.yml o .npmrc con comportamiento que afecta instalación y resolución
  • vite.config.ts, webpack.config.js, next.config.js o rollup.config.js
  • eslint.config.js y configuraciones de linters en formato JS
  • jest.config.js, playwright.config.ts o cypress.config.ts
  • archivos de CI como workflows que invocan scripts del repo

No todos son inseguros por definición. El problema es asumir que, por llamarse config, solo contienen datos.

Cómo se amplía la superficie de ataque

Cuando un config ejecuta código, la superficie de ataque deja de ser solo la aplicación. También incluye el entorno donde ese config corre, las dependencias que importa y los pasos automáticos que lo disparan. Eso cambia el mapa de amenazas para devs y para plataforma.

Piensa en un pipeline que instala dependencias, ejecuta linters, corre tests y genera build. Si alguno de esos pasos carga un config ejecutable, el atacante puede buscar una modificación en una dependencia transitiva, un plugin de terceros o un archivo de configuración versionado. Ya no necesita un exploit sofisticado sobre tu app. Solo necesita que el proceso confíe en algo que no revisas con el mismo nivel de detalle.

La otra parte del problema es la repetición. Un config ejecutable suele correr muchas veces: en cada npm install, en cada commit, en cada job de CI. Eso le da al atacante más oportunidades y a ti menos margen para notar un comportamiento raro.

Tres zonas donde el riesgo se multiplica

  1. Instalación de dependencias: scripts como preinstall o postinstall pueden ejecutarse automáticamente durante el setup.
  2. Build y test: configs de bundlers, runners o linters pueden importar módulos y ejecutar lógica al iniciar.
  3. CI/CD: si el pipeline toma decisiones con archivos del repo, un cambio pequeño puede afectar releases, artefactos o secretos expuestos al job.

La documentación oficial de npm sobre scripts deja claro que varios lifecycle scripts pueden ejecutarse durante instalación y publicación, así que vale la pena revisarla si tu equipo depende de ese flujo: https://docs.npmjs.com/cli/v10/using-npm/scripts

Casos reales de por qué esto importa

La historia de la seguridad de software ya mostró varias veces que el problema no siempre está en el código de negocio. A veces está en el mecanismo de entrega, en el package manager o en un archivo que el ecosistema interpreta como instrucciones.

Un ejemplo cercano es el abuso de lifecycle scripts en entornos Node.js. No hace falta que te roben el código fuente completo para causar daño. Si un paquete o un config logra ejecutar algo durante instalación, puede leer variables de entorno, tocar archivos locales o preparar una segunda etapa. Por eso tantas guías modernas recomiendan reducir al mínimo los scripts automáticos en instalaciones reproducibles.

Otro caso común es el de herramientas de build que aceptan configuración en JavaScript o TypeScript. Eso da flexibilidad, pero también abre la puerta a ejecutar lógica antes de compilar. En equipos grandes, esa lógica suele crecer sin mucho control porque “solo resuelve un detalle del monorepo”. Con el tiempo, ese detalle se convierte en una dependencia crítica.

Ejemplo práctico en un repo Node.js

Imagina este escenario:

  • tu repo usa npm
  • el setup corre npm install en CI
  • existe un paquete interno o externo con postinstall
  • el pipeline tiene acceso a variables de entorno sensibles

Si ese paquete cambia o se compromete, el script puede ejecutarse en el momento más conveniente para el atacante: cuando el entorno ya tiene credenciales cargadas y todavía no generaste el artefacto final. No hace falta un payload complejo. A veces basta con exfiltrar un token o alterar un archivo de build.

La documentación oficial de GitHub sobre seguridad en Actions también insiste en limitar permisos y tratar el contenido del repositorio como no confiable en ciertos contextos: https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions

Cómo detectar configs que ejecutan código

No necesitas convertirte en analista forense para empezar a ver este riesgo. Sí necesitas una revisión más mecánica de tus archivos y de los puntos donde tu tooling interpreta código dentro de un config.

Primero, identifica qué archivos en tu stack no son simples datos. En muchos repos, el problema empieza con extensiones como .js, .ts, .mjs o .cjs usadas como configuración. Luego revisa qué importan esos archivos, qué scripts invocan y qué se ejecuta automáticamente en instalación, build o test.

Checklist de revisión rápida

  1. Busca configs en JavaScript o TypeScript.
  2. Revisa si importan dependencias externas o internas.
  3. Identifica hooks automáticos de instalación y publicación.
  4. Marca los pasos de CI que leen configs del repo.
  5. Verifica si hay acceso a secretos, tokens o credenciales en ese momento.
  6. Pregunta si ese archivo necesita lógica o si puede ser datos estáticos.

Si quieres profundizar en el comportamiento de npm en scripts y lifecycle hooks, la guía oficial sigue siendo la referencia base: https://docs.npmjs.com/cli/v10/using-npm/scripts

Señales de alerta que sí valen tu tiempo

Hay patrones que merecen una revisión inmediata:

  • configs que hacen import de paquetes poco conocidos
  • postinstall o prepare en dependencias que no esperabas
  • lógica condicional que depende de process.env
  • archivos de configuración que cambian seguido sin motivo funcional claro
  • uso de eval, Function, child_process o llamadas a shell dentro de configs

No se trata de prohibir todo. Se trata de saber qué piezas tienen capacidad de ejecución y tratarlas como código sensible, no como metadata inocente.

Qué puede hacer tu equipo para reducir el riesgo

La buena noticia es que este punto ciego se puede cerrar bastante con medidas concretas. No necesitas rediseñar toda tu plataforma desde cero. Sí necesitas poner límites claros a qué configs pueden ejecutar lógica y en qué entornos.

La primera medida es reducir la superficie. Si un archivo puede ser puro dato, que lo sea. Si necesitas comportamiento dinámico, encapsúlalo en un módulo pequeño y revisable. Cuanto menos lógica escondida haya en configs, más fácil es auditar cambios y detectar anomalías.

La segunda medida es controlar la ejecución automática. En entornos CI, usa instalaciones reproducibles y revisa si puedes desactivar scripts donde no sean necesarios. En Node.js, por ejemplo, muchas organizaciones limitan scripts de instalación en jobs que solo validan código o generan artefactos intermedios.

Medidas concretas para devs y plataforma

  • Mantén configs ejecutables en el mínimo número posible.
  • Separa datos estáticos de lógica dinámica.
  • Revisa cada postinstall, prepare o hook automático.
  • Ejecuta CI con permisos mínimos y secretos segmentados.
  • Bloquea dependencias no aprobadas o demasiado opacas.
  • Observa cambios en configs como si fueran cambios en código crítico.
  • Añade revisión obligatoria para archivos que puedan ejecutar lógica.

Tabla de impacto por tipo de config

Tipo de archivoPuede ejecutar códigoRiesgo típicoQué revisar primero
package.jsonSí, vía scriptsInstalación y publicaciónpreinstall, postinstall, prepare
vite.config.tsBuild y pluginsImports, plugins y acceso a entorno
eslint.config.jsLógica en lintingMódulos importados y condiciones dinámicas
jest.config.jsEjecución de testsTransformaciones, setup files y resolvers
.yarnrc.ymlA veces indirectoInstalación y resoluciónComportamiento del package manager
workflow.yml de CISí, vía comandosPipeline y secretosPasos shell, permisos y variables expuestas

Si tu organización usa monorepos, este control es todavía más importante. Un cambio pequeño en un config compartido puede afectar decenas de paquetes o apps al mismo tiempo.

Cómo lo aterrizamos en un equipo real

Supón que trabajas en un equipo de plataforma que mantiene plantillas para varios servicios. El objetivo no es eliminar toda flexibilidad, porque eso frena a desarrollo. El objetivo es definir una política simple: qué configs pueden ejecutar código, quién los aprueba y bajo qué condiciones corren.

Una forma práctica de hacerlo es clasificar archivos en tres grupos: datos puros, configs ejecutables controlados y excepciones. Los datos puros no deberían cargar módulos ni llamar procesos externos. Los configs ejecutables controlados pueden hacerlo, pero con revisión adicional. Las excepciones se documentan con fecha, dueño y motivo.

Luego, automatiza la detección. Un linter de políticas o un script de revisión puede alertarte si aparece un postinstall, un eval o un import inesperado en un archivo de configuración. No hace falta capturar todo al 100 por ciento desde el día uno. Con que evites los casos más obvios ya reduces bastante el riesgo.

Un enfoque operativo en 4 pasos

  1. Inventaria configs ejecutables en tus repos y templates.
  2. Marca cuáles corren en laptop, CI y release.
  3. Define una lista de patrones prohibidos o de revisión obligatoria.
  4. Agrega controles en PR y en pipeline para cambios sensibles.

Si tu stack depende mucho de Node.js, vale la pena revisar también cómo se comportan los package managers modernos y qué scripts activan por defecto. La combinación de dependencia + hook + secretos sigue siendo una de las rutas más rentables para un atacante.

Tabla resumen

Pregunta cortaRespuesta corta
¿Cuál es el punto ciego?Tratar como dato un archivo que también ejecuta lógica.
¿Dónde pega más?En instalación, build, test y CI/CD.
¿Qué archivo revisar primero?Los que usan JS o TS como config y los scripts de package.json.
¿Qué señal es más sospechosa?Hooks automáticos y imports de dependencias poco claras.
¿Qué control ayuda más?Reducir lógica en configs y limitar permisos en CI.
¿Qué gana plataforma?Menos superficie de ataque y cambios más fáciles de auditar.

La idea no es demonizar la configuración ejecutable. A veces es la forma correcta de resolver un problema complejo. Pero si no la miras como código sensible, dejas una puerta abierta justo donde menos revisas.

Preguntas frecuentes

¿Qué es un config file que ejecuta código?
Es un archivo que parece de configuración, pero puede importar módulos, llamar funciones o disparar scripts cuando una herramienta lo lee. En vez de limitarse a guardar datos, participa activamente en la ejecución del proceso.
¿Por qué esto es un riesgo de supply chain?
Porque el atacante no necesita tocar tu aplicación final. Le basta con comprometer una dependencia, un plugin o un archivo que tu tooling ya confía y que se ejecuta durante instalación, build o CI.
¿Qué archivos debo revisar primero en un repo Node.js?
Empieza por `package.json`, `vite.config.ts`, `eslint.config.js`, `jest.config.js`, `next.config.js` y cualquier archivo de CI que invoque scripts. Luego revisa dependencias con lifecycle hooks como `postinstall` o `prepare`.
¿Se puede evitar por completo el uso de configs ejecutables?
No siempre, porque algunos proyectos necesitan lógica dinámica para build, tests o monorepos. Lo que sí puedes hacer es limitar su uso, separar datos de lógica y revisar esos archivos con más rigor que un YAML estático.
¿Qué control técnico da más resultado rápido?
Reducir permisos en CI y limitar scripts automáticos durante instalación suele dar impacto rápido. Si además revisas imports y hooks en configs ejecutables, bajas bastante la exposición.
¿Esto afecta solo a JavaScript y Node.js?
No. Aunque Node.js es un caso muy visible, el patrón aparece en otros ecosistemas con configs dinámicos, plugins o loaders. La idea clave es la misma: si el config puede ejecutar lógica, trátalo como código.
¿Cómo explico este riesgo a un equipo que solo ve configs?
Muéstrales qué archivo se ejecuta, en qué momento y con qué permisos. Cuando ven que un simple cambio en un config puede correr dentro de CI con secretos disponibles, el problema deja de parecer teórico.

Azirgo

¿Listo para construir tu Producto Digital?

Sitios web, apps móviles, software a medida y soluciones blockchain. Cuéntanos qué tienes en mente y armamos un plan claro contigo.

  • Cotización clara en 48 horas
  • Equipo en Ecuador, atención en español
  • Desde un MVP hasta un producto en producción