Hardening 2 (issue 0005, fases 0005a-0005e) cierra los hallazgos nuevos de la re-auditoría red-team (report 0006): bump de nats-server + toolchain (16 CVEs -> 0 alcanzables), drop de frames sin firma en rooms SignMsgs, limiter global de bytes en vuelo contra el DoS por concurrencia, TLS obligatorio en bind publico, y cableado de la ACL por subject que cierra el wildcard metadata leak. Detalle por fase en el capability growth log del app.md y en el report 0007. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.7 KiB
issue, title, status, created, completed, domain, scope, depends_on, blocks, source
| issue | title | status | created | completed | domain | scope | depends_on | blocks | source |
|---|---|---|---|---|---|---|---|---|---|
| 0005 | Hardening 2 — CVEs, spoof por firma omitida, DoS por concurrencia, TLS forzado (re-auditoría) | done | 2026-06-07 | 2026-06-07 | security | unibus (go.mod, pkg/client, pkg/membership/server.go, cmd/membershipd/config.go, pkg/embeddednats, pkg/blobstore) | 0001, 0004 (cierra los hallazgos NUEVOS de la re-auditoría sobre lo entregado) | 0001f (deploy público) y 0003f (deploy descentralizado) | projects/message_bus/reports/0006-2026-06-07-unibus-security-reaudit.md |
Objetivo
La re-auditoría red-team (report 0006) confirmó que el hardening 0004 cerró H1–H7/H12,
pero encontró hallazgos nuevos que mantienen el veredicto en "NO exponer público
aún". Este issue los cierra. La re-auditoría se hizo sobre el commit 618f6b6
(pre-0003); algunos hallazgos pueden haber cambiado con 0003 — cada fase debe primero
verificar si el hallazgo sigue vivo en el master actual (post-0003, v0.6.0) antes de
arreglarlo.
Estado verificado al crear este issue (master post-0003):
- N1 vivo:
go.modsigue ennats-server v2.10.22ygo 1.25.0. - N3 vivo:
pkg/client/client.go:802tieneif info.Policy.SignMsgs && f.Sig != nil(el patrón vulnerable exacto). - H4: 0003 añadió
pkg/membership/acl.go— hay que evaluar si cierra el wildcardSubscribe(">")o si falta la capa de NATS Permissions. - N2, N4: presumiblemente vivos (0003 no los tocó); verificar.
Fases (TBD, ramas issue/0005x-*)
0005a — N1 (Alto): CVEs en dependencias
Hallazgo: govulncheck ./... → 16 vulnerabilidades alcanzables: 14 en
github.com/nats-io/nats-server/v2@v2.10.22 (servidor embebido, expuesto público en el
deploy decidido) + 2 en la stdlib de Go (net/textproto GO-2026-5039, crypto/x509
GO-2026-5037).
Fix:
go get github.com/nats-io/nats-server/v2@v2.11.15(o superior que cubra las 14).- Subir la toolchain a
go1.26.4(cubre las 2 de stdlib); actualizar la directivagoengo.modsi procede. - Re-correr
govulncheck ./...hasta 0 affected. - Nota: este es un cambio de
go.mod/go.sumjustificado por CVE; documentarlo en el commit. Verificar que el bump de nats-server no rompe el cluster/JetStream de 0003 (correr toda la suite, incluido el e2e multi-nodo).
DoD: govulncheck ./... → "No vulnerabilities found" (o solo no-alcanzables); suite
completa verde tras el bump.
0005b — N3 (Alto): spoof por firma omitida en rooms firmadas
Hallazgo: pkg/client/client.go::processFrame verifica la firma solo si el frame la
trae: if info.Policy.SignMsgs && f.Sig != nil { verify }. Un atacante con acceso al
data plane publica un frame con Sig==nil y Sender forjado → el receptor lo acepta como
auténtico en una room que EXIGE firma.
Fix: en una room SignMsgs, un frame sin firma debe dropearse:
if info.Policy.SignMsgs {
if f.Sig == nil { return } // exige firma; sin ella, descarta
if !verify(...) { return }
}
DoD: portar TestReaudit_SigNilSpoof → ahora el frame Sig==nil con Sender forjado
en una room SignMsgs se descarta (no se entrega al handler). Golden (frame firmado
válido se entrega) + edge (room sin SignMsgs no se ve afectada) + error (Sig==nil en
SignMsgs → drop).
0005c — N2 (Medio-Alto): DoS por concurrencia
Hallazgo: el límite por-request (16 MiB) + rate-limit per-IP NO acotan la memoria agregada. 40 subidas de 16 MiB simultáneas (= el burst per-IP) → 1.42 GB RSS. Multi-IP escala sin techo.
Fix (elegir y documentar):
- Límite global de conexiones concurrentes y/o de bytes-en-vuelo (semáforo con cota de memoria total), y/o
- Stream del blob a disco en vez de
io.ReadAllen RAM (encaja con la cuota/GC del issue 0002), y/o - Bajar
maxBlobBytesy separar mejor el límite de control (1 MiB) del de blobs.
DoD: test que lanza N subidas concurrentes al techo y verifica que el RSS agregado
queda acotado (mide /proc/self/status, cota declarada) en vez de crecer linealmente
con N. Golden (concurrencia normal pasa) + edge (en la cota) + error (exceso → 429/503 sin
OOM).
0005d — N4 (Medio): forzar TLS del control plane en bind público
Hallazgo: el guard validateBootConfig cierra "público sin enforce" y "TLS sin
enforce", pero permite público + enforce sin --tls-cert → el control plane sirve
HTTP plano públicamente (reaparece H5: metadata en claro).
Fix: el guard debe exigir --tls-cert/--tls-key cuando el bind no es loopback.
public + enforce + sin TLS → log.Fatal.
DoD: portar TestGap_PublicEnforceNoTLS → ahora validateBootConfig("0.0.0.0", enforce, "", "") rechaza. Golden (público+enforce+TLS OK) + edge (loopback sin TLS
sigue OK para dev) + error (público sin TLS aborta).
0005e — H4 (Medio, residual): evaluar y completar la ACL por subject
Contexto: 0003 añadió pkg/membership/acl.go. Primero evaluar con el ataque del
report 0006 (TestReaudit_H4_WildcardMetadataLeak: un registrado no-miembro con
Subscribe(">") raw capta subjects + advisories de JetStream de rooms ajenas) si ese
acl.go ya lo cierra.
- Si lo cierra → portar el test como regresión y documentar.
- Si NO (probable: la ACL real necesita NATS
Permissionspor identidad a nivel del authenticator/cuenta, no solo lógica de membership en el control plane) → implementar las Permissions por identidad derivadas de pertenencia, o documentar el límite y el plan.
DoD: TestReaudit_H4_WildcardMetadataLeak → el no-miembro ya NO capta los subjects de
rooms ajenas (o, si queda residual, está documentado con su límite exacto).
Fuera de alcance (otros issues)
- H9 (cuota/GC de blobs) → issue 0002; se solapa con 0005c (streaming a disco).
- H10 (AEAD nonce) / H11 (nonce/ts en firma de owner) → bajo, futuro.
- H8 (custodia de la CA: generar en om) → operacional del deploy.
- Auditoría de la superficie nueva de 0003 (cluster routes auth, jetstreamStore KV fail-closed, nonce-cache replicado, failover) → el report 0006 NO la cubrió (auditó pre-0003). Pendiente una re-auditoría dedicada de 0003 (prompt ya preparado).
Definition of Done global
govulncheck ./...→ 0 alcanzables.- Los tests adversariales de la re-auditoría (
TestReaudit_SigNilSpoof,TestGap_PublicEnforceNoTLS,TestReaudit_H4_WildcardMetadataLeak, DoS-concurrencia) portados como regresión y en verde (o el residual documentado). CGO_ENABLED=0 go build ./... && go vet ./... && go test ./...verdes (incluido el e2e multi-nodo de 0003, para confirmar que el bump de nats-server no lo rompió).- Re-evaluación: el veredicto de exposición pública pasa de "NO-aún" a "sí-con-condiciones".