En la semana del 19 al 23 de octubre de 2026, tres incidentes distintos pusieron secretos de desarrolladores en manos de atacantes. OpenCode dijo que una parte legacy de su Console alojada expuso las claves de proveedores vinculadas del 1,4% de sus usuarios de Console. Railway informó que un atacante llegó a los sistemas que guardan las variables de entorno de sus clientes y descifró una parte. Y litellm 1.107.0, publicado en PyPI y Docker desde un pipeline de release secuestrado, traía un ladrón de credenciales que se ejecuta en cada arranque de Python.
Este es el runbook que seguiría hoy: averiguar si estuviste expuesto, rotar en orden de radio de impacto sin tirar producción, resolver cualquier máquina que haya ejecutado la build mala de LiteLLM y abaratar la próxima filtración. Cada comando está verificado contra la documentación del proveedor o los informes del incidente; cuando algo depende de una sola fuente, lo aclaro.
1. El Problema: Una Semana, Tres Filtraciones de Secretos de Desarrolladores
El hilo común es el objetivo, no la técnica. Railway dice que su atacante ‘intentaba obtener claves de API de proveedores de IA para hacer ataques de destilación’, y OpenCode también culpó a un ataque de destilación: claves robadas de OpenAI, Anthropic y Google usadas para extraer salidas de modelos a escala con la factura de otro. El ladrón de LiteLLM también se lleva esas claves, además de todo lo demás que haya en el host. Ninguna fuente vincula al atacante de LiteLLM con los otros dos.
Cronología, octubre de 2026 (UTC; informes de los proveedores)
¿Te afecta? La versión de dos minutos
Railway: si tu cuenta es anterior al 21 de octubre y guardás algún secreto en variables de Railway, Railway te pide revocarlo y volver a cargarlo, te haya llegado un email o no. OpenCode: solo usuarios de Console con proveedores externos vinculados; OpenCode dice que si no recibiste email, no te afectó. LiteLLM: solo si algo instaló litellm 1.107.0 o descargó uno de los cuatro tags de Docker dentro de las ventanas de arriba, o si todavía ejecutás un entorno o una imagen creados entonces. LiteLLM dice que su release privada enterprise no se vio afectada.
En esa lista se esconden dos tipos de exposición. Railway y OpenCode filtraron secretos concretos guardados: los rotás y listo. Un host que ejecutó litellm 1.107.0 filtró todo lo que podía leer y puede seguir corriendo una puerta trasera. El runbook los trata distinto.
2. Cuatro Conceptos Antes de Tocar una Clave
Breve a propósito. Estas cuatro ideas deciden el orden y el método de todo lo que sigue.
El radio de impacto decide el orden
¿Qué puede hacer este secreto?
Rotá primero lo que permite a un atacante crear más accesos o llegar a tus datos: las claves IAM de la nube pueden crear usuarios y claves nuevas, y las URLs de base de datos abren tus datos. Las claves de IA queman presupuesto; los tokens de GitHub llegan a tu código y tus releases. Si publicás paquetes o imágenes, subí los tokens de GitHub en la lista: así secuestraron el release de LiteLLM.
Secreto filtrado vs host comprometido
Railway y OpenCode vs LiteLLM
Un secreto filtrado se arregla rotándolo. Un host comprometido no: litellm 1.107.0 ejecutaba un ladrón y un cargador de código remoto cada hora, así que los secretos nuevos que pongas en esa máquina pueden volver a robarse. Primero reconstruí y después rotá desde un lugar limpio.
Rotación con solapamiento
Nueva, desplegar, verificar, revocar
La guía de Railway para rotar sin downtime: emití la credencial nueva, pasá los servicios a ella, verificá y recién entonces revocá la vieja. Los contenedores leen las variables solo al arrancar, así que nada cambia hasta que redesplegás. El solapamiento debe durar minutos, no días.
Hooks de arranque de Python
Archivos .pth y sitecustomize.py
Python ejecuta los archivos .pth y sitecustomize.py de site-packages cada vez que arranca el intérprete. Por eso el payload se dispara sin importar litellm, y por eso pip show, pip install o un downgrade en un entorno infectado lo vuelven a ejecutar.
3. Paso 1 — Triaje: ¿Estuviste Expuesto?
Empezá por el inventario, no por la rotación. No podés rotar lo que no listaste, y querés conservar la evidencia antes de cambiar nada.
Railway: listá cada secreto que guardás ahí
Railway CLI 5.x (5.71.0 mientras escribo; railway variables sigue funcionando como alias de railway variable). Imprimí nombres, nunca valores, por servicio y entorno. La CLI no devuelve las variables selladas, así que revisalas en el dashboard. Exportá antes los logs de auditoría de GitHub y de tus registros: la guía de GitHub de Railway dice que lo hagas antes de revocar nada.
# Solo nombres, por servicio y entorno: los valores nunca llegan a tu terminal railway login railway link # elegí proyecto, entorno y servicio railway variable list --service api --environment production --kv | cut -d= -f1 # La CLI no devuelve las variables selladas: listalas desde el dashboard # Antes de revocar nada, exportá: # Org de GitHub: Settings > Logs > Audit log (JSON o CSV) # Registros (Docker Hub, GHCR): historial de tokens de acceso y pushes
LiteLLM: encontrá la 1.107.0 sin arrancar Python
Correlo en laptops, servidores, runners de CI propios e imágenes. Los bloques 1–3 solo leen archivos; el bloque 4 arranca Python, así que dejalo para hosts que ya salieron limpios. Los nombres de archivo, rutas, el marcador de entorno y los hashes de los wheels salen del issue de GitHub, el post del incidente de LiteLLM, Aikido y Datadog.
# 1. Lockfiles y requirements
grep -rn 'litellm==1.107.0' --include='*.txt' --include='*.in' --include='*.toml' .
grep -rn -A2 'name = "litellm"' --include='uv.lock' --include='poetry.lock' . | grep '1\.107\.0'
# Prefijos SHA-256 de los siete wheels maliciosos
grep -rnE '7598727f209d|0aaf7ba346d4|462d0c9f67ca|668169d35b87|1bbce8caa94f|c2e32d0ce254|12d4ccc5c6f8' .
# 2. Entornos instalados, solo sistema de archivos
find / -type d -name 'litellm-1.107.0.dist-info' 2>/dev/null
find / -path '*-packages*' \( -name cook.pth -o -name cook.py -o -name sitecustomize.py \) 2>/dev/null
ls -la /tmp/.pc_once_* /tmp/remote_code_loader.lock 2>/dev/null
sudo grep -l PRIVCOLLECT_ACTIVE /proc/*/environ 2>/dev/null # Linux: payload corriendo ahora
# PowerShell: Get-ChildItem C:\ -Recurse -Filter cook.pth -ErrorAction SilentlyContinue
# 3. Imágenes: el digest del índice malicioso, y mirar dentro sin ejecutar
docker images --digests | grep -iE 'litellm|1bbcb1443bc9'
cid=$(docker create <IMAGE_ID>) # ID local de la imagen, no el tag: los tags se reapuntaron
docker export "$cid" | tar -tf - | grep -E 'cook\.pth|litellm-1\.107\.0\.dist-info'
docker rm "$cid"
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.containerStatuses[*].imageID}{"\n"}{end}' | grep -i litellm
# 4. Solo en hosts que pasaron 1-3 (estos arrancan Python)
pip show litellm | head -n 2
uv pip list | grep -i litellmLos runners alojados, las cachés y los mirrors también cuentan
Un runner alojado por GitHub se descarta al terminar el job, pero cada secreto que ese job podía leer se fue con el ladrón. Buscá litellm-1.107.0 o litellm==1.107.0 en los logs de CI de ejecuciones entre las 11:39 y las 14:20 UTC del 21 de octubre. Después revisá las cachés de pip y uv, los mirrors internos de PyPI y las cachés pull-through de tus registros: pueden seguir sirviendo una versión que PyPI ya retiró.
4. Paso 2 — Rotá por Radio de Impacto, Sin Downtime
Hacelo desde una máquina de confianza. Si tu laptop ejecutó litellm 1.107.0, no es esa máquina. Para cada secreto de la lista: creá el nuevo, ponelo donde vive, redesplegá, verificá y después revocá el viejo.
1. Claves raíz e IAM de la nube
Claves de acceso de AWS, claves de service accounts de GCP, secretos de Azure. Desactivá antes de borrar, así podés volver atrás durante unos minutos. Si un host con rol de instancia o de tarea ejecutó la 1.107.0, revocá también las sesiones activas de ese rol en IAM; LiteLLM sugiere buscar en CloudTrail llamadas a GetCallerIdentity de ese rol alrededor de la hora de instalación.
2. URLs de base de datos
Ante una sospecha de filtración en Postgres, MySQL, Redis o MongoDB de Railway, usá el flujo Regenerate de Railway y redesplegá cada servicio que referencie esas variables. Railway advierte que editar POSTGRES_PASSWORD a mano o ejecutar ALTER USER por tu cuenta deja desincronizadas las variables y la base. Ya que estás, desactivá la red pública en las bases que no la necesiten.
3. Claves de proveedores de IA
Creá la nueva, desplegala, verificá, borrá la vieja y después revisá el uso por clave desde el 14 de octubre. Railway avisó a los proveedores de todas las claves que guardaba y les pidió revocarlas, así que una ráfaga de 401 el 21 o 22 de octubre puede ser el proveedor y no tu código. No te olvidés de las claves guardadas dentro de la base de datos de un proxy LiteLLM.
4. Tokens de GitHub
PATs clásicos y fine-grained, autorizaciones OAuth y de GitHub Apps, deploy keys, secretos de Actions y el propio token OAuth de la CLI de GitHub. gh auth logout solo borra el token localmente; el manual de GitHub dice que revoques la app GitHub CLI, lo que mata sus tokens en todos los dispositivos. Railway también reseteó sus instalaciones de GitHub App, así que reconectá los repos y recreá RAILWAY_TOKEN y RAILWAY_API_TOKEN en tu CI.
5. Claves SSH
Generá una clave nueva en una máquina limpia, registrala y después quitá la clave pública vieja de GitHub y del authorized_keys de cada servidor. El ladrón lee ~/.ssh, así que una clave sin passphrase en un host expuesto está, sencillamente, perdida.
6. Secretos de firma de webhooks
Stripe, GitHub, Slack y similares. Usá la función de rotación del proveedor si la tiene; si no, hacé que tu verificador acepte el secreto viejo y el nuevo durante una ventana corta y después eliminá el viejo. La guía de Stripe de Railway dice que elijas expiración inmediata cuando la filtración está confirmada.
Railway: solapamiento de dos claves desde la CLI
Es el patrón sin downtime que documenta Railway. Dale al servicio un healthcheck que use de verdad la credencial, así un valor malo hace fallar el deploy nuevo mientras el viejo sigue sirviendo. Pasar la clave por stdin la mantiene fuera del historial de tu shell.
# 1. Creá la clave nueva en el proveedor. La vieja sigue funcionando.
# 2. Dejala lista sin reiniciar (railway link eligió el entorno)
read -rs NEW_KEY
printf '%s' "$NEW_KEY" | railway variable set OPENAI_API_KEY --stdin \
--service api --skip-deploys
# 3. Repetí con cada variable que cambie y desplegalas juntas
railway redeploy --service api
# 4. Comprobá que el deploy nuevo está sano y las llamadas funcionan
railway logs --service api --since 10m
# 5. Recién ahora revocá la clave vieja en el proveedor
# ¿Varios servicios? Guardá la clave una vez como variable compartida y referenciala:
# OPENAI_API_KEY=${{ shared.OPENAI_API_KEY }}Nube, GitHub y SSH desde la línea de comandos
Mismo orden que la lista de arriba. Los marcadores en mayúsculas los completás vos.
# AWS: clave nueva, desactivá la vieja y borrala tras un día tranquilo aws iam create-access-key --user-name APP_USER aws iam update-access-key --user-name APP_USER --access-key-id OLD_KEY_ID --status Inactive aws iam delete-access-key --user-name APP_USER --access-key-id OLD_KEY_ID aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=OLD_KEY_ID \ --start-time 2026-10-14T00:00:00Z # por región: repetí con --region # Claves de service accounts de GCP gcloud iam service-accounts keys create new-key.json --iam-account=SA_EMAIL gcloud iam service-accounts keys delete OLD_KEY_ID --iam-account=SA_EMAIL # CLI de GitHub: logout es solo local gh auth status # cuentas, hosts y scopes del token aquí gh auth logout --hostname github.com --user YOUR_USER # después github.com/settings/applications > GitHub CLI > Revoke gh auth login # token nuevo, desde una máquina limpia # SSH: clave nueva, registrala y eliminá la vieja en todas partes ssh-keygen -t ed25519 -C "laptop-2026-10" -f ~/.ssh/id_ed25519_2026_10 gh ssh-key add ~/.ssh/id_ed25519_2026_10.pub --title "laptop 2026-10" gh ssh-key list gh ssh-key delete OLD_SSH_KEY_ID
Cuándo saltarte el solapamiento
El solapamiento protege la disponibilidad, no el secreto. La propia guía de Postgres de Railway dice que, si sospechás una filtración, regenerés primero: cortar la credencial vieja importa más que una caída corta. Mi regla: si los paneles de uso o los logs de auditoría muestran abuso de la clave, revocala ya y asumí el downtime; si la rotación es preventiva, limitá el solapamiento a los minutos que tarda un redeploy.
5. Paso 3 — Si una Máquina Ejecutó litellm 1.107.0
Entonces es un incidente del host, no una actualización de dependencia. LiteLLM dice que asumás exfiltración si la 1.107.0 corrió en un host con salida a internet, y que la ausencia de archivos marcadores no prueba que el host esté limpio.
🧯 Contener, reconstruir, rotar
- 1. Aislá el host y conservá la evidencia: hacé un snapshot del disco o volumen y exportá los logs que vas a querer después, antes de borrar nada.
- 2. No arranqués Python ahí ni lo ‘arreglés’ con pip install litellm==1.106.2. Cada arranque del intérprete vuelve a ejecutar el payload, y Datadog señala que borrar el directorio del paquete litellm deja atrás cook.pth, cook.py y sitecustomize.py.
- 3. Reconstruí desde una imagen base limpia o una VM, venv y runner nuevos. LiteLLM también documenta una limpieza en el lugar (borrar los tres archivos y sus .pyc, matar el daemon que retiene /tmp/remote_code_loader.lock); yo la usaría solo para cortar la hemorragia mientras corre la reconstrucción.
- 4. Rotá todo lo que el host podía leer, desde una máquina limpia: el rol de instancia o de tarea, cada clave de proveedor más la master key y las virtual keys de la base de LiteLLM, la contraseña de DATABASE_URL y cualquier secreto en el entorno de los procesos de ese host.
- 5. Fijá la versión al reconstruir. Los avisos nombran la 1.106.2 como versión limpia; desde entonces LiteLLM publicó la 1.106.4 y backports como la 1.105.5 (25 de octubre), firmadas con una clave de cosign nueva.
Indicadores de salida y del host que conviene buscar
El endpoint de exfiltración www.litellm.dev/healthcheck aparece en el issue de GitHub y en Aikido y Datadog. No es el dominio de LiteLLM (ese es litellm.ai). El cargador consultaba un archivo ‘dead drop’ en GitHub que ya fue retirado; Datadog dice que solo contenía valores de prueba cuando lo observaron. Buscá en logs de DNS, proxy y firewall desde el 21 de octubre.
# Logs de DNS, proxy y firewall desde el 21 de octubre
grep -rniE 'litellm\.dev|letters9813/pythonclient-test' /var/log 2>/dev/null
# Servicios en Railway: logs de consultas DNS desde la CLI
railway logs --service api --dns --since 1w | grep -i 'litellm\.dev'
# Los POST salientes llevaban esta cabecera (Aikido, Datadog)
# X-Token: XWiA8gZNa7NcX2XRoAEb
# Un sitecustomize.py puede ser legítimo: compará el hash antes de alarmarte
find / -path '*-packages*' -name sitecustomize.py -exec sha256sum {} + 2>/dev/null \
| grep 34bef04fe88ac9d06. Paso 4 — Abaratá la Próxima Filtración
Nada de esto habría evitado la brecha de Railway; esa es responsabilidad de la plataforma. Sí reduce lo que te cuesta la próxima filtración, y dos de estas medidas habrían dejado litellm 1.107.0 fuera de tus builds por completo.
Fijá con hashes, y entendé lo que no hacen
Los hashes garantizan que el archivo que instalás es el que fijaste en el lockfile. No impiden que fijés un release nuevo malicioso: si hubieras regenerado el lock durante esas dos horas, el hash habría sido el del ladrón. Para eso está el cooldown.
# requirements.in: litellm[proxy]==1.106.4 uv pip compile requirements.in --generate-hashes -o requirements.txt pip install --require-hashes -r requirements.txt # Proyectos uv: uv.lock ya guarda hashes; que CI falle si se desvía uv sync --locked
Cooldowns de dependencias: un ataque de dos horas contra una espera de siete días
litellm 1.107.0 estuvo en PyPI menos de dos horas; los releases de marzo estuvieron publicados entre 40 minutos y tres horas, según la fuente. Una antigüedad mínima de un día habría saltado ambos. Revisá antes la versión de tu herramienta: uv 0.9.17+ para duraciones relativas, pip 26.1+ para duraciones en días (26.0 acepta timestamps), pnpm 10.16+ y npm con min-release-age (11.10+, según la cobertura del release). Dejá una vía de escape para parches de seguridad urgentes.
# uv >= 0.9.17: pyproject.toml
[tool.uv]
exclude-newer = "7 days"
# pip >= 26.1: ISO 8601, solo días
pip install --uploaded-prior-to P7D -r requirements.txt
# pnpm >= 10.16 (pnpm 11 usa 1440 por defecto): pnpm-workspace.yaml, minutos
minimumReleaseAge: 10080
# npm: .npmrc, días
min-release-age=7
# Renovate: renovate.json
{ "minimumReleaseAge": "7 days" }
# Dependabot: .github/dependabot.yml, dentro de cada entrada de updates
cooldown:
default-days: 7Docker: digest, no tag
Cuatro tags, latest y main-stable incluidos, apuntaron a la imagen maliciosa durante hasta dos horas y media. Un digest no se puede reapuntar. Verificá las firmas con la clave nueva de LiteLLM (commit e82a3c6, en uso desde el 24 de octubre); la clave vieja 0112e53 falla con los releases nuevos. Una advertencia de Datadog: los artefactos maliciosos traían procedencia de build que nombraba como origen el fork de nombre parecido, así que si revisás la procedencia, mirá qué repositorio indica.
# Resolvé el tag una vez, verificalo y fijá el digest docker buildx imagetools inspect ghcr.io/berriai/litellm:v1.106.4 # imprime Digest: sha256:... cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/e82a3c646908ba0a6ee0beddc4310d7a6cac4065/cosign.pub \ ghcr.io/berriai/litellm:v1.106.4 # Dockerfile o compose: el tag para humanos, el digest para la máquina FROM ghcr.io/berriai/litellm:v1.106.4@sha256:<DIGEST_QUE_VERIFICASTE>
📆 Este mes: menos secretos de larga vida
- Trusted Publishing para lo que publicás: PyPI cambia un token OIDC de tu CI por un token de subida válido durante 15 minutos, así que no hay token de larga vida que robar. No protege un workflow de release que un atacante pueda disparar: protegé tags y ramas sin bypass para admins y exigí un revisor en el environment de release.
- Tratá los tokens de gh como claves de producción: La causa raíz de LiteLLM empezó con una master key subida a un repo público en mayo y nunca revocada; el 10 de octubre, una API interna devolvió con ella el token OAuth de la CLI de GitHub de un owner de la organización. Mantené las cuentas owner fuera del uso diario de la CLI, revisá los scopes con gh auth status y quitá los que no usés con gh auth refresh --remove-scopes.
- Credenciales de vida corta donde existan: Railway no soporta federación OIDC (respuesta de su staff en el foro de la comunidad en febrero de 2026; hay una feature request abierta), así que los servicios en Railway siguen guardando claves estáticas. Hacé que perderlas salga barato: una clave por servicio, claves de proyecto de OpenAI con expiración, claves de OpenRouter con límite de gasto y expiración. Usá OIDC donde sí existe, como GitHub Actions hacia tu nube o hacia OpenAI.
- Sellá lo que puedas: Las variables selladas de Railway nunca se muestran en la UI ni las devuelven la API o la CLI. Eso limita quién puede leerlas después; no asumiría que cambia algo cuando la brecha está en la propia capa de almacenamiento.
¿Seguís usando LiteLLM? Mi guía del gateway sigue en pie
El 14 de octubre recomendé LiteLLM como gateway autoalojado, fijado a v1.105.1 por tag exacto. Esa versión nunca se vio afectada y el consejo se mantiene, con dos cambios: fijá por digest en vez de por tag, y verificá con la clave nueva de cosign, porque el comando de ese post usa la clave vieja 0112e53, que falla desde la 1.105.4. Actualizar significa 1.105.5 en la línea 1.105 o 1.106.4 en la 1.106.
Leé la guía del gateway LiteLLM →7. Errores Comunes que Evitaría
Cada uno aparece en las guías de los proveedores o en los hilos del incidente.
❌ Hacer downgrade de litellm en el lugar en un host infectado.
✅ Reconstruí. Cualquier arranque de Python re-ejecuta el payload, y los tres archivos sobreviven al directorio del paquete.
❌ Rotar desde la laptop que ejecutó el ladrón.
✅ Rotá desde una máquina limpia, o los secretos nuevos se filtran igual.
❌ Ejecutar gh auth logout y dar el token por revocado.
✅ Revocá la app GitHub CLI en github.com; el logout es solo local.
❌ Revocar la clave vieja antes de que el deploy nuevo esté sano.
✅ Nueva, desplegar, verificar, revocar, salvo que veas abuso activo.
❌ Rotar la copia de Railway y olvidarte de las demás.
✅ Encontrá cada copia: archivos .env, secretos de CI, otras plataformas, la base de LiteLLM.
❌ Rotar LITELLM_SALT_KEY junto con la master key.
✅ LiteLLM dice que no rotés la salt key: cambiá LITELLM_MASTER_KEY, reiniciá y rotá las claves en cada proveedor.
8. Qué Está Confirmado y Qué No (Revisado el 26 de Octubre de 2026)
‘Confirmado’ significa el registro del propio proveedor más al menos un informe independiente. Todo lo demás va redactado como ‘según’.
Artefactos y ventanas de LiteLLM
PyPI 1.107.0 de 11:39:51 a 13:23 UTC; Docker Hub v1.107.0, latest, main-latest y main-stable de 11:46:56 a 14:20 UTC. Coinciden el post del incidente de LiteLLM, el issue #47826 de GitHub y Datadog. Aikido da las 12:47 como fin de la ventana de PyPI; uso la hora más tardía de LiteLLM.
Alcance en Railway
Como máximo el 1,85% de los workspaces tuvo variables de entorno potencialmente comprometidas y una parte se descifró, según el informe preliminar de Railway; TechCrunch recoge el mismo comunicado. Railway no puede decir qué variables de cada proyecto quedaron expuestas, por cómo estaban cifradas las claves (envelope encryption).
Cifras de OpenCode
1,4% de los usuarios de Console con proveedores externos vinculados, según el post de OpenCode en X y su email, tal como los cita BleepingComputer. No encontré un post en su blog ni una causa raíz.
¿Una campaña o tres?
Railway y OpenCode describen robo de claves para destilación. Nada vincula al atacante de LiteLLM con ellos, y Datadog dice que tampoco se estableció relación con el incidente de LiteLLM de marzo.
Causa raíz en Railway
Descrita solo en parte: una escalada de privilegios en la infraestructura de build y después credenciales de CI. Railway dice que publicará un seguimiento cuando concluya la investigación; al 26 de octubre no había salido.
Regla de oro
Inventario antes de rotar, reconstrucción antes de rotar en cualquier host que ejecutó la build mala, y nunca revoqués una clave vieja hasta que la nueva esté sirviendo tráfico, salvo que otro ya la esté usando.
Checklist para Copiar y Pegar
Pegalo en el ticket del incidente y andá tachando.
[ ] Railway: listá nombres de variables por servicio y entorno (las selladas, desde el dashboard) [ ] Exportá el audit log de la org de GitHub y los logs de registros antes de revocar nada [ ] LiteLLM: grep en lockfiles y hashes de wheels; buscá dist-info, cook.pth, sitecustomize.py [ ] LiteLLM: imágenes por digest, imageIDs de k8s, logs de CI 21 oct 11:39-14:20 UTC, cachés, mirrors [ ] ¿Positivo? Aislá, snapshot, reconstruí desde una base limpia (sin downgrade en el lugar) [ ] Desde una máquina limpia, rotá en orden: nube/IAM > BD > claves IA > GitHub (incl. gh) > SSH > webhooks [ ] Cada una: nueva > railway variable set --skip-deploys > redeploy > verificar > revocar la vieja [ ] ¿Abuso visible en uso o auditoría? Revocá primero y asumí el downtime [ ] Railway: recreá RAILWAY_TOKEN / RAILWAY_API_TOKEN, reconectá GitHub, redesplegá el último commit [ ] Railway: desactivá la red pública en las bases que no la necesitan [ ] Proxy LiteLLM: master key nueva (no la salt key), regenerá las virtual keys [ ] Revisá uso y costo por clave desde el 14 oct; security log de GitHub; CloudTrail por clave [ ] Fijá: hashes en lockfiles, imágenes por digest, cosign con la clave e82a3c6 [ ] Cooldown: uv exclude-newer / pip --uploaded-prior-to / pnpm / npm / Renovate / Dependabot [ ] Límites de gasto y expiración en cada clave de IA; OIDC donde la plataforma lo soporte
Conclusión
El patrón de esta semana es bastante claro: las claves de proveedores de IA ya valen la pena robarlas a escala, y los atacantes las sacan de donde estén, sea el almacén de variables de un PaaS, una consola alojada o el site-packages de un desarrollador. No controlás la próxima brecha de la plataforma. Sí controlás cuánto tiempo sigue siendo útil una clave filtrada.
Si hoy solo hacés tres cosas: listá cada secreto que guardás en Railway y rotalo con el patrón de solapamiento, demostrá que ninguna máquina o imagen tuya ejecutó litellm 1.107.0 y sumá un cooldown a tus actualizaciones de dependencias. El resto del checklist puede esperar a mañana.
El runbook en una pantalla
Triaje
- • Nombres de variables en Railway
- • Lockfiles, imágenes, logs de CI
- • Archivos e IOCs de salida
Rotar
- • Primero nube, BD, claves IA
- • Después GitHub, SSH, webhooks
- • Nueva, desplegar, verificar, revocar
Endurecer
- • Hashes y digests
- • Cooldowns por antigüedad
- • Claves de vida corta y acotadas
Fuentes
- Railway — Security Disclosure: Wednesday October 21st
- Railway — Preliminary Report: Oct 21st Security Incident
- Docs de Railway — Rotar credenciales sin downtime
- Docs de Railway — CLI: railway variable
- TechCrunch — Railway pide a todos sus usuarios revocar sus claves tras un ciberataque
- BleepingComputer — el hackeo de OpenCode expone claves de API en plena ola de ataques de destilación
- GitHub — issue #47826 de BerriAI/litellm (1.107.0 comprometida)
- LiteLLM — Incidente de seguridad, 21 de octubre de 2026
- LiteLLM — Cadena de ataque de la v1.107.0 (análisis de causa raíz)
- Datadog Security Labs — LiteLLM comprometido en PyPI y Docker
- Aikido — LiteLLM comprometido: infostealer y RAT
- GitHub — release v1.106.4 de LiteLLM (clave de cosign nueva)
- Manual de GitHub CLI — gh auth logout
- Docs de uv — exclude-newer
- Docs de pnpm — minimumReleaseAge
- Docs de Renovate — minimum release age
- Docs de PyPI — Trusted Publishers
- Railway Station — la federación OIDC no está soportada (respuesta del staff)



