El «repojacking» y por qué las rutas de los módulos de Go son objetivos de apropiación

Ya están disponibles los parches de php2shell (CVE-2026-109618 y CVE-2026-109619) para PHP en fin de vida útil. Dado que las respuestas del departamento de ventas están tardando más de lo habitual debido a la elevada demanda, la forma más rápida de obtener los paquetes parcheados es comprar ELS para Runtimes en línea (se abre en una pestaña nueva). Lee las preguntas frecuentes sobre los parches

Ir directamente al resumen generado por IA de esta entrada del blog

  • Resume esta entrada con Perplexity
  • Resume esta publicación con Gemini

Principales conclusiones

  • Go identifica un paquete por su ubicación, en lugar de por un nombre que pertenezca a alguien, por lo que un nombre de usuario de GitHub caducado puede convertirse por sí solo en un problema de la cadena de suministro.
  • La base de datos de sumas de comprobación garantiza que todo el mundo reciba el mismo código para una versión determinada, pero no que dicha versión proceda de alguien en quien confíes.
  • GitHub retira algunos espacios de nombres populares, pero las descargas que se realizan a través del espejo de módulos de Go no se registran como los clones que activan esa protección.
  • Las rutas que hay que revisar son las pequeñas y transitivas que nadie lee.

En diciembre de 2023, los investigadores de VulnCheck se dedicaron a buscar repositorios de Go en GitHub que se encontraran en un estado recuperable, y el recuento fue mayor de lo que la mayoría de la gente podría imaginar. Más de 15 000. Más de 9.000 quedaron expuestos debido a un cambio de nombre de usuario en GitHub, y otros más de 6 000, por la eliminación de cuentas. En conjunto, esos repositorios contienen más de 800 000 versiones de módulos de Go.

Para nada de eso se necesitan credenciales robadas ni un proceso de compilación comprometido. Solo hace falta que una cuenta caduque y que alguien se dé cuenta antes que el propietario original.

Esos datos ya tienen un par de años y nadie ha publicado otros más recientes. Sin embargo, el mecanismo en el que se basan no ha cambiado, y la razón por la que funciona en Go y prácticamente en ningún otro sitio se debe a la forma en que Go define qué es un paquete.

Por qué la identidad de un paquete «Go» es una ubicación en lugar de un nombre

La mayoría de los principales registros mantienen un registro de titularidad. npm sabe quién está autorizado a publicar axios. PyPI sabe quién puede subir una nueva versión de requests. Maven Central verifica un espacio de nombres antes de que nadie pueda reclamar un groupId bajo él.

En Go, una ruta hace las veces de dirección.

github.com/someuser/somelib no es un nombre registrado en un registro de Go. Se trata de una ruta de módulo que indica al entorno de desarrollo dónde buscar, y quien controla esa ubicación controla qué versiones se pueden servir desde ella a continuación. Cuando la cuenta que hay detrás desaparece y otra persona reclama el nombre de usuario, la ruta de importación de tu go.mod ahora apunta a su código.

Se trata de algo muy distinto a las campañas de suplantación de cuentas que han acaparado los titulares últimamente, como PolinRider. Esa campaña también llegó a los módulos de Go en 2026, pero el mecanismo no es el mismo. Aquellos atacantes se hicieron con el control de cuentas que aún estaban activas y, en varios casos, reescribieron las etiquetas de Git para ocultar lo que habían hecho. El «repojacking» es más silencioso y, en cierto modo, más sencillo, porque no es necesario irrumpir en ningún sitio. Una vez que la cuenta ha desaparecido, ya no queda nada que defender.

La resolución de módulos de Go es elegante, y vale la pena mencionarlo antes de entrar en lo que falla en ella. El comando «go get github.com/user/lib» se resuelve, por defecto, a través del proxy de módulos, registra una versión y escribe un hash. Sin necesidad de crear una cuenta, sin enviar datos al registro ni pasar por un proceso de publicación. Basta con enviar una etiqueta de versión con el nombre adecuado para que la versión exista, sin necesidad de ningún paso de publicación adicional.

Eso funciona porque la ruta de importación es la fuente de referencia. La ruta indica a la cadena de herramientas dónde buscar, y ningún otro sistema registra quién está autorizado a introducir código allí.

Compara qué información tiene realmente cada registro sobre un paquete.

Registro La identidad es Registro de titularidad Recuperable tras el abandono
npm Un nombre registrado Sí, vinculado a la cuenta No, los nombres siguen estando reservados
PyPI Un nombre registrado Sí, vinculado a la cuenta Solo mediante un proceso formal
Maven Central Un espacio de nombres vinculado a un dominio verificado o a un repositorio de origen Sí, espacio de nombres verificado Sí, si el dominio caduca
crates.io Un nombre registrado Sí, vinculado a la cuenta No, los nombres siguen estando reservados
Packagist Un nombre registrado Sí, vinculado a la cuenta No, los nombres siguen estando reservados
Módulos de Go Una sede de VCS Ninguno Sí, siempre que haya sitio disponible

Maven tiene su propia versión de este problema a través de los dominios caducados, tal y como describió el estudio «MavenGate» en enero de 2024. Los ID de grupo se derivan de los dominios, por lo que un dominio que caduca cede el espacio de nombres asociado a él.

Packagist también tiene un caso documentado. En un incidente ocurrido en 2022, un investigador se apropió del repositorio de GitHub asociado a un paquete de Packagist y consiguió que se publicara malware, ya que Packagist rastreaba GitHub en busca de versiones sin un paso de publicación independiente. Esa puerta en concreto se ha cerrado desde entonces, y tras el incidente de reescritura de etiquetas de Laravel-Lang en mayo de 2026, Packagist fue más allá y hizo que las versiones estables fueran inmutables, medida anunciada en mayo y activada en julio. No obstante, conviene dejar claro qué es lo que soluciona esto. Las versiones inmutables impiden que alguien cambie la referencia Git de una versión publicada. No hacen nada para evitar que alguien se apropie de un espacio de nombres abandonado y publique algo nuevo bajo él.

En el caso de Go, ese segundo escenario es la configuración predeterminada para cualquier módulo alojado en una cuenta personal de GitHub. El propio equipo de seguridad de GitHub señaló a Go como una de las tres formas en que las compilaciones pueden extraer código directamente de un repositorio en su publicación de 2024 sobre el «repojacking», junto con GitHub Actions y los submódulos de Git, y su recomendación para esas dos es fijar una confirmación específica. Go cuenta con una capa de integridad de la que ellos carecen, ya que las versiones de los módulos se autentican frente a go.sum y, por defecto, frente a la base de datos pública de sumas de comprobación. Y ahí es donde esto se vuelve más interesante de lo que parece a primera vista.

También existe una versión de este riesgo que se manifiesta de forma más gradual. Una gran parte de los componentes de código abierto que se encuentran en algún lugar del código de producción llevan años sin recibir ninguna actualización, y los proyectos inactivos son precisamente aquellos cuyos mantenedores acaban borrando su cuenta o dejando que caduque el dominio. En la mayoría de los ecosistemas, esto supone un problema a la hora de aplicar parches. En Go, además, supone una puerta de entrada para ataques.

Cómo funciona el «repojacking» en una ruta de importación

Hay dos acciones relacionadas con la cuenta que liberan una ruta de GitHub. Al eliminar una cuenta se libera el nombre de usuario, y al cambiar un nombre de usuario se libera el anterior. En ambos casos, github.com/olduser/lib queda sin asignar, y quien registre ese nombre de usuario a continuación podrá volver a crear un repositorio con ese nombre y hacerse con la ruta. Eliminar un único repositorio de una cuenta que aún existe no tiene este efecto, ya que nadie más puede crear un repositorio dentro de tu cuenta.

GitHub sí ofrece protección contra esto, y dicha protección es más limitada de lo que la mayoría de la gente cree. La retirada de un espacio de nombres bloquea de forma permanente una combinación de propietario y nombre de repositorio para que no pueda reutilizarse, y afecta a los repositorios que tuvieran más de 100 clones o más de 100 usos de GitHub Actions en la semana anterior el cambio de nombre o la eliminación, además de cualquier elemento que contenga una acción incluida en GitHub Marketplace.

Lo que significa que la protección cubre los paquetes que ya preocupan a todo el mundo y deja sin cubrir el resto.

 

En el caso concreto de Go, esa «cola larga» resulta ser enorme, y la misma investigación que identificó esos más de 15 000 repositorios señaló el motivo. El espejo de módulos de Go descarga un repositorio una sola vez y, a continuación, ofrece esa copia al resto de usuarios. Un módulo puede estar detrás de miles de llamadas «go get» sin que esas descargas servidas por el proxy se registren como clones en GitHub, por lo que una biblioteca muy utilizada puede seguir estando por debajo del umbral de clones que, de otro modo, protegería su espacio de nombres. En el caso de una biblioteca de Go corriente, que no sea una GitHub Action ni esté en el Marketplace, el recuento de clones subestima considerablemente el uso posterior.

Una biblioteca de utilidades con 40 clones a la semana, importada de forma transitiva a unos cientos de proyectos, queda muy por debajo del umbral en cualquier caso. Esas son las rutas que realmente hay que auditar, y también son las que nadie lee, porque se encuentran a cuatro niveles de profundidad en el árbol de dependencias de otra persona.

La retirada se evalúa en el momento en que se cambia el nombre de la cuenta o se elimina, no de forma continua. Es posible que un repositorio que fuera popular hace dos años y que haya estado inactivo desde entonces no haya alcanzado el umbral en la semana en cuestión. No existe ninguna API para comprobarlo ni ninguna lista pública, por lo que la única forma de averiguarlo es intentar crear el repositorio y ver si GitHub responde que el nombre ha sido retirado y no se puede reutilizar.

Por qué la base de datos de sumas de comprobación no puede avalar una nueva versión

La infraestructura de sumas de comprobación de Go es realmente buena, mejor que la de la mayoría de los ecosistemas. Saber exactamente qué garantiza y hasta dónde llega esa garantía es la diferencia entre sentirse protegido y estar protegido.

sum.golang.org mantiene un registro de hash de módulos, solo de adición y auditable públicamente. Cuando una compilación resuelve una versión de módulo público que aún no figura en go.sum, la cadena de herramientas busca ese hash en el registro antes de escribirlo localmente, de modo que la primera descarga no queda sin verificar. Las compilaciones posteriores se comparan con lo que ya figura en el archivo, y el comando «go mod verify» comprueba que las copias de la caché de módulos local no se hayan modificado desde su descarga.

Toda esa maquinaria es muy eficaz en una tarea concreta: se encarga de que todo aquel que solicite example.com/[email protected] reciba los mismos bytes, tanto hoy como dentro de cinco años. Si se vuelve a publicar contenido diferente en una versión que ya ha sido registrada, la discrepancia se hace evidente de inmediato.

El «repojacking» nunca altera una versión registrada. El atacante publica una versión totalmente nueva, o una ruta completamente nueva, y la base de datos de sumas de comprobación hace exactamente lo para lo que fue diseñada. En el caso de una versión verdaderamente nueva, el contenido que ofrece el atacante puede convertirse en el contenido registrado para ese módulo y esa versión, y a partir de ese momento se autenticará fielmente para todos los usuarios posteriores. El hash es real. La entrada del registro es real. Nada en ninguno de los dos indica que el código provenga del mantenedor en el que confiabas originalmente.

 

Escenario Hash anterior para esa versión Se ha detectado una toma de control
Versión grabada reeditada con contenido nuevo Sí Sí, la suma de comprobación no coincide
Nueva versión menor de una ruta secuestrada No, la versión más reciente No
Se ha añadido una nueva dependencia tras una adquisición No No
La dependencia transitiva superó el punto de toma de control No, la versión más reciente No

Si actualizas una dependencia o añades una nueva, la cadena de herramientas puede demostrar que los bytes son los mismos que obtienen todos los demás. Eso no es lo mismo que demostrar que pertenecen a tu compilación.

El módulo proxy de proxy.golang.org resulta útil en este sentido, dentro de ciertos límites. El servidor espejo conserva las versiones que ya ha descargado y puede seguir sirviéndolas después de que desaparezcan del repositorio original, por lo que una versión descargada antes de una adquisición sigue estando disponible y sin cambios, incluso si el repositorio se elimina o se sustituye posteriormente. Esa es una propiedad de seguridad real. Solo se aplica a las versiones ya almacenadas en caché antes de la adquisición, y no tiene nada que ver con la siguiente.

Lo que queda es la pregunta a la que ninguna suma de comprobación responde: si el contenido que aparece en una primera resolución merece confianza alguna. Analizar un módulo antes de que se resuelva y recompilarlo desde el código fuente para compararlo con lo que ha servido la ruta es un control diferente que se aplica antes en la secuencia. Esa es la capa que SecureChain para Go añade delante del proxy.

Las rutas de importación de Vanity abren una segunda vía

El «repojacking» dirigido a los nombres de usuario de GitHub es la variante que ha recibido un nombre y sobre la que se han realizado algunos estudios. La variante que recibe mucha menos atención es la relacionada con los dominios personalizados.

Go admite rutas de importación personalizadas mediante etiquetas meta HTML. Al resolver example.com/mylib, la cadena de herramientas es redirigida a https://example.com/mylib?go-get=1, que devuelve una etiqueta meta «go-import» en la que se indica la ubicación real en el sistema de control de versiones (VCS), y la cadena de herramientas la sigue. Así es como funciona golang.org/x/… y cómo una empresa publica módulos bajo su propio dominio.

El modo de fallo se explica por sí solo, sobre todo porque no requiere que el atacante sea especialmente ingenioso. Si el dominio caduca, quienquiera que lo registre a continuación controlará todas las rutas de los módulos que se encuentren bajo él y podrá redirigir esas rutas a donde quiera. Aquí no hay umbral de recuento de clones, ni retirada de espacios de nombres, ni GitHub interviene en absoluto.

Pequeñas consultoras, startups desaparecidas, proyectos de código abierto de una sola persona: esa es la exposición. Además, un dominio caducado resulta más barato y discreto de adquirir que un nombre de usuario de GitHub con algún historial asociado.

go list -m -f ‘{{.Path}}’ all \
  | grep -v ‘^github.com/\|^golang.org/\|^gopkg.in/’ \
  | cut -d/ -f1 | sort -u

Así obtendrás todos los servidores no evidentes desde los que se resuelve tu compilación. Averigua quién controla cada uno de esos dominios y vigila el estado del registro, ya que un dominio a punto de caducar, o uno que simplemente parezca abandonado, es una dependencia con una fecha límite asociada.

Revisión de las rutas de los módulos para detectar riesgos de apropiación indebida

Dos comprobaciones, ambas rápidas. Ejecútalas en todos los repositorios de Go que tenga tu organización, no solo en el servicio estrella que todo el mundo ya supervisa.

Empieza por marcar las URL de los repositorios de GitHub que ya no devuelven una respuesta de repositorio activo.

go list -m -f ‘{{.Path}}’ all | grep ‘^github.com/’ \
  | cut -d/ -f1-3 | sort -u \
  | while read p; do
  printf ‘%s %s\n’ “$(curl -s -o /dev/null -w ‘%{http_code}’ “https://$p”)” “$p”
done | grep -v ‘^200’

Un código 404 es una señal de alta prioridad que indica que la ruta ya no está disponible públicamente; conviene investigarla antes de considerarla un riesgo confirmado de «repojacking», ya que también puede significar que el repositorio se ha convertido en privado. Un código 301 suele indicar un cambio de nombre o un traslado de cuenta, y la comprobación evita deliberadamente seguir las redirecciones para que el estado que se muestre sea el que se ve. Una vez que un atacante se apropia de una ruta, la comprobación vuelve al estado 200, por lo que esto detecta la ventana antes de una apropiación, en lugar de después de ella.

 

Entonces fíjate en lo que se ha quedado atrás entre los caminos que sí tienen salida.

go list -m -u all | grep ‘\[‘

Esto señala los módulos para los que hay versiones más recientes disponibles, lo cual es un punto de partida más que una respuesta. En el caso de un módulo que lleva varias versiones de retraso mientras que el desarrollador original sigue publicando actualizaciones, conviene contrastarlo con la actividad real del repositorio.

Establece la comprobación de errores 404 en CI con una periodicidad semanal, en lugar de tratarla como una auditoría puntual. Una cuenta que hoy sea propietaria de uno de esos repositorios puede ser eliminada o renombrada el mes que viene, y el plazo que transcurre antes de que alguien reclame el antiguo nombre de usuario es la advertencia gratuita que recibes.

Qué hacer realmente con un módulo cuyo desarrollador original ha desaparecido definitivamente es una cuestión más complicada que encontrarlo. Asumir la propiedad del código significa que tu equipo pasa a ser responsable de sus CVE, y la mayoría de las organizaciones que eligen ese camino no cuentan con personal dedicado a ello. La retroaplicación de correcciones al código del que el mantenedor original se ha alejado es una disciplina muy específica, centrada en las pruebas de regresión y la compatibilidad de comportamiento más que en nuevas funcionalidades, lo que suele ser la razón por la que acaba quedando fuera del ámbito de competencias de un equipo de producto.

Qué cambiar en tus compilaciones de Go

Go no cuenta con un gancho de ciclo de vida al estilo de npm, ni con un equivalente a «postinstall», por lo que la descarga de una dependencia no proporciona a un atacante un punto de ejecución gratuito. Esa es una ventaja real frente a npm y PyPI.

Sin embargo, esta ventaja tiene un límite. El código de un módulo de Go sigue ejecutándose en algún momento, siempre que algo que lo importe se compile, se pruebe o se ejecute, normalmente a través de una función `init()` que se activa al iniciar el sistema. El desencadenante automático en el momento de la instalación desaparece. La ruta de ejecución del código no desaparece, lo cual supone un problema diferente, no uno menor.

Por dónde empezar depende de en qué punto te encuentres ya. Si nunca se han auditado las rutas de los módulos, ejecuta esta semana la comprobación de errores 404 en todos los repositorios, ya que una hora de trabajo te proporcionará una lista concreta a partir de la cual trabajar. Si tus dependencias incluyen módulos alojados en cuentas personales de GitHub, añádelos como «vendor» con el comando `go mod vendor` y confirma el resultado; de este modo, el contenido revisado de las dependencias se guardará en tu propio repositorio, de modo que las compilaciones que utilicen el directorio «vendor» nunca descargarán esas versiones desde una ruta que pudiera cambiar de manos.

Si resuelves módulos desde dominios personalizados, comprueba el estado de registro de cada uno de ellos, ya que un dominio personalizado caducado supone una toma de control más amplia que un simple nombre de usuario y no cuenta con ningún umbral de protección. Si utilizas GOPRIVATE o GONOSUMDB, revisa los patrones con cuidado. GONOSUMDB excluye los módulos coincidentes de la verificación de la base de datos de sumas de comprobación, y GOPRIVATE actúa como valor predeterminado para esa exclusión a menos que lo anules; por lo tanto, un comodín más amplio de lo previsto elimina silenciosamente la verificación de la base de datos de sumas de comprobación de más módulos de los que pretendías excluir. Y si Go convive con npm, PyPI, Maven o crates.io en tu organización, establece una política única para todos ellos, ya que los atacantes actúan en distintos ecosistemas y proteger bien uno solo no hace más que trasladar el problema a otro lugar.

Nada de esto es una solución definitiva. No hay garantía de que un módulo que hoy sea seguro porque su responsable de mantenimiento sigue en activo vaya a seguir siéndolo.

La vulnerabilidad depende en gran medida de qué parte de tu código se aloja en cuentas de la organización frente a las personales. Un servicio de Go que dependa principalmente de golang.org/x, los principales SDK de la nube y los proyectos de la CNCF suele tener menos problemas de este tipo. Un servicio que incorporara seis bibliotecas de utilidades con un único mantenedor allá por 2019 tiene mucho trabajo por delante, y realizar la auditoría es la forma de averiguar qué dependencias requieren atención.

Dónde encaja SecureChain para Go

Las comprobaciones anteriores no impiden que se produzca una toma de control. Te indican dónde estás expuesto, lo cual es útil, pero no es lo mismo que la prevención. La laguna que queda por cubrir es decidir si una nueva versión autenticada del módulo merece confianza, en lugar de limitarse a confirmar que todo el mundo ha recibido los mismos bytes, y eso es precisamente lo que hace SecureChain para Go está diseñado para gestionar. Recompila los módulos a partir del código fuente, los analiza en el momento de la descarga y sigue aplicando parches a las vulnerabilidades conocidas incluso cuando el desarrollador original del módulo ya no está activo. En la página del producto encontrarás todos los detalles, y el boletín de TuxCare está disponible si prefieres estar al tanto de las investigaciones sobre la cadena de suministro específicas de Go a medida que se publiquen.

PREGUNTAS FRECUENTES

¿Qué es el «repojacking»?

Recuperar la ruta de un repositorio de origen del que un proyecto sigue dependiendo, normalmente después de que la cuenta original se haya eliminado o renombrado. Dado que la ruta sigue apareciendo en los archivos de dependencias, el código publicado en la ubicación recuperada genera compilaciones que se basan en la ruta original.

¿Por qué Go es especialmente vulnerable al «repojacking»?

Go identifica los módulos por su ruta en el sistema de control de versiones (VCS), en lugar de por un nombre registrado con un registro de propiedad. No existe ninguna cuenta de registro que posea github.com/user/lib, por lo que, cuando el nombre de la cuenta subyacente queda disponible, quienquiera que lo reclame pasa a controlar la ruta del módulo.

¿Impide la base de datos de sumas de comprobación de Go el «repojacking»?

No por sí solo. Garantiza que todos los que descarguen una versión determinada de un módulo obtengan un contenido idéntico, incluso en la primera descarga, cuando se consulta el hash en el registro público. Lo que no puede determinar es si una versión totalmente nueva publicada desde una ruta pirateada es fiable, ya que una versión genuinamente nueva publicada desde esa ruta puede convertirse en el contenido registrado con el que todos los usuarios posteriores comparan para verificar su autenticidad.

¿Evita GitHub la apropiación del espacio de nombres de los repositorios?

En parte, y principalmente para los repositorios populares. La retirada de un espacio de nombres bloquea una combinación de propietario y nombre de repositorio cuando el repositorio ha tenido más de 100 clones o más de 100 usos de GitHub Actions en la semana anterior, o cuando contiene una acción listada en el Marketplace. Los proyectos más pequeños no alcanzan ese umbral, por lo que sus espacios de nombres siguen estando disponibles para su reclamación. En el caso de Go, el criterio de clonación es aún menos estricto, ya que el espejo del módulo puede servir miles de descargas sin generar clones.

¿Cómo puedo encontrar dependencias de Go que puedan recuperarse?

Enumera las rutas de los módulos con el comando `go list -m -f ‘{{.Path}}’ all` y, a continuación, comprueba si la URL de cada repositorio sigue devolviendo una respuesta válida. Si aparece un error 404 en una ruta de la que depende tu compilación, es necesario investigar más a fondo, ya que suele significar que el repositorio de origen ha desaparecido, aunque también puede indicar que ahora es privado. En cualquier caso, conviene comprobar esa ruta antes de darla por segura, y es recomendable hacerlo de forma periódica, en lugar de solo una vez.

¿El sistema de «vendoring» protege contra el «repojacking»?

Sí, en el caso de las dependencias que ya se hayan añadido a tu directorio «vendor». Al añadirlas a «vendor», el contenido de las dependencias se copia en tu repositorio, y el modo «vendor» de Go no se conecta a Internet durante la compilación. Sin embargo, esto no protege las nuevas dependencias ni las actualizaciones de versión, que siguen resolviéndose desde la ruta de forma habitual.

¿Los módulos de Go ejecutan código durante la instalación?

Los módulos de Go no cuentan con scripts de ciclo de vida de dependencias comparables a los «hooks» de instalación de npm. El código malicioso presente en un módulo de Go se ejecuta cuando se ejecuta cualquier elemento que lo importe, ya sea una prueba, una herramienta de compilación o un binario de producción, normalmente a través de una función `init()` que se activa durante el inicio.

 

Resumen
El «repojacking» y por qué las rutas de los módulos de Go son objetivos de apropiación
Nombre del artículo
El «repojacking» y por qué las rutas de los módulos de Go son objetivos de apropiación
Descripción
Las rutas de los módulos de Go son ubicaciones del sistema de control de versiones (VCS), en lugar de nombres propios. Lo que revelan los 15 000 repositorios expuestos, y lo que la base de datos de sumas de comprobación puede y no puede hacer.
Autor
Nombre del editor
TuxCare
Logotipo de la editorial

Únase a más de 4.500 profesionales de Linux y el código abierto.

2 veces al mes. Sin spam.

Índice
Obtenga las respuestas de seguridad de código abierto que necesita

Únase a más de 4.500 profesionales de Linux y el código abierto.

2 veces al mes. Sin spam.