Riesgo de Seguridad: Acceso No Revocado en GitHub Expone Repositorios de CrowdSec
TL;DR — Lo que vas a tener al final
- Tiempo de setup: 2-3 horas para el switch a enforcing.
- Requisitos: Root, kernel moderno, 2-4 semanas en permissive antes de enforcing.
- Resultado: Sistema con MAC activo, logs en /var/log/audit/audit.log.
- Troubleshooting: audit2allow, audit2why, setenforce 0 para emergencias.
Qué pasó y por qué te importa aunque no uses CrowdSec
El 18 de septiembre de 2026, CrowdSec publicó un comunicado incómodo: un atacante copió alrededor de 170 de sus repositorios privados en GitHub el 22 de mayo, usando la cuenta de un empleado que acababa de dejar la empresa. CrowdSec mantuvo su acceso a GitHub abierto después de su salida. La laptop del ex-empleado había sido comprometida en el ataque a la cadena de suministro contra TanStack de mayo 2026, donde versiones maliciosas de paquetes npm robaron credenciales de los equipos de los desarrolladores. El código apareció en un foro online el 16 de septiembre, junto con correos de 83 usuarios de CrowdSec y datos de 51 inversores potenciales de 2020.
CrowdSec es una empresa francesa de seguridad que probablemente sabe más sobre gestión de incidentes que la mayoría. Si una organización así se quema con algo tan básico como no revocar el acceso de GitHub de un ex-empleado, vale la pena mirar tu propia casa con la misma lupa. La lección no se queda en CrowdSec ni en TanStack: es un recordatorio de tres riesgos simultáneos — dependencia de la cadena de suministro de npm, gestión de identidades fuera de banda, y tiempo de exposición de credenciales tras el offboarding.
Timeline del incidente: lo que se sabe hasta ahora
CrowdSec fue bastante detallado en su timeline, lo que ayuda a entender exactamente cómo se acumularon los puntos ciegos. Esta cronología es la pieza que necesitas para mapearla contra tus propios procesos.
| Fecha | Evento | Punto de control que faltó |
|---|---|---|
| Mayo 2026 | TanStack publica versiones maliciosas de sus paquetes npm que roban credenciales | Verificación de integridad post-instalación (npm audit signatures, lockfile pinning) |
| Mayo 2026 | Laptop de ex-empleado de CrowdSec queda comprometida (robo de tokens locales) | Rotación de credenciales tras incidente de supply chain en el ecosistema |
| Mayo 2026 | Ex-empleado deja CrowdSec pero su acceso a GitHub permanece activo | Workflow de offboarding automatizado (suspender primero, luego desactivar) |
| 22 mayo 2026 | Atacante clona ~170 repositorios privados usando el OAuth token del ex-empleado | Detección de actividad anómala en GitHub audit log + alertas en tiempo real |
| 18 sep 2026 | CrowdSec publica el incidente tras detectar el código en un foro (16 sep) | Rotación proactiva de tokens / no esperar al leak público |
Lo que hace este caso didáctico es que las defensas de CrowdSec probablemente estaban bien configuradas en su perímetro. La brecha vino de una credencial legítima que nunca debió seguir viva después de mayo. Es el clásico problema de “todo está seguro excepto el usuario que ya no trabaja aquí”.
El problema real: no fue el ataque TanStack, fue la no revocación
TanStack fue el vector inicial, pero el agujero que dejó a CrowdSec sin 170 repos privados fue la decisión de mantener el acceso GitHub de un ex-empleado. Eso convierte un incidente aislado en una brecha persistente: el atacante tuvo semanas o meses para operar con una cuenta que la organización ya no controlaba.
Para que veas cómo se ve un token de GitHub vigente desde el lado del atacante, este sería el flujo típico:
# El atacante ya tiene el token (robado via TanStack npm package)
# Verifica que sigue funcionando contra la org
curl -s -H "Authorization: token ghp_xxxxxxxxxxxxxxxxxxxx"
https://api.github.com/user | jq '{login,id,name}'
# Lista los repos accesibles con ese token
curl -s -H "Authorization: token ghp_xxxxxxxxxxxxxxxxxxxx"
"https://api.github.com/user/repos?visibility=private&affiliation=collaborator&per_page=100"
| jq -r '.[].full_name'
Si este comando devuelve repositorios privados de tu organización, ya tienes un problema. El primer bloque valida que el token no fue revocado; el segundo enumera todo lo que el ex-empleado podía ver. CrowdSec tenía 170 repos que alguien con credencial válida podía copiar.
Troubleshooting: si no tienes `jq`, reemplaza la última línea por `python3 -m json.tool`. Si el atacante ya rotó el token, el segundo endpoint devolverá `401`; eso es buena noticia y significa que el control sí funcionó.
El patrón real detrás del incidente es lo que los frameworks como NIST 800-53 AC-2 llaman “account management”: deshabilitar y luego revocar, en ese orden, dentro de una ventana de horas tras la salida. CrowdSec falló ese control básico.
Cómo lo mitigas en tu organización (aunque tengas 1 servidor)
La buena noticia es que este incidente se previene con controles accesibles incluso para una operación pequeña. Empieza por detectar quién tiene acceso a qué en GitHub:
# Lista todos los miembros externos y cuándo fueron agregados
gh api orgs/TU-ORG/outside-collaborators
--jq '.[] | {login, created_at: .created_at}'
# Lista colaboradores de un repo específico con su rol
gh api repos/TU-ORG/TU-REPO/collaborators
--jq '.[] | {login, role_name, permissions: .permissions}'
El primer comando te dice quién tiene acceso desde fuera de tu organización. El segundo te dice qué nivel de permiso tiene cada persona dentro de un repo. Si alguien aparece con `role_name: “Maintain”` y no trabaja contigo desde hace seis meses, ya tienes un incidente latente.
Troubleshooting: si `gh` no está autenticado, corre `gh auth login` o exporta `GH_TOKEN`. Un colaborador con `permissions.admin: true` que ya no reconoces es exactamente lo que le pasó a CrowdSec.
Listar no es suficiente. Lo que cambia el juego es automatizar la revocación cuando alguien sale. Si usas un directorio centralizado, corre un script al evento “user disabled”:
#!/usr/bin/env bash
# offboard-github.sh — corre desde tu pipeline de HR / IdP
set -euo pipefail
USER="$1"
ORG="TU-ORG"
gh api -X DELETE "/orgs/${ORG}/members/${USER}" || true
USER_ID=$(gh api "/users/${USER}" --jq '.id')
gh api -X DELETE "/applications/grants/${USER_ID}" || true
echo "GitHub access removed for ${USER}"
Este script saca al usuario de la organización, revoca concesiones OAuth y PAT, y deja la puerta cerrada. Lo que CrowdSec omitió fue al menos el primer paso.
Troubleshooting: `DELETE /applications/grants/{user_id}` requiere una GitHub App autorizada; sin ella queda como TODO. Para detección de drift más robusta, usa `tfsec` o `steampipe` con el plugin `github`.
Auditoría mensual: detecta el mismo error antes que el atacante
El offboarding es reactivo. Para detectar el caso contrario — ex-empleados que se quedaron sin que te dieras cuenta — necesitas una auditoría periódica. Este script se ejecuta vía cron cada lunes y te avisa si hay drift entre tu IdP y GitHub:
#!/usr/bin/env bash
# audit-github-members.sh — corre cada lunes a las 09:00 vía cron
set -euo pipefail
ORG="TU-ORG"
INACTIVE_DAYS=90
ACTIVE_USERS=$(curl -s -H "Authorization: Bearer ${IDP_TOKEN}"
"https://admin.googleapis.com/directory/v1/users?domain=TU-DOMINIO&fields=users(primaryEmail)"
| jq -r '.users[].primaryEmail' | sort)
GH_MEMBERS=$(gh api "/orgs/${ORG}/members" --paginate --jq '.[].login' | sort)
GHOST_USERS=$(comm -13 <(echo "$ACTIVE_USERS") <(echo "$GH_MEMBERS"))
[ -n "$GHOST_USERS" ] && echo "$GHOST_USERS"
| mail -s "[ALERTA] GitHub ghost users en ${ORG}" ops@tu-dominio
gh api "/orgs/${ORG}/outside-collaborators" --paginate
| jq -r '.[] | select(.updated_at < (now - 86400 * '"${INACTIVE_DAYS}"') | strftime("%Y-%m-%dT%H:%M:%SZ")) | .login'
| mail -s "[REVIEW] Outside collaborators inactivos en ${ORG}" ops@tu-dominio
La sección “GHOST_USERS” es exactamente el caso CrowdSec: gente que sigue en GitHub pero ya no está en tu directorio de empleados. Si esa lista no está vacía, tienes un problema antes de que el atacante lo encuentre.
Troubleshooting: el cron requiere un token de GitHub con scope `read:org` y un token de IdP con permiso de lectura de usuarios. Si usas SAML/SSO, agrega `–affiliation=direct` para excluir invitados. Si no tienes `mail` configurado, reemplaza por `curl -X POST` a tu webhook. Para audit log continuo en GitHub Enterprise Cloud, exporta eventos `oauth_access_grant` y `outside_collaborator_remove` a Panther o Loki y alerta si aparece un grant sin `member_add` previo en los últimos 5 minutos.
El otro lado: rota tokens tras un ataque de supply chain
El vector inicial fue TanStack, pero la brecha se mantuvo abierta porque nadie rotó credenciales. Si tu equipo instala paquetes populares de JavaScript, da por hecho que cada incidente de supply chain relevante te afecta aunque no uses directamente el paquete troyano. La rotación vale más que el diagnóstico técnico:
# Developers que tocaron código durante el incidente
git log --all --since="2026-05-01" --until="2026-05-31"
--pretty=format:"%ae" | sort -u > /tmp/affected_devs
# Revoca SSH keys de cada developer afectado
while read -r dev; do
gh api "/users/${dev}/keys" --jq '.[].id' | xargs -I{}
gh api -X DELETE "/users/${dev}/keys/{}" || true
done < /tmp/affected_devs
# Cierra todas las sesiones activas en GitHub Enterprise
gh api -X DELETE "/admin/sessions"
El primer bloque lista developers que hicieron commits en mayo. El segundo revoca sus SSH keys. El tercero cierra sesiones activas en GitHub Enterprise. Si tu equipo consume paquetes via npm registry privado, agrega `npm token revoke` para cada `.npmrc` corporativo.
Troubleshooting: este script es destructivo y debe correr en ventana de mantenimiento. Para listas grandes, paraleliza con `xargs -P 4`. La lección: si tu equipo tocó un paquete afectado, rota. No esperes a que el atacante use las credenciales. CrowdSec esperó desde mayo hasta septiembre y el código ya estaba en un foro público.
Checklist de offboarding seguro (úsalo el día que alguien se va)
Más allá de GitHub, el offboarding cubre media docena de superficies. Este checklist vale la pena tener pegado en el runbook de tu equipo:
- Cuenta de usuario en IdP: deshabilitar primero, eliminar después. La ventana de deshabilitado es donde se revocan tokens sin perder rastro de auditoría.
- PAT y SSH keys en GitHub: revocar todas las concesiones OAuth, los PAT finos y las deploy keys. Si usas GitHub Apps, audita qué installation tokens le pertenecen.
- Cuentas en cloud (AWS, GCP, Azure, Hetzner, DigitalOcean): rotar IAM access keys, eliminar su usuario IAM, mover sus recursos a un usuario de servicio.
- Repos y registros (Docker Hub, GitLab, npm, PyPI): remover como maintainer de paquetes publicados bajo su cuenta personal; idealmente transferir ownership antes de que la cuenta expire.
- Secrets compartidos (Vault, 1Password, Bitwarden): revocar acceso a vaults compartidos, rotar cualquier secreto que solo esa persona conocía, especialmente tokens de deploy.
- Servidor físico / VPN / Tailscale: remover claves SSH autorizadas en todos los hosts, expulsar de la tailnet, rotar claves de cualquier servidor donde tenía acceso root.
- Mensajería y correo: archivar y reasignar buzones, expulsar de canales de Slack/Matrix/Discord donde haya material sensible.
Estos siete pasos deben correr el mismo día que la persona sale, idealmente antes de que entregue la laptop. Si tu proceso tarda una semana, estás aceptando el mismo riesgo que aceptó CrowdSec. Una forma barata de validarlo es hacer un drill anual: simula que alguien se va y mide cuánto tarda tu checklist en ejecutarse. Si pasa de cuatro horas, hay margen.
Qué hacer distinto si dependes de paquetes npm
El vector original del incidente fue TanStack, así que vale la pena mirar también tu postura frente a la cadena de suministro de JavaScript. El primer control es activar la verificación de firma de npm y bloquear builds con dependencias sin attestations:
# En tu CI, antes de npm install:
npm config set audit-level high
npm install --ignore-scripts # evita postinstall scripts en paquetes nuevos
# Verifica attestations de provenance para paquetes críticos
npm view @tanstack/react-query --json | jq '.dist.attestations'
# En package.json, exige versiones exactas (no ^ ni ~) para deps de seguridad
{
"overrides": {
"@tanstack/react-query": "5.62.7"
}
}
El primer comando sube el nivel mínimo de auditoría a “alto”, rompiendo el build si hay vulnerabilidades severas. El segundo bloquea scripts de post-instalación, donde la mayoría de paquetes maliciosos colocan su payload. El tercero verifica si un paquete tiene attestations de provenance firmadas por npm. El cuarto permite pinear una versión exacta para que un ataque de confusión de dependencias no pueda deslizarte una versión troyana.
Troubleshooting: `–ignore-scripts` rompe paquetes legítimos con binarios nativos. Mantén una allowlist y aplica el flag solo a dependencias nuevas. La sección `overrides` solo funciona en npm 8.3+. Considera usar un lockfile verificado en CI y bloquear la resolución contra mirrors no aprobados para reducir la superficie de ataque.
Conclusión: el eslabón más débil sigue siendo humano
CrowdSec tiene producto legítimo, empleados sofisticados y presupuesto para hacer las cosas bien. El incidente vino por algo que cabe en una frase: “no revocamos el acceso de un ex-empleado”. Si a ellos les pasa, le puede pasar a cualquier homelab con tres repos en GitHub y un servidor en Hetzner.
Tres controles que cubren el 90% del riesgo:
- Automatiza el offboarding en tu IdP + GitHub. Lo que se hace a mano y “cuando me acuerdo” no se hace.
- Rota credenciales tras cualquier incidente de supply chain en tu ecosistema, aunque no uses el paquete afectado.
- Audita mensualmente los outside collaborators con `gh api orgs/TU-ORG/outside-collaborators`. Cinco minutos que pueden ahorrarte un comunicado de prensa.
SOC2, EDR y SIEM valen la pena, pero todos fallan si la cuenta del ex-empleado sigue viva. Empieza por ahí.
Preguntas frecuentes
¿Por qué Riesgo de Seguridad: Acceso No Revocado en GitHub Expone Repositorios de CrowdSec es relevante para sysadmins Linux?
Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.
¿Qué necesito para empezar con Riesgo de Seguridad: Acceso No?
Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.
¿Cuáles son los errores más comunes con Riesgo de Seguridad: Acceso No?
Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.
¿Cuándo usar Riesgo de Seguridad: Acceso No y cuándo una alternativa?
Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.
¿Cómo migro desde una configuración previa a Riesgo de Seguridad: Acceso No?
Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.
Lee también
- Guía Fedora 44: Novedades, Instalación y Upgrade para Sysadmins Linux
- Configura VLANs en Linux con nmcli: Guía paso a paso para sysadmins
- Protege tu SSH: Guía esencial para configurar Fail2ban en Linux
- Guía Definitiva: Configura Kubernetes con kubeadm en Ubuntu para Sysadmins Linux
- Configura Prometheus y Grafana: Guía DevOps para SysAdmins Linux
