Nota técnica

Escribes un comando y acaba en tu máquina código de 165 publishers. El 87 % no viene de nada que hayas nombrado.

892 lockfiles y 88,376 versiones: el proyecto mediano recibe código de 165 publishers, y el 87 % no está detrás de nada que haya nombrado.

· Actualizada el 1 de octubre de 2026 · 13 min de lectura
Qué cambió, en el registro de correcciones de norte-labs: instruction-gap, mcp-install y agent-plugins.

En dos semanas de septiembre medí tres cosas sobre el mismo acto, instalar un paquete y arrancarlo. Las cuento juntas porque miran ese acto desde sus dos extremos. La primera dice cuánto confía un desarrollador cuando escribe npm install: cuántas identidades distintas publican lo que su lockfile resuelve. Las otras dos miran el mismo acto desde el otro lado, en dos poblaciones distintas que nadie había observado: qué hace un paquete, un servidor MCP del registro oficial o un plugin de un asistente de código, mientras se instala y arranca. No son los mismos paquetes ni los mismos publishers. Es el mismo mecanismo visto desde el otro extremo. Todo está publicado, con el registro de cada proyecto y de cada paquete, en github.com/nortesoftware/norte-labs.

La pregunta de la primera medición era simple. Un desarrollador escribe un comando con un package.json que declara unas cuantas dependencias. ¿Cuántas decisiones toma el gestor por él, y de cuántas cuentas distintas acaba habiendo código en su máquina sin que él las eligiera? Nadie había publicado el número de identidades publicadoras por proyecto sobre una muestra declarada de proyectos reales. Por paquete sí: Zimmermann y otros midieron en 2019 que un paquete npm medio confía implícitamente en 39 mantenedores. Por manifiestos construidos a mano también: en agosto de 2026 dep-weight contó las cuentas que publicaron lo que resuelven 14 de ellos. Lo más cercano sobre proyectos reales son cinco y trece proyectos de conveniencia de sus propios autores. Sobre una muestra declarada, con lockfile, junto a lo que el manifiesto declara, no.

Tomé como marco todos los repositorios de GitHub en JavaScript o TypeScript con al menos 100 estrellas y un push en los últimos doce meses, sin forks: 38,791 el 17 de septiembre de 2026. Saqué 1,200 al azar con una semilla publicada. 1,018 tenían package.json en la raíz, 898 de esos guardaban un lockfile en el repositorio, 892 se pudieron leer. Leí los lockfiles, nada más: ni instalé, ni descargué un solo paquete. Para cada versión resuelta pedí al registro de npm su manifiesto, que dice qué cuenta la publicó, qué mantenedores tenía al publicarse, si declara scripts de instalación y de dónde viene su tarball. 88,376 versiones distintas.

El proyecto mediano declara 26 dependencias directas [13–56]. Su lockfile resuelve 604 versiones de paquete [293–1,074]. Detrás de esas versiones hay 165 publishers [94–259]: 5.3 por cada dependencia pedida. Entre corchetes va la mitad central de los proyectos; en los porcentajes, más abajo, el intervalo del 95 %. Cuentas que figuraban como mantenedores cuando se publicó cada versión: 401. De los publishers del proyecto mediano, el 87 % no está detrás de ningún paquete que el desarrollador nombró; en la mediana de cada cifra, 18 publishers están detrás de lo que pidió y 143 detrás de lo que no pidió. En el proyecto mediano, el gestor eligió, dentro de los rangos que el desarrollador escribió, la versión del 99.9 % de lo que instaló; solo el 3.1 % [2.2–4.5] de los proyectos fija exactas todas sus directas, y fijar una directa no decide nada por debajo de ella.

Publisher no es persona. Es una cuenta de npm o, en las versiones publicadas por trusted publishing, el repositorio de GitHub que nombra el certificado de procedencia. Quité las cuentas cuyo nombre dice automatización y quedan 130 publishers, 4.3 por dependencia directa [2.7–6.4]. El filtro es un patrón sobre el nombre de la cuenta: termina en bot o en publisher, o en ci como palabra aparte; empieza por bot- o ci-; contiene -bot-, robot, automation, release-bot, releasebot o deploy; es exactamente types, npm o GitHub Actions; o es un repositorio de trusted publishing. Está publicado tal cual en measurements/instruction-gap/src/compute.ts. Una persona puede tener varias cuentas, así que 130 es una cota superior de personas. Y una cuenta bot es el token de alguien, así que 165 es lo que el registro sabe, no cuántas personas hay.

Hay 79 publishers que están en la mitad o más de los 892 proyectos, y son un tercio de los publishers de un proyecto mediano. Cuatro de ellos son cuentas de npm que están en nueve árboles de cada diez: types, isaacs, sindresorhus, qix. La cuenta qix es la que fue suplantada por phishing en septiembre de 2025 para publicar versiones maliciosas de chalk, debug y otros 16 paquetes, con más de dos mil millones de descargas semanales entre todos. Aquel incidente fue un compromiso de exactamente esa posición, y un año después la cuenta sigue en nueve árboles de cada diez.

El 87.6 % de los proyectos [85.2–89.6] resuelve al menos un paquete que declara código para ejecutarse en la instalación: preinstall, install, postinstall o un binding.gyp que npm compila. En el proyecto mediano son 3 paquetes de 3 publishers y ninguno es una dependencia directa; en 409 de los 781 proyectos con ese código, nada de él está detrás de una directa. En el 85.3 % de los proyectos [82.8–87.5] al menos parte de ese código viene de un paquete que el desarrollador no nombró. Cuántos proyectos alcanza cada uno de esos paquetes, y cuánto queda sin fsevents, que solo se instala en macOS, no lo medí.

Aquí entra un matiz que cambió este año. npm 12, del 8 de julio de 2026, bloquea por defecto los scripts de instalación de las dependencias y las compilaciones implícitas de node-gyp, salvo que el manifiesto raíz los permita. pnpm 10 lo hace desde enero de 2025. Yarn Berry lo trae desactivado por defecto desde la 4.14, de abril de 2026. Bun solo ejecuta una lista propia de permitidos, 367 nombres el 17 de septiembre. Mi cifra cuenta código declarado para ejecutarse en la instalación. Si se ejecuta depende del gestor y de su versión: bajo yarn 1, Yarn Berry anterior a 4.14 y npm anterior a 12 se ejecuta todo; bajo los demás, solo lo permitido. Cuántos de los 892 proyectos dicen ellos mismos qué paquetes pueden ejecutar scripts al instalarse no lo medí.

Por defecto, solo los lockfiles de npm y yarn 1 registran de dónde viene cada paquete; son 572 proyectos. El 11.7 % de ellos [9.3–14.6] registra al menos un host que no es el registro de npm. En 24 aparece registry.npmmirror.com, el espejo chino que patrocina Alibaba Cloud, escrito en el lockfile por quien instaló con ese espejo configurado. npm solo reescribe registry.npmjs.org al registro configurado, así que un lockfile que registra el espejo hace que cada instalación posterior con npm anterior a 12 descargue de él.

Los proyectos no son 892 tiradas independientes. Dos proyectos sobre el mismo framework comparten buena parte del árbol. Agrupé cada proyecto por el framework que declara en producción, o en cualquier campo si no hay ninguno en producción, y calculé el efecto de diseño. Para la razón publishers por dependencia directa, la correlación dentro de cada framework es 0.081 y el efecto de diseño 11.4: la media es 7.0, con un intervalo de [6.5–7.6] si se suponen proyectos independientes y de [5.2–8.8] cuando se respeta el agrupamiento; la mediana, que es la cifra de arriba, es 5.3. Para los recuentos en bruto el efecto es mayor: 36.5 para los publishers y 33.5 para las versiones. Por eso las cifras citables son las medianas, no las medias. Cuánto cambia el árbol de un framework a otro no lo medí.

Este marco son repositorios populares de código abierto, no la población de proyectos en los que un desarrollador instala. Las librerías y los monorepos están sobrerrepresentados frente a las aplicaciones; el código privado no está; cada repositorio es un commit de un día; las listas de mantenedores son las que registró cada versión al publicarse, no las de hoy. Las dependencias de desarrollo están incluidas porque npm install las instala; para los lockfiles que distinguen las dependencias de desarrollo di también la cifra solo de producción: 5 directas, 53 versiones, 20 publishers en la mediana. PyPI no lo medí: su registro dice quién puede publicar un proyecto, no quién subió cada versión, y el método no sirve allí sin cambiar la pregunta.

Eso es la primera medición: cuántas identidades hay detrás de un comando. Las otras dos no miran a esos 892 proyectos ni a esos 165 publishers. Miran el mismo acto desde el otro extremo, en poblaciones elegidas porque nadie las había observado: qué hace un paquete mientras se instala y arranca.

A principios de septiembre intenté instalar 600 servidores MCP del registro oficial, una muestra aleatoria con semilla de los 11,484 que se distribuyen como paquete de npm o PyPI y arrancan por stdio, cada uno en un sandbox con un directorio home señuelo, bajo strace, y arranqué los 579 que se instalaron con el handshake del protocolo y diez segundos de espera. El 12.6 % de los árboles npm [9.7–16.2] ejecuta algún script de instalación; en 31 de esos 51 árboles es una dependencia que compila o baja un módulo nativo. Tres paquetes de 600 se registran solos en la configuración de un asistente de IA sin que nadie lo pida, uno de ellos desde el postinstall, editando el archivo de configuración de Claude Desktop. En el primer arranque, con credenciales falsas, el 8.1 % [6.2–10.6] contacta algún host. 21 servidores de PyPI consultan pypi.org porque la librería fastmcp comprueba actualizaciones por defecto, sin que el servidor lo decida. Cuatro mandan telemetría a PostHog, Google, Microsoft o un dominio propio, y ninguna de sus cuatro entradas en el registro lo menciona. Nueve leen el .env del home, con las llaves que haya. Y entre una quinta parte y un tercio de los servidores de PyPI no arrancan en una instalación limpia: el SDK oficial renombró una clase en su versión 2.0 y los paquetes rotos no fijan mcp por debajo de 2. Cuento los que no arrancan por el error de ese renombre, no cualquier fallo de importación que nombre mcp. Digo entre una quinta parte y un tercio porque 19 de los 52 rotos son de un solo publisher: son el 30 % de las instalaciones, el 21 % si se quita ese publisher, y por publisher tienen al menos uno roto 32 de 142, el 23 %; el intervalo que respeta el agrupamiento va del 14 % al 45 %. No es todo lo que rompió la versión 2.0: diez más se detienen en un decorador que esa versión quitó, así que al menos 62 de 175, el 35 %, no arrancan por ella. Los otros 48 que no arrancan no los examiné contra la 2.0. El campo status del registro los muestra como active porque active es su valor por defecto al publicar, no una afirmación de que funcionen.

Cinco días después apliqué el mismo método a las cuatro poblaciones enteras de plugins de los asistentes de código: los 42 agentes del registro ACP, los que Zed y JetBrains instalan; los 330 plugins del marketplace de Cursor; los 171 del marketplace de Devin; las 73 extensiones de Zed que proveen un servidor de contexto. 618 instalaciones, porque dos agentes se distribuyen de dos formas. Empecé por los agentes porque el registro ACP los instala y lanza en su propia CI y publica el resultado: de los 34 que su CI lanza y reporta, mi resultado coincidió con el suyo en 33, y el desacuerdo era un archivo tar que su extractor rechaza, no un agente. Lo que su CI no ve: 22 de los 42 agentes contactan un host antes del primer prompt, 8 mandan telemetría, 12 de los 23 paquetes npm ejecutan scripts de instalación, 5 leen un .env que encuentran subiendo desde el directorio de trabajo hasta el home y cargan sus claves en el entorno. Y uno desempaqueta un binario embebido en su paquete y lo ejecuta en el primer arranque: lee los identificadores de la placa base y la dirección MAC de la interfaz de red y envía un informe cifrado a un servidor de terceros, del que recibe un token de máquina, antes de cualquier inicio de sesión; ni la recogida ni la variable de entorno que la desactiva están documentadas. Ese hallazgo tiene una fecha de publicación comprometida con el fabricante, el 16 de diciembre de 2026, y hasta entonces no lo nombro aquí. En Cursor, cuya página de seguridad dice que todo plugin se revisa a mano y que "No binaries are shipped", ninguno de los 330 repositorios contiene un binario, y cuatro plugins descargan y ejecutan uno que el repositorio revisado no contiene: dos al arrancar la sesión, uno antes de cada prompt, uno antes de los comandos de shell que coinciden con un patrón; tres de los cuatro bajan la versión "latest" del día, uno por curl | sh, leído en sus scripts. La revisión, tal como está documentada, lee lo que se envía; lo que hace el script después queda fuera. Tres hallazgos salieron comunicados en cuatro envíos: Cursor y el fabricante del agente por correo, el mismo hallazgo del agente al registro ACP por informe privado, y a Zed por issue público, porque cinco de sus 73 extensiones declaran un repositorio que no es su código y el registro copia el campo sin comprobarlo.

Lo que une las tres mediciones es un acto leído desde los dos lados. Desde el lado del desarrollador, un comando son 165 publishers cuyo código acaba en su máquina, el 87 % detrás de nada que haya nombrado, y una lista corta de 79 que están en la mitad o más de los proyectos de la muestra. Desde el lado del código, en servidores MCP y en plugins de asistentes, cuando se mira lo que hacen al instalarse y arrancar, aparecen las comprobaciones de versión que nadie pidió, la telemetría que ninguna entrada del registro declara, el .env leído desde el home, el binario que baja en el primer arranque, la huella de hardware. Nada de esto es un hallazgo de malicia. Es lo que esas poblaciones hacen al instalarse y arrancar, medido igual para todas, y en casi todos los casos está en código abierto que cualquiera podía leer. Es la distancia entre lo que uno pidió y lo que llegó, medida con una muestra declarada y con los límites escritos al lado.