Reports
CRÍTICA[RCE]#3619287

DuckDuckGo: un workflow con pull_request_target permitía ejecutar código y envenenar los scripts de todos sus navegadores

Un workflow de GitHub Actions de content-scope-scripts ejecutaba código de PR ajenas con acceso a secretos. Cualquiera podía robar una clave de API y acabar colando una versión maliciosa en los navegadores de DuckDuckGo.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

El repositorio duckduckgo/content-scope-scripts contiene, según su propio README, el JavaScript compartido que usan todos los navegadores de DuckDuckGo ("shared JavaScript used within ALL DuckDuckGo browsers"). El fallo estaba en uno de sus workflows de GitHub Actions:

.github/workflows/semver-label.yml

Ese workflow se disparaba con pull_request_target, un evento que se ejecuta en el contexto del repositorio base, con acceso a sus secretos, aunque la pull request venga de un fork de cualquier usuario. Y aun así, semver-label.yml:

  1. Descargaba el código de la cabeza de la PR (controlado por quien la abre).
  2. Ejecutaba sobre él npm install y npm run build.
  3. Después lanzaba un script de confianza con ANTHROPIC_API_KEY en las variables de entorno.

No había ningún control de acceso: cualquier usuario de GitHub podía dispararlo con solo abrir una PR desde un fork. Así, el código del atacante se ejecutaba en el runner de Actions con los secretos del repositorio a su alcance.

El investigador agrupó cuatro hallazgos en el reporte:

  1. Ejecución remota de código y robo de ANTHROPIC_API_KEY mediante semver-label.yml.
  2. Ataque a la cadena de suministro manipulando etiquetas de versión para activar el pipeline de publicación.
  3. Exposición del secreto DAX_PAT en otros workflows relacionados que también usan pull_request_target.
  4. Un total de 10 workflows con pull_request_target repartidos por la organización de DuckDuckGo en GitHub.

La versión divulgada del reporte no incluye el contenido del workflow vulnerable ni el detalle de los hallazgos 3 y 4.

Cadena de ataque

Según la descripción del investigador:

  1. Desde una cuenta cualquiera de GitHub, hacer un fork de duckduckgo/content-scope-scripts y abrir una pull request contra el repositorio original con código propio.
  2. El evento pull_request_target lanza semver-label.yml sin ninguna comprobación.
  3. El workflow descarga el código de la PR y ejecuta npm install y npm run build: el código del atacante corre en el runner.
  4. Desde ahí se puede extraer ANTHROPIC_API_KEY y abusar del GITHUB_TOKEN del workflow, que tiene permiso pull-requests: write.
  5. Con ese token, añadir etiquetas de versión semántica a cualquier PR del repositorio. Al fusionarse una PR etiquetada, se dispara la cadena de publicación automática:
semver-release.yml → build.yml
  1. build.yml genera la versión sin firma de código ni verificación de integridad, y los proyectos que dependen de ella la toman de la rama de releases sin validar ninguna firma.

Impacto

  • Ejecución de código y robo de secretos: cualquier usuario de GitHub podía ejecutar código arbitrario en el runner de Actions con solo abrir una PR desde un fork, y llevarse ANTHROPIC_API_KEY para usar la API a costa de DuckDuckGo.
  • Control del pipeline de publicación: con el GITHUB_TOKEN (pull-requests: write) el atacante podía poner etiquetas de versión en cualquier PR y decidir cuándo se publicaba una versión nueva.
  • Cadena de suministro de todos los navegadores: una versión envenenada de content-scope-scripts llegaría a los navegadores de DuckDuckGo para Android, iOS, macOS y Windows, a las extensiones de Chrome y Firefox y al componente de autocompletado. Según el investigador, eso afecta a decenas de millones de usuarios en todas las plataformas.
  • Sin firma de versiones: build.yml publicaba sin firma de código ni verificación de integridad, y quienes consumen la rama de releases no validan ninguna firma.

El reporte se envió el 21 de marzo de 2026, con severidad crítica, y se hizo público el 11 de junio de 2026 ya resuelto.