SeguridadDesarrollo IA

Brechas de Railway y LiteLLM: Runbook Práctico para Rotar Secretos

26 de octubre de 2026
14 min de lectura
Un aro con llaves de latón viejas y algunas llaves nuevas y brillantes, colgado de un gancho en una habitación en penumbra
Compartir:

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)

17 octOpenCode anuncia en X que un sistema legacy de Console expuso las claves de proveedores vinculadas del 1,4% de sus usuarios de Console; su email sitúa la exposición más temprana posible a las 00:30 UTC (según cita BleepingComputer).
20 oct13:58: empieza la ventana del ataque a Railway, según su informe preliminar. Esa tarde detienen las builds tras una sospecha de escalada de privilegios en la infraestructura de build.
21 oct07:25–09:18: Railway detecta consultas no autorizadas sobre variables de entorno cifradas; una parte se descifra. Hacia las 09:25 piden a los proveedores que revoquen claves.
21 oct11:39: llegan a PyPI siete wheels maliciosos de litellm 1.107.0. 11:46: les siguen los tags v1.107.0, latest, main-latest y main-stable en Docker Hub y GHCR.
21 octA las 13:23 PyPI ya retiró la 1.107.0; los tags de GHCR desaparecen antes de las 13:51 y todos los de Docker Hub se eliminan o se reapuntan antes de las 14:20.
22–23 octRailway publica un informe preliminar (como máximo el 1,85% de los workspaces tuvo variables potencialmente comprometidas) y guías de rotación por proveedor. LiteLLM publica su análisis de causa raíz.
26 octMientras escribo, el informe forense final de Railway sigue pendiente.

¿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 litellm

Los 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. 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. 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. 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. 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. 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 34bef04fe88ac9d0

6. 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: 7

Docker: 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

  1. 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.
  2. 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.
  3. 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.
  4. 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

Diego Rodriguez

Diego Rodriguez

Ingeniero Senior Full-Stack & AI

Diego tiene mas de 9 anos de experiencia construyendo aplicaciones potenciadas por IA de produccion, desde orquestacion de LLMs y pipelines RAG hasta deteccion de riesgos con ML y sistemas de trading algoritmico.

Conoce mas sobre Diego →