# PENTEST INTERNO del VPS — 2026-07-09

> Auditoría exhaustiva pedida por el dueño: aislamiento entre servicios (no pivotar de uno a otro si uno cae), fuga de
> información, contención de OpenClaw + CVEs públicos, puertos/puertas abiertas, inyección en APIs (XML/etc). Método:
> workflow multi-agente (6 auditores en paralelo → verificación adversarial de CADA hallazgo). 24 hallazgos, verificados
> uno a uno. Detalle bruto: workflow `pentest-vps-cobayka`. Liga con `docs/SERVER-SECURITY-*.md` anteriores y `[[vps-hardening]]`.

## Veredicto
**Servidor sólido. 0 críticos · 0 altos · 0 medios.** Todo LOW/INFO, **nada explotable en vivo hoy**. Confirmados como
**NEGATIVOS (buenas noticias)**: **sin XXE/SSRF/SQLi** en las APIs de entrada (form/cuenta/panel: todo `node:sqlite`
parametrizado, sin parsers XML de input externo, sin `child_process`/`eval`/`Function` con input); **OpenClaw 2026.6.11 sin
CVE aplicable** (gateway solo-loopback); self-XSS del panel mitigado por CSP + allowlist.

## Arreglado y verificado (2026-07-09)
| # | Hallazgo | Fix aplicado | Verificación |
|---|---|---|---|
| 1 | `python3 -m http.server :8131` huérfano (de otra sesión, en `public-v2`, user ubuntu sudo-NOPASSWD) | `kill` | `:8131` cerrado; no lo referenciaba Caddy ni ningún unit |
| 2 | Caddy admin `:2019` sin auth (cualquier proceso local reprograma Caddy) | → socket unix `/run/caddy/admin.sock` 0600 (bitácora 53) | `:2019` refused; otro UID sin sudo → denegado; reload intacto |
| 3 | Helper root del panel permitía `stop` de caddy/fail2ban/clamav (defense-evasion post-compromiso) | quitado `stop` de esos 4 (queda restart/reload); MC/webpanel intactos | helper active, panel arriba, botón «Parar» de esos deja de renderizar |
| 4 | Minecraft `:25565` abierto sin rate-limit | `ufw limit 25565/tcp` (v4+v6) | LIMIT activo; NO reinicia MC; established no se tocan |
| 5 | HSTS del ápex sin `includeSubDomains` | añadido a `cobayka.es` + `www` (NO preload — decisión del dueño) | `validate` OK, `reload`; header en vivo; web 200 |
| 6 | `.bak` del Caddyfile world-readable (0644, sin secretos) | 640 root:caddy en todos | perms alineados |

## ✅ APLICADO Y VERIFICADO — el fix nº1 de AISLAMIENTO (cuenta split, 2026-07-09)
**Separar `/cuenta` del usuario `cobayka`.** Hoy `cobayka-form.service` (:3010, público vía Caddy) y `cuenta-cobayka.service`
(:3030) corren **con el mismo uid `cobayka`** → un fallo en el formulario público podría **leer `cuenta.db`** (semillas TOTP en
claro, tokens de sesión) **y `cuenta.env`**. Severidad hoy = **LOW** (puerta de `/cuenta` CERRADA, solo datos de prueba, y
`ProtectSystem=strict` limita a lectura), pero **escala a alto en cuanto se abra la puerta con clientes reales** → el momento
correcto de arreglarlo es **antes del alta** (ahora, con la puerta cerrada, es el más seguro).
- **Plan atómico** (una sola operación): crear user/grupo `cuenta` (nologin); re-dueño de `/srv/cobayka-web/cuenta/{data,backups}`
  a `cuenta`; `cuenta.env` a `root:cuenta 640`; `User=cuenta` en `cuenta-cobayka.service` **Y** en `cuenta-backup.service` (a la
  vez, o el backup nocturno falla); CLI `--grant-support` como `cuenta` en adelante; `restart cuenta-cobayka` (loopback, sin MC).
- **HECHO (2026-07-09, confirmado por el dueño):** creado uid/grupo `cuenta` (nologin); `data/`+`backups/`+`cuenta.db` re-dueño a
  `cuenta`; `cuenta.env` → `root:cuenta 640`; **código `/srv/cobayka-web/cuenta` a `root:root`** (ni `cobayka` ni `cuenta` lo
  escriben); `User=cuenta` en `cuenta-cobayka.service` Y `cuenta-backup.service`; `--grant-support` → `sudo -u cuenta`. **Verificado:**
  `cobayka` ya NO lee `cuenta.db`/`cuenta.env` ni escribe el código; backup nocturno OK con el nuevo uid; `/cuenta` responde 200; form
  y web intactos. Se capturó además el hardening systemd que otro chat añadió al vivo (11:49) en la copia versionada → `verificar.sh` OK:36/0.
- (Extra recomendado, independiente): cifrar `totp_secret` en reposo y guardar hash del token de sesión — que leer `cuenta.db`
  no baste para forjar 2FA ni repetir sesiones. También antes del alta.

## Residuales ACEPTADOS (con razón honesta — no se «arreglan» a lo tonto)
- **Egress loopback del agente/MC sin filtrar por IP.** Un filtro `IPAddressDeny` naíf **rompe DNS (127.0.0.53) y el propio
  gateway/túnel** (el agente se conecta a su :18789; el dueño lo pilota por SSH-forward). El filtrado L3 no da granularidad de
  puerto, así que cualquier `allow` que mantenga vivo el gateway reabre :3000/:3010/:3030/:8100. Aislamiento real = `PrivateNetwork`
  + netns/proxy (invasivo) o una regla **nftables por uid+puerto** (frágil por el entrelazado con UFW). Contención actual =
  `InaccessiblePaths` + los servicios internos con su propia auth. **Residual documentado**; opción nft-por-puerto disponible si se quiere.
- **Secretos en claro en `openclaw.json` / home del agente** (tokens telegram/gateway, PII): 0600, home 700, **no legibles cross-uid**;
  radio contenido al propio agente. Aceptado (es como guarda OpenClaw sus credenciales).
- **`sandbox=off` + exec-en-aprobación**: la caja systemd ya contiene; sandbox-on exigiría Docker y rompería Codex. Aceptado (decisión previa).
- **Minecraft (uid minecraft) puede leer los secretos del stack de seguridad** (DISCORD_BOT_TOKEN/PROXYCHECK_KEY): radio-de-daño;
  reducirlo toca el arranque del MC → **requiere OK del dueño** para reiniciar (§7). Pendiente de ventana sin jugadores.
- **Agente puede reescribir sus propios SOUL/AGENTS/USER/MEMORY** (erosión blanda de persona, sin bypass de los controles duros):
  opción segura = `ReadOnlyPaths` solo de SOUL.md+USER.md (dejar AGENTS/MEMORY escribibles) + baseline git de deriva. Requiere
  restart del agente (blip). Recomendado, bajo.

## Decisiones de negocio del dueño (no técnicas)
- **HSTS `preload`** (enviar a hstspreload.org): casi irreversible → decidir tú tras auditar subdominios (hay un `ftp.cobayka.es`
  CNAME sin cert). Solo se aplicó `includeSubDomains` (reversible).
- **Whitelist del server MC «amigos»**: dejarla abierta es **decisión durable tuya** (§8-bis) — el auditor sugería cerrarla; NO se toca.

## Deriva de versionado detectada
`cobayka-build/deploy/cuenta-cobayka.service` (versionada) va por detrás del vivo (otro chat le añadió hardening systemd el 07-09
sin versionar). Es benigna; su reconciliación es de ese chat. Aparece como la única `DERIVÓ` de `verificar.sh`.
