<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Networkpolicy on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/networkpolicy/</link><description>Recent content in Networkpolicy on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Thu, 11 Jun 2026 11:00:00 +0000</lastBuildDate><atom:link href="https://blog.lo0.es/tags/networkpolicy/index.xml" rel="self" type="application/rss+xml"/><item><title>Hardening y secretos del stack LLM soberano: defensa en profundidad</title><link>https://blog.lo0.es/posts/hardening-secretos-stack-llm-soberano/</link><pubDate>Thu, 11 Jun 2026 11:00:00 +0000</pubDate><guid>https://blog.lo0.es/posts/hardening-secretos-stack-llm-soberano/</guid><description>&lt;blockquote>
&lt;p>Parte de la serie operativa sobre exprimir un clúster LLM on-premise genérico 4×H100 SXM 80GB. Las piezas hermanas: la &lt;a href="https://blog.lo0.es/posts/ingesta-documental-rag-pdf-a-chunk-indexado/">ingesta documental de PDF a chunk indexado&lt;/a> que llena la base vectorial, el &lt;a href="https://blog.lo0.es/posts/servir-embeddings-rerankers-tei-produccion/">servicio de embeddings y rerankers con TEI en producción&lt;/a> que la alimenta, y —la más directamente acoplada a este post— el &lt;a href="https://blog.lo0.es/posts/gitops-stack-inferencia-llm-flux/">GitOps del stack de inferencia con Flux&lt;/a>, porque GitOps y secretos comparten un problema huevo-gallina que aquí resolvemos. El ensamblado completo de todo esto en un asistente conversacional (LibreChat + LiteLLM + RAG) lo cubre otro post de la serie, todavía en borrador.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Un stack de inferencia LLM soberano son, como mínimo, seis servicios: un &lt;strong>gateway&lt;/strong> (LiteLLM o equivalente), un &lt;strong>motor de inferencia&lt;/strong> (vLLM), un &lt;strong>servicio de embeddings/rerankers&lt;/strong> (TEI), una &lt;strong>base vectorial&lt;/strong>, una &lt;strong>base de estado&lt;/strong> (conversaciones, usuarios) y un &lt;strong>front&lt;/strong> (LibreChat). Seis servicios son seis superficies de ataque, y el asistente &amp;ldquo;funciona&amp;rdquo; en cuanto el gateway devuelve tokens — mucho antes de estar endurecido. Este post recorre la defensa en profundidad capa por capa:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Secretos&lt;/strong>: nunca en claro en git. &lt;code>sealed-secrets&lt;/code> (cifrado asimétrico, controlador en el clúster) vs &lt;strong>SOPS/age&lt;/strong> (cifrado de fichero, simple, GitOps puro) vs &lt;strong>External Secrets Operator + Vault&lt;/strong> (secretos dinámicos, rotación nativa). El problema huevo-gallina del GitOps.&lt;/li>
&lt;li>&lt;strong>Red&lt;/strong>: &lt;code>NetworkPolicy&lt;/code> default-deny + allow explícito; L3/L4 con la NetworkPolicy estándar, &lt;strong>L7 y egress por DNS&lt;/strong> con Cilium (eBPF); control de egress para que el dato no salga del perímetro; mTLS interno.&lt;/li>
&lt;li>&lt;strong>Pod security&lt;/strong>: &lt;code>runAsNonRoot&lt;/code>, &lt;code>readOnlyRootFilesystem&lt;/code>, drop de capabilities, seccomp &lt;code>RuntimeDefault&lt;/code>, Pod Security Standards &lt;strong>restricted&lt;/strong>, nada privilegiado.&lt;/li>
&lt;li>&lt;strong>Cadena de suministro&lt;/strong>: pin por &lt;strong>digest&lt;/strong>, escaneo con &lt;strong>Trivy&lt;/strong>, firma con &lt;strong>cosign&lt;/strong>, admission control que rechaza lo no firmado.&lt;/li>
&lt;li>&lt;strong>AuthN/Z&lt;/strong>: claves virtuales de LiteLLM, OIDC/LDAP en el front, RBAC de Kubernetes con mínimo privilegio.&lt;/li>
&lt;li>&lt;strong>Runtime&lt;/strong>: detección y, opcionalmente, enforcement con &lt;strong>Tetragon&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Dato en reposo&lt;/strong>: cifrado de almacenamiento, credenciales de la base vectorial y de la base de estado.&lt;/li>
&lt;/ol>
&lt;p>La tesis: el hardening &lt;strong>reduce&lt;/strong> el radio de impacto, no lo elimina. Por eso se prioriza por impacto, y el primer trabajo en un 4×H100 es el par gateway↔vector store y el egress-deny.&lt;/p>
&lt;h2 id="la-analogía-el-despacho-soberano">La analogía: el despacho soberano&lt;/h2>
&lt;p>Imagina un despacho que custodia documentación sensible — el corpus que alimenta el RAG, las conversaciones de los usuarios, las credenciales del motor. No basta con que la puerta de la calle cierre. La seguridad de verdad es &lt;strong>defensa en profundidad&lt;/strong>: varias capas, cada una asumiendo que la anterior puede fallar.&lt;/p>
&lt;div class="diagram" style="max-width:760px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 760 520" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Defensa en profundidad como capas concéntricas: perímetro, control de accesos, tabiques, caja fuerte, cámaras, datos">
&lt;defs>&lt;marker id="hd1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#888"/>&lt;/marker>&lt;/defs>
&lt;rect x="20" y="20" width="720" height="480" rx="14" fill="none" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="380" y="44" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="#3b82f6">Perímetro — control de egress (el dato no sale)&lt;/text>
&lt;rect x="58" y="58" width="644" height="404" rx="12" fill="none" stroke="#22c55e" stroke-width="2"/>
&lt;text x="380" y="80" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="600" fill="#22c55e">Control de accesos en cada puerta — NetworkPolicy default-deny&lt;/text>
&lt;rect x="100" y="94" width="560" height="332" rx="10" fill="none" stroke="#f59e0b" stroke-width="2"/>
&lt;text x="380" y="114" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="600" fill="#f59e0b">Tabiques cortafuegos — Pod Security restricted + namespaces&lt;/text>
&lt;rect x="142" y="128" width="476" height="262" rx="9" fill="none" stroke="#888" stroke-width="1.6"/>
&lt;text x="380" y="148" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="600" fill="currentColor">Portero que verifica credenciales — admission control (cosign)&lt;/text>
&lt;rect x="184" y="162" width="392" height="194" rx="8" fill="none" stroke="#888" stroke-width="1.4"/>
&lt;text x="380" y="182" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="600" fill="currentColor">Cámaras — Tetragon/Falco (registro y detección)&lt;/text>
&lt;rect x="248" y="208" width="264" height="120" rx="10" fill="#ef4444" fill-opacity="0.12" stroke="#ef4444" stroke-width="2"/>
&lt;text x="380" y="238" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="#ef4444">Caja fuerte&lt;/text>
&lt;text x="380" y="262" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor">secretos cifrados + llave rotada&lt;/text>
&lt;text x="380" y="284" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor">corpus RAG · conversaciones&lt;/text>
&lt;text x="380" y="306" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor">credenciales del motor&lt;/text>
&lt;text x="380" y="490" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="#888">Cada capa asume que la de fuera puede caer. Atravesarlas todas es caro; ese coste es la seguridad.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>El mapeo es directo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>El perímetro del edificio&lt;/strong> es el &lt;strong>control de egress&lt;/strong>: que un secreto filtrado o un proceso comprometido no pueda exfiltrar el corpus a un servidor externo. Es la capa más infravalorada y la primera que pongo en un despliegue soberano.&lt;/li>
&lt;li>&lt;strong>El control de accesos en cada puerta interior&lt;/strong> es la &lt;code>NetworkPolicy&lt;/code> default-deny: el front no habla con la base vectorial directamente porque no tiene por qué; solo los pares estrictamente necesarios están abiertos.&lt;/li>
&lt;li>&lt;strong>Los tabiques cortafuegos&lt;/strong> son el aislamiento de pod (&lt;code>restricted&lt;/code>) y los namespaces: si un servicio arde, el fuego no salta al de al lado.&lt;/li>
&lt;li>&lt;strong>El portero que verifica credenciales&lt;/strong> es el admission control que comprueba la &lt;strong>firma cosign&lt;/strong> de cada imagen antes de dejarla entrar al clúster.&lt;/li>
&lt;li>&lt;strong>Las cámaras&lt;/strong> son Tetragon/Falco: registran qué hizo cada proceso y detectan (o matan) lo anómalo.&lt;/li>
&lt;li>&lt;strong>La caja fuerte con llave rotada&lt;/strong> son los secretos: cifrados en reposo, fuera de git, y con rotación para que una llave robada caduque.&lt;/li>
&lt;/ul>
&lt;p>Ninguna capa es suficiente sola. El edificio es seguro porque atravesarlas todas es caro.&lt;/p>
&lt;h2 id="capa-1--secretos-el-problema-huevo-gallina-del-gitops">Capa 1 — Secretos: el problema huevo-gallina del GitOps&lt;/h2>
&lt;p>El stack se despliega por GitOps: la pieza hermana de &lt;a href="https://blog.lo0.es/posts/gitops-stack-inferencia-llm-flux/">Flux&lt;/a> reconcilia el estado declarado en git contra el clúster. Eso es excelente para los manifiestos — pero hay un problema fundacional: el motor de inferencia necesita el token de Hugging Face para descargar el modelo, la base de estado necesita su contraseña, el gateway necesita su clave maestra. &lt;strong>Esos secretos no pueden ir en claro en git.&lt;/strong> Cualquiera con acceso de lectura al repo —y en una organización con &lt;code>read&lt;/code> generoso eso es mucha gente, más todo backup del repo, más todo fork— los leería.&lt;/p>
&lt;p>El huevo y la gallina: GitOps quiere que &lt;strong>todo&lt;/strong> el estado esté en git, pero los secretos no pueden estar en git &lt;strong>en claro&lt;/strong>. La solución, en sus tres familias:&lt;/p>
&lt;h3 id="familia-a--sealed-secrets-cifrado-asimétrico-controlador-en-el-clúster">Familia A — sealed-secrets (cifrado asimétrico, controlador en el clúster)&lt;/h3>
&lt;p>&lt;a href="https://github.com/bitnami-labs/sealed-secrets">Bitnami Sealed Secrets&lt;/a> es un controlador que corre en el clúster más una CLI cliente, &lt;code>kubeseal&lt;/code>. Usa &lt;strong>criptografía asimétrica&lt;/strong>: hay un par de claves. La &lt;strong>pública&lt;/strong> la tienen los desarrolladores y sirve para cifrar; la &lt;strong>privada&lt;/strong> vive &lt;strong>solo&lt;/strong> en el controlador del clúster y sirve para descifrar. El flujo:&lt;/p>
&lt;ol>
&lt;li>El desarrollador toma un &lt;code>Secret&lt;/code> normal y lo cifra con &lt;code>kubeseal&lt;/code>, que obtiene la clave pública del controlador. Resultado: un recurso &lt;code>SealedSecret&lt;/code>.&lt;/li>
&lt;li>Ese &lt;code>SealedSecret&lt;/code> &lt;strong>sí&lt;/strong> se commitea a git en claro — está cifrado, no es legible.&lt;/li>
&lt;li>El controlador en el clúster lo detecta, lo descifra con su clave privada y crea el &lt;code>Secret&lt;/code> de Kubernetes real en el namespace destino.&lt;/li>
&lt;/ol>
&lt;p>La propiedad clave: como la clave privada &lt;strong>nunca sale del clúster&lt;/strong>, ni el desarrollador ni nadie con acceso a git puede descifrar. Y el cifrado &lt;strong>incluye el nombre del namespace&lt;/strong>: un &lt;code>SealedSecret&lt;/code> sellado para &lt;code>inferencia&lt;/code> no se puede mover a &lt;code>front&lt;/code> y descifrarlo allí — se comporta como si cada namespace tuviera su propia llave. El controlador gestiona también la &lt;strong>rotación&lt;/strong> de las claves de sellado, etiquetándolas como &lt;code>active&lt;/code> o &lt;code>compromised&lt;/code>.&lt;/p>
&lt;p>Ventaja: encaja perfecto en GitOps puro, el secreto cifrado vive con el resto del estado. Limitación: el secreto, ya descifrado, acaba siendo un &lt;code>Secret&lt;/code> de Kubernetes normal — está en &lt;code>etcd&lt;/code>, y hay que cifrar &lt;code>etcd&lt;/code> aparte (lo veremos en la capa 7).&lt;/p>
&lt;h3 id="familia-b--sops--age-cifrado-de-fichero-simple">Familia B — SOPS + age (cifrado de fichero, simple)&lt;/h3>
&lt;p>&lt;a href="https://github.com/getsops/sops">SOPS&lt;/a> cifra el fichero YAML/JSON entero (o solo sus valores) y lo deja en git cifrado, descifrándolo solo en el momento del despliegue. Soporta KMS de nube (AWS/GCP/Azure), PGP y —lo relevante para soberanía on-premise— &lt;strong>age&lt;/strong>, un esquema de cifrado offline simple sin servidor. El operador de GitOps (Flux trae integración nativa de SOPS) descifra al reconciliar usando la clave age guardada en el clúster.&lt;/p>
&lt;p>Ventaja: simple, scriptable, sin servidor que mantener; el peso operativo recae en custodiar la clave age. Es la recomendación habitual para equipos pequeños o para arrancar. Limitación: la rotación es manual (re-cifrar todo con la nueva clave) y no hay secretos dinámicos.&lt;/p>
&lt;h3 id="familia-c--external-secrets-operator--vault-secretos-dinámicos-rotación-nativa">Familia C — External Secrets Operator + Vault (secretos dinámicos, rotación nativa)&lt;/h3>
&lt;p>El &lt;a href="https://external-secrets.io/">External Secrets Operator (ESO)&lt;/a> &lt;strong>no guarda secretos en git en absoluto&lt;/strong>. En git solo va un &lt;code>ExternalSecret&lt;/code>: una referencia que dice &amp;ldquo;el campo &lt;code>password&lt;/code> viene del path &lt;code>secret/data/vectordb&lt;/code> de tal &lt;code>SecretStore&lt;/code>&amp;rdquo;. El operador lee de un almacén externo —típicamente &lt;a href="https://developer.hashicorp.com/vault">HashiCorp Vault&lt;/a>— y sincroniza el &lt;code>Secret&lt;/code> de Kubernetes. Vault guarda los secretos en su propio backend cifrado y los sirve por API autenticada, de modo que &lt;strong>nunca residen en git&lt;/strong> y, con sus motores dinámicos, puede emitir credenciales de base de datos de &lt;strong>vida corta&lt;/strong> que caducan solas.&lt;/p>
&lt;p>Ventaja: rotación nativa, secretos dinámicos, auditoría centralizada, un único punto de gobierno. Limitación: hay que operar Vault (sellado/desellado, alta disponibilidad, política de acceso), lo que es trabajo real.&lt;/p>
&lt;h3 id="cómo-elegir-y-la-rotación">Cómo elegir, y la rotación&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Criterio&lt;/th>
&lt;th>sealed-secrets&lt;/th>
&lt;th>SOPS + age&lt;/th>
&lt;th>ESO + Vault&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Secreto en git&lt;/td>
&lt;td>cifrado&lt;/td>
&lt;td>cifrado&lt;/td>
&lt;td>solo referencia&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Servidor extra&lt;/td>
&lt;td>controlador ligero&lt;/td>
&lt;td>ninguno&lt;/td>
&lt;td>Vault (pesado)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rotación&lt;/td>
&lt;td>claves de sellado auto&lt;/td>
&lt;td>manual (re-cifrar)&lt;/td>
&lt;td>nativa / dinámica&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Secretos dinámicos (vida corta)&lt;/td>
&lt;td>no&lt;/td>
&lt;td>no&lt;/td>
&lt;td>sí&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Encaje GitOps puro&lt;/td>
&lt;td>excelente&lt;/td>
&lt;td>excelente&lt;/td>
&lt;td>bueno (referencias)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Curva operativa&lt;/td>
&lt;td>baja&lt;/td>
&lt;td>muy baja&lt;/td>
&lt;td>alta&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Para un 4×H100 soberano arrancando, &lt;strong>SOPS/age o sealed-secrets&lt;/strong> cubren el 90% con coste mínimo. Cuando el sistema crece y aparecen requisitos de rotación frecuente y auditoría —típico en escenarios ENS Categoría Alta— se migra a &lt;strong>ESO + Vault&lt;/strong>. La regla de la rotación: &lt;strong>un secreto que nunca rota es un secreto que, una vez filtrado, está filtrado para siempre&lt;/strong>. La rotación no impide la filtración; acota su ventana de validez. Volveremos a ello al hablar de radio de impacto.&lt;/p>
&lt;h2 id="capa-2--red-default-deny-egress-y-la-matemática-de-la-superficie">Capa 2 — Red: default-deny, egress y la matemática de la superficie&lt;/h2>
&lt;h3 id="la-matemática-de-los-pares-de-comunicación">La matemática de los pares de comunicación&lt;/h3>
&lt;p>Sin política de red, en Kubernetes &lt;strong>todos los pods pueden hablar con todos los pods&lt;/strong>. Esa es la postura por defecto, y es la peor para un sistema soberano. Con $N$ servicios, el número de &lt;strong>pares ordenados de comunicación&lt;/strong> posibles (quién-puede-llamar-a-quién) es:&lt;/p>
&lt;p>$$P_{\text{abierto}} = N \cdot (N-1) \approx N^2$$&lt;/p>
&lt;p>Con nuestros $N = 8$ componentes (gateway, vLLM, TEI, base vectorial, base de estado, front, además del controlador de secretos y el de observabilidad), eso son $8 \cdot 7 = 56$ pares dirigidos posibles. Cincuenta y seis caminos por los que un servicio comprometido podría pivotar lateralmente.&lt;/p>
&lt;p>Ahora aplicamos &lt;code>NetworkPolicy&lt;/code> &lt;strong>default-deny&lt;/strong>: nada habla con nada salvo lo explícitamente permitido. La whitelist real de un stack LLM es pequeña. Los pares estrictamente necesarios:&lt;/p>
&lt;ul>
&lt;li>front → gateway&lt;/li>
&lt;li>gateway → vLLM&lt;/li>
&lt;li>gateway → TEI&lt;/li>
&lt;li>gateway → base vectorial&lt;/li>
&lt;li>gateway → base de estado&lt;/li>
&lt;li>front → base de estado (sesiones/usuarios)&lt;/li>
&lt;/ul>
&lt;p>Eso son $E = 6$ aristas. La superficie de comunicación cae de $56$ a $6$:&lt;/p>
&lt;p>$$\frac{E}{P_{\text{abierto}}} = \frac{6}{56} \approx 0{,}107$$&lt;/p>
&lt;p>Casi un &lt;strong>89% de los caminos posibles quedan cerrados&lt;/strong>. El front ya no puede tocar vLLM ni TEI ni la base vectorial directamente; si alguien compromete el front, no tiene ruta de red hacia el corpus. Esto es la diferencia entre $\sim N^2$ y una whitelist $E \ll N^2$.&lt;/p>
&lt;div class="diagram" style="max-width:720px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 720 380" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Grafo de servicios con default-deny: aristas permitidas en verde, bloqueadas en rojo punteado">
&lt;text x="360" y="24" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">Modelo default-deny — verde permitido, rojo bloqueado&lt;/text>
&lt;!-- nodes -->
&lt;circle cx="120" cy="90" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="120" y="94" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="currentColor">front&lt;/text>
&lt;circle cx="360" cy="90" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="360" y="94" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="currentColor">gateway&lt;/text>
&lt;circle cx="600" cy="60" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="600" y="64" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="currentColor">vLLM&lt;/text>
&lt;circle cx="600" cy="160" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="600" y="164" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="currentColor">TEI&lt;/text>
&lt;circle cx="360" cy="270" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="360" y="267" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="600" fill="currentColor">vector&lt;/text>
&lt;text x="360" y="280" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="600" fill="currentColor">DB&lt;/text>
&lt;circle cx="120" cy="270" r="34" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="2"/>
&lt;text x="120" y="267" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="600" fill="currentColor">estado&lt;/text>
&lt;text x="120" y="280" text-anchor="middle" font-family="sans-serif" font-size="10" font-weight="600" fill="currentColor">DB&lt;/text>
&lt;!-- allowed edges (green) -->
&lt;path d="M154,90 L326,90" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M392,78 L568,66" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M392,102 L568,150" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M372,122 L360,236" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M340,120 L140,238" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;path d="M120,124 L120,236" stroke="#22c55e" stroke-width="2.4" fill="none"/>
&lt;!-- blocked edges (red dashed) -->
&lt;path d="M146,116 L334,250" stroke="#ef4444" stroke-width="1.6" stroke-dasharray="5 4" fill="none"/>
&lt;path d="M150,108 L572,150" stroke="#ef4444" stroke-width="1.6" stroke-dasharray="5 4" fill="none"/>
&lt;path d="M154,96 L566,62" stroke="#ef4444" stroke-width="1.6" stroke-dasharray="5 4" fill="none"/>
&lt;path d="M388,256 L572,170" stroke="#ef4444" stroke-width="1.6" stroke-dasharray="5 4" fill="none"/>
&lt;text x="360" y="350" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#22c55e">6 aristas permitidas&lt;/text>
&lt;text x="360" y="366" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#ef4444">~50 caminos restantes cerrados&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="l3l4-estándar-l7-y-egress-con-cilium">L3/L4 estándar, L7 y egress con Cilium&lt;/h3>
&lt;p>La &lt;code>NetworkPolicy&lt;/code> nativa de Kubernetes opera a &lt;strong>L3/L4&lt;/strong>: selecciona pods por label y permite/deniega por puerto y protocolo. Sirve para el grueso de la whitelist. Pero tiene límites: no entiende DNS ni HTTP. Aquí entra &lt;a href="https://docs.cilium.io/en/stable/network/kubernetes/policy/">Cilium&lt;/a>, que aplica las políticas en el &lt;strong>kernel vía eBPF&lt;/strong> y extiende con &lt;code>CiliumNetworkPolicy&lt;/code>:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Egress por FQDN/DNS&lt;/strong>: en vez de fijar IPs (que cambian), permitir &lt;code>egress&lt;/code> solo a &lt;code>huggingface.co&lt;/code> para que vLLM descargue el modelo y bloquear el resto. Crítico para el control de egress.&lt;/li>
&lt;li>&lt;strong>L7&lt;/strong>: permitir solo ciertos métodos/paths HTTP entre gateway y un servicio interno.&lt;/li>
&lt;li>&lt;strong>Modos de enforcement&lt;/strong>: en modo por defecto, un endpoint sin política seleccionándolo tiene todo abierto; en modo &lt;em>always&lt;/em>, &lt;strong>todo está denegado&lt;/strong> hasta que una política lo abra explícitamente. Para un sistema soberano, &lt;em>always&lt;/em> + default-deny es la postura objetivo.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>El control de egress es la pieza soberana por excelencia.&lt;/strong> Que un servicio interno &lt;strong>no pueda&lt;/strong> abrir conexiones a Internet salvo a un puñado de destinos explícitos significa que, aunque comprometan vLLM o el gateway, &lt;strong>el corpus no puede salir del perímetro&lt;/strong>. El RAG es el activo de valor; el egress-deny es lo que impide que se fugue.&lt;/p>
&lt;h3 id="mtls-interno">mTLS interno&lt;/h3>
&lt;p>La &lt;code>NetworkPolicy&lt;/code> dice &lt;em>quién&lt;/em> puede hablar con quién, pero no cifra ni autentica el tráfico este-oeste. Para eso, &lt;strong>mTLS&lt;/strong> (TLS mutuo: cliente y servidor se autentican entre sí). Cilium ofrece autenticación mutua nativa; alternativas como Linkerd o Istio dan una malla de servicios completa. Para un stack de seis servicios, la malla completa de Istio suele ser sobreingeniería; la autenticación mutua de Cilium o Linkerd (más ligero) es proporcional. El efecto: un atacante en la red del clúster no puede suplantar al gateway ni espiar el tráfico interno en claro.&lt;/p>
&lt;h2 id="capa-3--pod-security-restricted-y-nada-privilegiado">Capa 3 — Pod security: restricted, y nada privilegiado&lt;/h2>
&lt;p>Las &lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/">Pod Security Standards&lt;/a> definen tres perfiles: &lt;code>privileged&lt;/code> (sin restricciones), &lt;code>baseline&lt;/code> (lo mínimo razonable) y &lt;strong>&lt;code>restricted&lt;/code>&lt;/strong> (endurecido). El objetivo para todo el stack LLM es &lt;code>restricted&lt;/code>, aplicado vía Pod Security Admission con label de namespace en modo &lt;code>enforce&lt;/code>. El perfil &lt;code>restricted&lt;/code> exige:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>&lt;code>runAsNonRoot: true&lt;/code>&lt;/strong> y &lt;code>runAsUser&lt;/code> distinto de 0. Ningún contenedor corre como root.&lt;/li>
&lt;li>&lt;strong>&lt;code>readOnlyRootFilesystem: true&lt;/code>&lt;/strong>. El sistema de ficheros raíz es de solo lectura; lo que necesite escribir va a un &lt;code>emptyDir&lt;/code> o volumen explícito. Un atacante no puede dejar binarios persistentes en el contenedor.&lt;/li>
&lt;li>&lt;strong>&lt;code>drop: [&amp;quot;ALL&amp;quot;]&lt;/code>&lt;/strong> de capabilities de Linux. Sin capacidades que no se necesiten.&lt;/li>
&lt;li>&lt;strong>seccomp &lt;code>RuntimeDefault&lt;/code>&lt;/strong>, que aplica el perfil del runtime y bloquea las syscalls peligrosas.&lt;/li>
&lt;li>&lt;strong>&lt;code>allowPrivilegeEscalation: false&lt;/code>&lt;/strong>, nada &lt;code>privileged&lt;/code>, sin host namespaces.&lt;/li>
&lt;/ul>
&lt;p>El matiz con GPU: los pods que usan H100 cargan el device plugin de NVIDIA, lo que históricamente tentaba a relajar el &lt;code>securityContext&lt;/code>. No hace falta correr el contenedor de inferencia como privilegiado para usar la GPU; el acceso al dispositivo se gestiona por el plugin, y el pod de vLLM puede y debe correr &lt;code>restricted&lt;/code>. Esto es un tabique cortafuegos: si comprometen vLLM, el atacante tiene un proceso no-root, sin capabilities, con FS de solo lectura y syscalls recortadas — un punto de partida pésimo para escalar.&lt;/p>
&lt;h2 id="capa-4--cadena-de-suministro-de-imágenes">Capa 4 — Cadena de suministro de imágenes&lt;/h2>
&lt;p>La imagen de contenedor es código de terceros que corre con acceso a tus datos. Tres controles compuestos:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Pin por digest, no por tag.&lt;/strong> &lt;code>vllm/vllm-openai:latest&lt;/code> es un blanco móvil; &lt;code>@sha256:...&lt;/code> es inmutable. Fijar el digest garantiza que lo que se desplegó es exactamente lo auditado.&lt;/li>
&lt;li>&lt;strong>Escaneo con &lt;a href="https://github.com/aquasecurity/trivy">Trivy&lt;/a>.&lt;/strong> Antes de promover una imagen, Trivy enumera sus CVEs y compone el SBOM (inventario de componentes). Falla el pipeline si hay vulnerabilidades críticas sin mitigar.&lt;/li>
&lt;li>&lt;strong>Firma con &lt;a href="https://docs.sigstore.dev/cosign/signing/signing_with_containers/">cosign&lt;/a> y verificación en admission.&lt;/strong> El proyecto Sigstore (cosign para firmar, Fulcio como CA de certificados efímeros vía OIDC, Rekor como log de transparencia inmutable) permite firmar la imagen. Y el &lt;a href="https://github.com/sigstore/policy-controller">policy-controller de Sigstore&lt;/a> —o Kyverno— es un &lt;strong>admission controller&lt;/strong> que verifica la firma &lt;strong>antes&lt;/strong> de admitir el pod: una imagen sin firma válida no entra al clúster.&lt;/li>
&lt;/ol>
&lt;p>Este es el portero de la analogía: comprueba la credencial (firma) de cada imagen en la puerta. Una imagen envenenada subida a un registro, o un tag secuestrado, se queda fuera porque no lleva firma del emisor de confianza.&lt;/p>
&lt;h2 id="capa-5--authnz-claves-virtuales-oidc-y-rbac-mínimo">Capa 5 — AuthN/Z: claves virtuales, OIDC y RBAC mínimo&lt;/h2>
&lt;p>Tres puntos de control de identidad, de fuera adentro:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Front (OIDC/LDAP).&lt;/strong> El usuario humano se autentica contra el proveedor de identidad corporativo. El front no inventa su propio sistema de usuarios; delega en OIDC. Esto da SSO, MFA y revocación centralizada.&lt;/li>
&lt;li>&lt;strong>Gateway (claves virtuales de LiteLLM).&lt;/strong> El gateway emite &lt;strong>claves virtuales&lt;/strong>: cada equipo, aplicación o usuario tiene su clave con presupuesto, &lt;em>rate limit&lt;/em> y modelos permitidos. La clave maestra del gateway —y las API keys reales hacia el motor— &lt;strong>nunca&lt;/strong> las ve el cliente; solo manejan su clave virtual, revocable individualmente. Si se filtra una clave virtual, se revoca esa y solo esa.&lt;/li>
&lt;li>&lt;strong>Kubernetes RBAC con mínimo privilegio.&lt;/strong> Los &lt;code>ServiceAccount&lt;/code> de cada servicio tienen los permisos justos. El pod de vLLM no necesita listar &lt;code>Secret&lt;/code> de otros namespaces ni crear pods. RBAC restrictivo significa que un token de service account robado abre muy poco.&lt;/li>
&lt;/ul>
&lt;h2 id="capa-6--runtime-las-cámaras-con-tetragon">Capa 6 — Runtime: las cámaras con Tetragon&lt;/h2>
&lt;p>Las capas anteriores son preventivas. Falta la &lt;strong>detección&lt;/strong>: ¿y si algo, pese a todo, se ejecuta donde no debe? &lt;a href="https://tetragon.io/">Tetragon&lt;/a> es seguridad observable y enforcement en runtime sobre eBPF, &lt;em>Kubernetes-aware&lt;/em>. Hooka eventos del kernel —&lt;code>process_exec&lt;/code>, &lt;code>tcp_connect&lt;/code>, &lt;code>security_file_open&lt;/code>— con sobrecarga típica por debajo del 1%, y puede pasar de &lt;strong>observar&lt;/strong> (registrar el evento) a &lt;strong>enforcement&lt;/strong> (matar el proceso o cortar la conexión en el kernel, &lt;code>Sigkill&lt;/code>).&lt;/p>
&lt;p>La regla operativa, que detallo en el &lt;a href="https://blog.lo0.es/posts/runbook-aislar-agentes-ia-bubblewrap-tetragon/">runbook de bubblewrap + Tetragon&lt;/a>: &lt;strong>adopta primero, bloquea después&lt;/strong>. Primero se despliega en modo observación para levantar un baseline del comportamiento normal del stack —qué binarios ejecuta vLLM, a qué se conecta el gateway— sin falsos positivos. Solo después se promueven a enforcement las reglas claras: matar cualquier proceso que intente leer rutas de secretos, o cortar todo &lt;code>tcp_connect&lt;/code> a destinos fuera de la whitelist de egress. &lt;a href="https://falco.org/">Falco&lt;/a> es la alternativa detection-only sobre eBPF; Tetragon añade enforcement. Estas cámaras producen, además, la evidencia de auditoría: qué ejecutó cada servicio y qué intento se bloqueó.&lt;/p>
&lt;h2 id="capa-7--dato-en-reposo">Capa 7 — Dato en reposo&lt;/h2>
&lt;p>Lo último que protegemos es el dato parado:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Cifrado del almacenamiento.&lt;/strong> Los volúmenes persistentes —donde viven la base vectorial y la base de estado— sobre cifrado de disco (LUKS/dm-crypt) o cifrado a nivel de Ceph. Un disco robado del CPD no revela el corpus.&lt;/li>
&lt;li>&lt;strong>Cifrado de &lt;code>etcd&lt;/code>.&lt;/strong> Recuerda que los &lt;code>Secret&lt;/code> de Kubernetes, una vez descifrados por sealed-secrets o ESO, son objetos normales en &lt;code>etcd&lt;/code>. Hay que activar el &lt;em>encryption at rest&lt;/em> de &lt;code>etcd&lt;/code>, o el secreto está en claro en el plano de control.&lt;/li>
&lt;li>&lt;strong>Credenciales de las bases.&lt;/strong> La contraseña de la base vectorial y la de la base de estado son secretos de primera (capa 1), nunca embebidas en el manifiesto ni en variables de entorno en claro en git.&lt;/li>
&lt;/ul>
&lt;h2 id="tabla-de-exposición-servicio--puerto--egress-externo">Tabla de exposición: servicio × puerto × ¿egress externo?&lt;/h2>
&lt;p>Esta tabla es el insumo para escribir las &lt;code>NetworkPolicy&lt;/code>. La columna de egress es la que decide qué sale del perímetro.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Servicio&lt;/th>
&lt;th>Puerto interno&lt;/th>
&lt;th>Llamantes permitidos&lt;/th>
&lt;th>¿Egress externo?&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>front&lt;/td>
&lt;td>3080&lt;/td>
&lt;td>(ingress humano vía OIDC)&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>gateway (LiteLLM)&lt;/td>
&lt;td>4000&lt;/td>
&lt;td>front&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>vLLM&lt;/td>
&lt;td>8000&lt;/td>
&lt;td>gateway&lt;/td>
&lt;td>&lt;strong>Solo&lt;/strong> &lt;code>huggingface.co&lt;/code> para descarga inicial; cero en operación&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TEI (embeddings/reranker)&lt;/td>
&lt;td>8080&lt;/td>
&lt;td>gateway&lt;/td>
&lt;td>&lt;strong>Solo&lt;/strong> descarga del modelo; cero en operación&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>base vectorial&lt;/td>
&lt;td>6333&lt;/td>
&lt;td>gateway&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>base de estado&lt;/td>
&lt;td>5432&lt;/td>
&lt;td>gateway, front&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>controlador de secretos&lt;/td>
&lt;td>—&lt;/td>
&lt;td>(plano de control)&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>observabilidad&lt;/td>
&lt;td>9090&lt;/td>
&lt;td>scraping interno&lt;/td>
&lt;td>No&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La lectura: &lt;strong>ningún servicio necesita egress externo en operación normal&lt;/strong>. vLLM y TEI lo necesitan &lt;strong>una vez&lt;/strong> para bajar el modelo, y ese permiso puede ser temporal o restringido por FQDN a &lt;code>huggingface.co&lt;/code>. Todo lo demás es egress-deny total. Si un servicio empieza a intentar conexiones de salida que esta tabla no contempla, Tetragon lo registra y, en enforcement, lo corta.&lt;/p>
&lt;h2 id="radio-de-impacto-un-secreto-filtrado-con-y-sin-defensa">Radio de impacto: un secreto filtrado, con y sin defensa&lt;/h2>
&lt;p>El &lt;strong>radio de impacto&lt;/strong> (&lt;em>blast radius&lt;/em>) mide cuánto daño hace un compromiso. Modelémoslo para el peor caso realista: se filtra la credencial de la base de estado.&lt;/p>
&lt;p>&lt;strong>Sin hardening&lt;/strong> (red plana, secreto sin rotar, sin egress-deny, sin detección):&lt;/p>
&lt;ul>
&lt;li>El secreto da acceso a la base de estado desde cualquier pod (red plana → 56 pares abiertos).&lt;/li>
&lt;li>El secreto no rota → es válido &lt;strong>indefinidamente&lt;/strong>; la ventana de explotación es $\infty$ hasta que alguien lo note.&lt;/li>
&lt;li>Sin egress-deny, el atacante vuelca la base entera a un servidor externo.&lt;/li>
&lt;li>Sin Tetragon, nadie se entera hasta el incidente público.&lt;/li>
&lt;li>Radio de impacto: &lt;strong>toda la base de estado, exfiltrada, sin detección, por tiempo indefinido&lt;/strong>.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Con hardening&lt;/strong> (default-deny, ESO + Vault con credenciales dinámicas, egress-deny, Tetragon en enforcement):&lt;/p>
&lt;ul>
&lt;li>El secreto solo es usable desde el pod que tiene ruta de red a la base de estado (1-2 pares, no 56).&lt;/li>
&lt;li>Con credenciales dinámicas de Vault, el secreto &lt;strong>caduca&lt;/strong> —digamos en una ventana $T$ de horas—; pasado $T$, no vale nada.&lt;/li>
&lt;li>El egress-deny impide el volcado a Internet: el atacante puede leer, pero no sacar.&lt;/li>
&lt;li>Tetragon registra el acceso anómalo y, en enforcement, mata el proceso que intenta la conexión de salida.&lt;/li>
&lt;/ul>
&lt;p>La reducción cualitativa es enorme, pero seamos cuantitativos con la ventana. Si un secreto estático vale para siempre y uno rotado con periodo $T$ vale como mucho $T$, y los compromisos llegan a tasa $\lambda$, el número esperado de secretos &lt;em>vivos y explotables&lt;/em> en un instante dado pasa de crecer sin techo a quedar acotado por $\lambda \cdot T$. Con rotación cada 24 h ($T = 1$ día) frente a &amp;ldquo;nunca&amp;rdquo;, la ventana de un secreto concreto cae de meses a un día: una reducción de &lt;strong>uno a dos órdenes de magnitud&lt;/strong> en la exposición temporal. Combinado con la reducción de pares de red ($56 \to \sim 2$, factor $\sim 28\times$) y el egress-deny (de exfiltración posible a imposible por la ruta directa), el radio de impacto se reduce drásticamente.&lt;/p>
&lt;p>&lt;strong>Pero seamos honestos: no llega a cero.&lt;/strong> El atacante con la credencial &lt;em>puede leer&lt;/em> la base de estado durante la ventana $T$ desde el pod comprometido. El hardening convirtió &amp;ldquo;catástrofe indefinida y silenciosa&amp;rdquo; en &amp;ldquo;incidente acotado, detectado y sin exfiltración por la vía directa&amp;rdquo;. Eso es exactamente lo que la defensa en profundidad promete: no la invulnerabilidad, sino que &lt;strong>el coste de un compromiso completo sea alto y su radio, pequeño&lt;/strong>.&lt;/p>
&lt;h2 id="ángulo-ens--nis2">Ángulo ENS / NIS2&lt;/h2>
&lt;p>Estas capas no son higiene voluntaria: materializan controles concretos. El mapeo lo desarrolla en detalle el &lt;a href="https://blog.lo0.es/posts/controles-tecnicos-ens-42001-eu-ai-act/">post de controles técnicos ENS × ISO 42001 × EU AI Act&lt;/a>; aquí, el resumen accionable:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Capa de hardening&lt;/th>
&lt;th>Medida ENS (RD 311/2022)&lt;/th>
&lt;th>Vínculo NIS2 / marco&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Secretos cifrados + rotación&lt;/td>
&lt;td>&lt;code>op.exp.11&lt;/code> (claves criptográficas), &lt;code>mp.info.3&lt;/code> (cifrado)&lt;/td>
&lt;td>Gestión de credenciales; en Cat. Alta, HSM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>NetworkPolicy default-deny + segmentación&lt;/td>
&lt;td>&lt;code>mp.com.1&lt;/code> (perímetro), &lt;code>mp.com.4&lt;/code> (separación de flujos)&lt;/td>
&lt;td>NIS2 art. 21: medidas de seguridad de red&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Control de egress&lt;/td>
&lt;td>&lt;code>mp.com.1&lt;/code> + &lt;code>op.mon.1&lt;/code>&lt;/td>
&lt;td>Prevención de exfiltración&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>mTLS interno&lt;/td>
&lt;td>&lt;code>mp.com.2-3&lt;/code> (confidencialidad/integridad en tránsito)&lt;/td>
&lt;td>TLS obligatorio&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Pod Security restricted&lt;/td>
&lt;td>&lt;code>op.exp.2&lt;/code> (config endurecida)&lt;/td>
&lt;td>Hardening de configuración (CIS)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Trivy + cosign + admission&lt;/td>
&lt;td>&lt;code>op.exp.6&lt;/code> (código dañino), &lt;code>op.ext.3&lt;/code> (cadena de suministro)&lt;/td>
&lt;td>NIS2 supply chain&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OIDC + claves virtuales + RBAC&lt;/td>
&lt;td>&lt;code>op.acc.1-2-5&lt;/code> (identificación, acceso, autenticación)&lt;/td>
&lt;td>MFA en Cat. Alta&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Tetragon runtime&lt;/td>
&lt;td>&lt;code>op.mon.1&lt;/code> (detección de intrusión)&lt;/td>
&lt;td>Monitorización y respuesta&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cifrado en reposo + etcd&lt;/td>
&lt;td>&lt;code>mp.info.3&lt;/code> (cifrado), &lt;code>mp.si&lt;/code> (soportes)&lt;/td>
&lt;td>Dato en reposo&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La nota honesta para auditoría: &lt;strong>el hardening reduce el riesgo, no lo elimina&lt;/strong>, y la madurez se demuestra &lt;strong>priorizando por impacto&lt;/strong>. Un auditor competente no quiere ver las nueve capas a medias; quiere ver que el egress-deny del activo crítico (el corpus) y la gestión de secretos están sólidos antes que el mTLS perfecto entre servicios de baja sensibilidad. Para el contexto de gestión y gobernanza, ver también &lt;a href="https://blog.lo0.es/posts/iso-42001-aims-llm-on-premise/">ISO/IEC 42001 como AIMS&lt;/a> y el &lt;a href="https://blog.lo0.es/posts/eu-ai-act-mapeo-arquitectura-llm-on-premise/">mapeo del EU AI Act sobre la arquitectura&lt;/a>.&lt;/p>
&lt;h2 id="aplicado-al-clúster-genérico-4h100">Aplicado al clúster genérico 4×H100&lt;/h2>
&lt;p>En un despliegue real sobre 4×H100 SXM 80GB, no se endurecen las nueve capas a la vez. El orden, priorizado por impacto:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Egress-deny sobre vLLM, TEI y la base vectorial, primero.&lt;/strong> Es la barrera que impide que el corpus salga del perímetro, y se pone en cuanto los servicios arrancan. Permite egress por FQDN a &lt;code>huggingface.co&lt;/code> solo durante la descarga inicial del modelo; después, cero. Es la capa de mayor retorno por hora invertida.&lt;/li>
&lt;li>&lt;strong>Secretos del motor de inferencia y de las bases, fuera de git.&lt;/strong> El token de Hugging Face que usa vLLM para descargar el modelo, las contraseñas de la base vectorial y de la base de estado, la clave maestra del gateway: a sealed-secrets o, si ya hay requisito de rotación, a ESO + Vault. Nunca en el &lt;code>values.yaml&lt;/code> en claro.&lt;/li>
&lt;li>&lt;strong>NetworkPolicy default-deny + la whitelist de 6 aristas.&lt;/strong> El gateway y la base vectorial son los servicios más expuestos —el gateway porque recibe todo el tráfico, la base vectorial porque custodia el RAG embebido—. Cerrar todo lo que no sea la whitelist recorta el pivoteo lateral del $\sim N^2$ a las 6 aristas reales.&lt;/li>
&lt;li>&lt;strong>Pod Security &lt;code>restricted&lt;/code> en el namespace de inferencia&lt;/strong>, incluido el pod de vLLM con GPU (no necesita privilegios para usar la H100).&lt;/li>
&lt;li>&lt;strong>cosign + admission&lt;/strong> para que solo entren imágenes firmadas; &lt;strong>Trivy&lt;/strong> en el pipeline de GitOps de &lt;a href="https://blog.lo0.es/posts/gitops-stack-inferencia-llm-flux/">Flux&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Tetragon en observación&lt;/strong>, baseline, y luego enforcement sobre el egress de los pods sensibles.&lt;/li>
&lt;li>&lt;strong>Cifrado en reposo&lt;/strong> de los volúmenes de las bases y de &lt;code>etcd&lt;/code>.&lt;/li>
&lt;/ol>
&lt;p>El gateway y la base vectorial son los dos que endurezco primero dentro de la whitelist: el gateway por ser la cara expuesta, la base vectorial por contener el activo que el egress-deny protege. El resto se construye encima, capa a capa, asumiendo siempre que la anterior puede fallar.&lt;/p>
&lt;h2 id="lo-que-el-hardening-no-resuelve">Lo que el hardening NO resuelve&lt;/h2>
&lt;p>Para cerrar con honestidad, lo que estas capas &lt;strong>no&lt;/strong> cubren y necesita otras piezas:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Ataques a nivel de prompt&lt;/strong> (jailbreak, inyección indirecta vía el corpus RAG): eso es trabajo de &lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">guardrails&lt;/a> y &lt;a href="https://blog.lo0.es/posts/llm-guard-fundamentos/">LLM Guard&lt;/a>, no de NetworkPolicy.&lt;/li>
&lt;li>&lt;strong>Agentes con permisos legítimos que hacen algo dañino&lt;/strong>: el aislamiento de runtime del &lt;a href="https://blog.lo0.es/posts/aislar-agentes-ia-cliente-cluster/">post de aislar agentes&lt;/a> acota qué puede tocar un agente, pero un permiso concedido es un permiso usable.&lt;/li>
&lt;li>&lt;strong>El factor humano&lt;/strong>: un secreto bien gestionado pero compartido por Slack sigue filtrado. La rotación acota la ventana, no elimina el error.&lt;/li>
&lt;li>&lt;strong>Vulnerabilidades zero-day&lt;/strong> en los propios componentes: Trivy detecta lo conocido; lo desconocido pasa hasta que se publica el CVE.&lt;/li>
&lt;/ul>
&lt;p>El hardening es un multiplicador del coste de atacar, no un muro infranqueable. Su valor está en hacer que un compromiso individual quede &lt;strong>acotado, detectado y sin salida&lt;/strong> — y eso, para un sistema soberano que custodia datos sensibles, es exactamente el objetivo.&lt;/p>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/gitops-stack-inferencia-llm-flux/">GitOps del stack de inferencia con Flux&lt;/a> — la pieza hermana: GitOps reconcilia el estado, y comparte con este post el problema huevo-gallina de los secretos.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/controles-tecnicos-ens-42001-eu-ai-act/">Controles técnicos: ENS × ISO 42001 × EU AI Act&lt;/a> — el mapeo detallado de cada capa de hardening a medida ENS, control 42001 y artículo del AI Act.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/iso-42001-aims-llm-on-premise/">ISO/IEC 42001: el AIMS del LLM on-premise&lt;/a> — el sistema de gestión que enmarca el hardening como control documentado.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/eu-ai-act-mapeo-arquitectura-llm-on-premise/">EU AI Act: mapeo sobre la arquitectura LLM on-premise&lt;/a> — los artículos de robustez y ciberseguridad (Art. 15) que estas capas satisfacen.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/llm-guard-fundamentos/">LLM Guard: fundamentos&lt;/a> — la capa de seguridad a nivel de prompt/contenido que el hardening de infraestructura no cubre.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety en LLMs&lt;/a> — el WAF semántico complementario a la NetworkPolicy.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/aislar-agentes-ia-cliente-cluster/">Aislar agentes de IA: del workstation al clúster&lt;/a> — el modelo de amenaza del aislamiento de runtime.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/runbook-aislar-agentes-ia-bubblewrap-tetragon/">Runbook: enjaular al agente de IA con bubblewrap y Tetragon&lt;/a> — el procedimiento operativo de Tetragon (observar primero, bloquear después) referenciado en la capa 6.&lt;/li>
&lt;/ul></description></item><item><title>Runbook: enjaular al agente de IA — bubblewrap en el cliente, Tetragon en el cluster</title><link>https://blog.lo0.es/posts/runbook-aislar-agentes-ia-bubblewrap-tetragon/</link><pubDate>Tue, 09 Jun 2026 17:00:00 +0200</pubDate><guid>https://blog.lo0.es/posts/runbook-aislar-agentes-ia-bubblewrap-tetragon/</guid><description>&lt;blockquote>
&lt;p>Compañero &lt;strong>operativo&lt;/strong> de &lt;a href="https://blog.lo0.es/posts/aislar-agentes-ia-cliente-cluster/">El contratista con la llave maestra&lt;/a>. Aquel post explica el &lt;em>porqué&lt;/em> y el &lt;em>dónde&lt;/em> —el modelo de amenaza, las cinco familias de aislamiento, qué dominio usa cada una—; este es el &lt;em>cómo&lt;/em>, con comandos. Si no lo has leído, léelo antes: aquí doy por sabido qué es el radio de explosión, por qué &lt;code>bwrap&lt;/code> corre sin root y qué vigila Tetragon. El procedimiento va en dos tracks independientes —&lt;strong>cliente&lt;/strong> y &lt;strong>cluster&lt;/strong>— porque, como argumenta el post hermano, el control se extrapola pero la primitiva se reescribe.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Dos procedimientos reproducibles. &lt;strong>Cliente (workstation):&lt;/strong> instala &lt;code>ai-jail&lt;/code> (envuelve &lt;code>bubblewrap&lt;/code>), genera el &lt;code>.ai-jail&lt;/code> por proyecto, audita con &lt;code>--dry-run&lt;/code>, fija las allowlists con &lt;code>--bootstrap&lt;/code>, usa &lt;code>--lockdown&lt;/code> para lo que no te fíes, y deja al agente sin permiso de &lt;code>git push&lt;/code>. &lt;strong>Cluster (RKE2 con Cilium + Tetragon):&lt;/strong> pon el baseline de pod (&lt;code>securityContext&lt;/code> sin privilegios, &lt;code>seccomp: RuntimeDefault&lt;/code>, &lt;code>NetworkPolicy&lt;/code> default-deny), mete el pod del agente no confiable en una microVM con &lt;code>runtimeClassName: kata&lt;/code>, y despliega las &lt;code>TracingPolicy&lt;/code> de Tetragon en &lt;strong>dos fases&lt;/strong> —observar con &lt;code>action: Post&lt;/code> para levantar el baseline, luego promover a &lt;code>action: Sigkill&lt;/code> sobre &lt;code>tcp_connect&lt;/code> (egress) y &lt;code>security_file_open&lt;/code> (rutas de secretos)—. La regla de oro de la fase Tetragon: &lt;strong>adopta primero, bloquea después&lt;/strong>; nunca metas un &lt;code>Sigkill&lt;/code> en producción sin haber visto antes los eventos en modo observación.&lt;/p>
&lt;h2 id="el-flujo-de-los-dos-tracks">El flujo de los dos tracks&lt;/h2>
&lt;div class="diagram" style="max-width:800px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 800 250" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Dos tracks operativos: cliente (instalar, configurar, bootstrap, lockdown) y cluster (baseline, RuntimeClass, observar, enforce)">
&lt;defs>&lt;marker id="rm2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="400" y="22" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">Track CLIENTE — workstation&lt;/text>
&lt;rect x="20" y="36" width="150" height="46" rx="7" fill="#d4ecff" stroke="#1f5fa8" stroke-width="1.4"/>
&lt;text x="95" y="56" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#0d3a66">1 · Instalar&lt;/text>
&lt;text x="95" y="72" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#0d3a66">ai-jail + bwrap&lt;/text>
&lt;rect x="200" y="36" width="150" height="46" rx="7" fill="#d4ecff" stroke="#1f5fa8" stroke-width="1.4"/>
&lt;text x="275" y="56" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#0d3a66">2 · .ai-jail&lt;/text>
&lt;text x="275" y="72" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#0d3a66">--dry-run&lt;/text>
&lt;rect x="380" y="36" width="150" height="46" rx="7" fill="#d4ecff" stroke="#1f5fa8" stroke-width="1.4"/>
&lt;text x="455" y="56" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#0d3a66">3 · --bootstrap&lt;/text>
&lt;text x="455" y="72" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#0d3a66">allow/deny/ask&lt;/text>
&lt;rect x="560" y="36" width="150" height="46" rx="7" fill="#d4ecff" stroke="#1f5fa8" stroke-width="1.4"/>
&lt;text x="635" y="52" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#0d3a66">4 · lockdown&lt;/text>
&lt;text x="635" y="68" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#0d3a66">+ git sin push&lt;/text>
&lt;path d="M170,59 L198,59" stroke="#666" stroke-width="1.5" fill="none" marker-end="url(#rm2)"/>
&lt;path d="M350,59 L378,59" stroke="#666" stroke-width="1.5" fill="none" marker-end="url(#rm2)"/>
&lt;path d="M530,59 L558,59" stroke="#666" stroke-width="1.5" fill="none" marker-end="url(#rm2)"/>
&lt;line x1="20" y1="118" x2="780" y2="118" stroke="#ccc" stroke-width="1" stroke-dasharray="3 3"/>
&lt;text x="400" y="150" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">Track CLUSTER — RKE2 + Cilium/Tetragon&lt;/text>
&lt;rect x="20" y="164" width="150" height="46" rx="7" fill="#e6d9f2" stroke="#5a2db0" stroke-width="1.4"/>
&lt;text x="95" y="184" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#42208a">1 · Baseline&lt;/text>
&lt;text x="95" y="200" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#42208a">secCtx+NetPol&lt;/text>
&lt;rect x="200" y="164" width="150" height="46" rx="7" fill="#e6d9f2" stroke="#5a2db0" stroke-width="1.4"/>
&lt;text x="275" y="184" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#42208a">2 · RuntimeClass&lt;/text>
&lt;text x="275" y="200" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#42208a">kata microVM&lt;/text>
&lt;rect x="380" y="164" width="150" height="46" rx="7" fill="#fde9d6" stroke="#a85a00" stroke-width="1.6"/>
&lt;text x="455" y="184" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#8a4a00">3 · Observar&lt;/text>
&lt;text x="455" y="200" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#8a4a00">Tetragon · Post&lt;/text>
&lt;rect x="560" y="164" width="150" height="46" rx="7" fill="#fbd4b8" stroke="#a85a00" stroke-width="1.8"/>
&lt;text x="635" y="184" text-anchor="middle" font-family="sans-serif" font-size="11" font-weight="600" fill="#8a4a00">4 · Enforce&lt;/text>
&lt;text x="635" y="200" text-anchor="middle" font-family="sans-serif" font-size="9.5" fill="#8a4a00">Tetragon · Sigkill&lt;/text>
&lt;path d="M170,187 L198,187" stroke="#666" stroke-width="1.5" fill="none" marker-end="url(#rm2)"/>
&lt;path d="M350,187 L378,187" stroke="#666" stroke-width="1.5" fill="none" marker-end="url(#rm2)"/>
&lt;path d="M530,187 L558,187" stroke="#666" stroke-width="1.5" fill="none" marker-end="url(#rm2)"/>
&lt;text x="400" y="236" text-anchor="middle" font-family="sans-serif" font-size="10" font-style="italic" fill="#555">adopta primero (observar), bloquea después (enforce)&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h1 id="track-a--cliente-workstation-del-desarrollador">Track A — Cliente (workstation del desarrollador)&lt;/h1>
&lt;h2 id="a0--instalar-ai-jail-y-bubblewrap">A0 — Instalar ai-jail y bubblewrap&lt;/h2>
&lt;p>&lt;code>ai-jail&lt;/code> envuelve el sandbox; en Linux necesita &lt;code>bubblewrap&lt;/code> aparte, en macOS no necesita dependencia extra.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ai-jail (macOS y Linux)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">brew tap akitaonrails/tap &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> brew install ai-jail
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># o, con cargo:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cargo install ai-jail
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># o, con mise:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">mise use -g ubi:akitaonrails/ai-jail
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># bubblewrap en Linux (elige tu distro)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo pacman -S bubblewrap &lt;span class="c1"># Arch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo apt install bubblewrap &lt;span class="c1"># Debian / Ubuntu&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo dnf install bubblewrap &lt;span class="c1"># Fedora&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Comprueba que el binario está y que &lt;code>bwrap&lt;/code> corre sin root (no debe pedir &lt;code>sudo&lt;/code>):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ai-jail --version
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bwrap --ro-bind / / --unshare-all &lt;span class="nb">echo&lt;/span> &lt;span class="s2">&amp;#34;bwrap ok sin root&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Si &lt;code>bwrap&lt;/code> falla pidiendo privilegios, tu kernel tiene los &lt;em>unprivileged user namespaces&lt;/em> deshabilitados; habilítalos (&lt;code>sysctl kernel.unprivileged_userns_clone=1&lt;/code> en Debian/Ubuntu antiguos) antes de seguir.&lt;/p>
&lt;h2 id="a1--el-fichero-ai-jail-por-proyecto">A1 — El fichero .ai-jail por proyecto&lt;/h2>
&lt;p>En el primer arranque dentro del proyecto, &lt;code>ai-jail&lt;/code> crea un &lt;code>.ai-jail&lt;/code> (TOML) &lt;strong>commiteable al repo&lt;/strong>: cualquier compañero que clone hereda la misma política.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> ~/Projects/mi-app
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ai-jail claude &lt;span class="c1"># crea .ai-jail y lanza Claude Code dentro del sandbox&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El fichero generado:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># .ai-jail — configuración del sandbox (commitéalo al repo)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">command&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;claude&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">rw_maps&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[]&lt;/span> &lt;span class="c"># directorios extra con escritura&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">ro_maps&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[]&lt;/span> &lt;span class="c"># directorios extra de solo lectura&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Antes de confiar en el sandbox, audítalo.&lt;/strong> &lt;code>--dry-run --verbose&lt;/code> imprime cada punto de montaje, cada flag de aislamiento y el comando &lt;code>bwrap&lt;/code> completo, sin ejecutar nada:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ai-jail --dry-run --verbose claude
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lee la salida y confirma tres cosas: que &lt;code>$HOME&lt;/code> se monta como tmpfs (no el real), que &lt;code>~/.ssh&lt;/code>, &lt;code>~/.aws&lt;/code> y &lt;code>~/.gnupg&lt;/code> &lt;strong>no aparecen&lt;/strong> entre los montajes, y que el único directorio con escritura es el del proyecto. Si necesitas un directorio extra:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ai-jail --rw-map ~/Projects/shared-lib claude &lt;span class="c1"># extra con escritura&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ai-jail --map /opt/datasets claude &lt;span class="c1"># extra de solo lectura&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Otros agentes, mismo binario:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ai-jail codex
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ai-jail opencode
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ai-jail bash &lt;span class="c1"># shell pelado para depurar el sandbox&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">ai-jail -- python script.py &lt;span class="c1"># cualquier comando&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="a2--las-allowlists-de-permisos-con---bootstrap">A2 — Las allowlists de permisos con &amp;ndash;bootstrap&lt;/h2>
&lt;p>&lt;code>--bootstrap&lt;/code> genera las configuraciones de permisos de cada agente, con allow/deny/ask sensatos, y hace backup antes de sobrescribir:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ai-jail --bootstrap
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lo que produce, en resumen:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Agente&lt;/th>
&lt;th>Fichero&lt;/th>
&lt;th>Política base&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Claude Code&lt;/td>
&lt;td>&lt;code>~/.claude/settings.json&lt;/code>&lt;/td>
&lt;td>&lt;strong>allow&lt;/strong>: &lt;code>git status/diff/log&lt;/code>, &lt;code>ls&lt;/code>, &lt;code>grep&lt;/code>, &lt;code>cargo&lt;/code>, &lt;code>npm&lt;/code>, &lt;code>python&lt;/code>, &lt;code>docker compose&lt;/code> · &lt;strong>ask&lt;/strong>: &lt;code>git push&lt;/code>, &lt;code>rm&lt;/code>, &lt;code>docker run&lt;/code> · &lt;strong>deny&lt;/strong>: &lt;code>rm -rf&lt;/code>, &lt;code>sudo&lt;/code>, &lt;code>chmod 777&lt;/code>, &lt;code>git push --force&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Codex&lt;/td>
&lt;td>&lt;code>~/.codex/config.toml&lt;/code>&lt;/td>
&lt;td>&lt;code>approval_policy = &amp;quot;on-request&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OpenCode&lt;/td>
&lt;td>&lt;code>~/.config/opencode/opencode.json&lt;/code>&lt;/td>
&lt;td>permisos de &lt;code>bash&lt;/code>, &lt;code>edit&lt;/code>, &lt;code>write&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>La clave operativa: &lt;code>git push&lt;/code> está en &lt;strong>ask&lt;/strong>, no en &lt;strong>allow&lt;/strong>, y &lt;code>git push --force&lt;/code> en &lt;strong>deny&lt;/strong>. El agente puede commitear, ramear y rebasar localmente cuanto quiera; nada de eso toca el remoto. (Si usas el &lt;code>/sandbox&lt;/code> de Claude Code, fija además &lt;code>&amp;quot;allowUnsandboxedCommands&amp;quot;: false&lt;/code> para cerrar el &lt;em>escape hatch&lt;/em> &lt;code>dangerouslyDisableSandbox&lt;/code>, que de fábrica es opt-out.)&lt;/p>
&lt;h2 id="a3--lockdown-para-lo-que-no-te-fíes">A3 — Lockdown para lo que no te fíes&lt;/h2>
&lt;p>Para auditar código de terceros o correr un agente sobre un proyecto que no conoces, &lt;code>--lockdown&lt;/code> va más allá: proyecto montado en &lt;strong>read-only&lt;/strong>, GPU/Docker/display deshabilitados, &lt;code>--rw-map&lt;/code>/&lt;code>--map&lt;/code> ignorados, &lt;code>$HOME&lt;/code> tmpfs puro sin dotfiles del host, red cortada con &lt;code>--unshare-net&lt;/code> y environment limpiado con &lt;code>--clearenv&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">ai-jail --lockdown bash
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Es el sandbox más restrictivo posible sin llegar a una VM. Úsalo como defecto mental para todo lo que no sea tu propio código en tu propia máquina.&lt;/p>
&lt;h2 id="a4--la-red-de-seguridad-git-sin-push">A4 — La red de seguridad: git sin push&lt;/h2>
&lt;p>No es un flag, es una propiedad del entorno que cambia el cálculo de riesgo. Si el proyecto está en git con remoto, y el agente &lt;strong>no&lt;/strong> tiene permiso de &lt;code>push&lt;/code>, el peor caso —que corrompa cada fichero del proyecto— se revierte con:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git checkout . &lt;span class="c1"># vuelve al último commit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># y si tocó .git (improbable): borra el dir y re-clona&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>El remoto nunca se tocó. &lt;strong>Sandbox para el filesystem + git para el código + push manual&lt;/strong> es ya un nivel razonable para uso diario: &lt;code>ai-jail&lt;/code> protege tus datos y el sistema, git protege el código, y la decisión de publicar sigue siendo tuya.&lt;/p>
&lt;hr>
&lt;h1 id="track-b--cluster-rke2-con-cilium--tetragon">Track B — Cluster (RKE2 con Cilium + Tetragon)&lt;/h1>
&lt;p>El agente no confiable —o la inferencia que ejecuta código generado— corre como pod. El mismo principio del cliente, otras primitivas. Asumimos un cluster genérico RKE2 con Cilium como CNI y Tetragon ya desplegado (el &lt;code>DaemonSet&lt;/code> del agente eBPF en cada nodo).&lt;/p>
&lt;h2 id="b0--el-baseline-del-pod">B0 — El baseline del pod&lt;/h2>
&lt;p>Antes de cualquier eBPF, lo de serie. &lt;code>securityContext&lt;/code> sin privilegios, raíz read-only, seccomp por defecto:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Pod&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ai-agent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">agentes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ai-agent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">securityContext&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsNonRoot&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runAsUser&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">10001&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">seccompProfile&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">RuntimeDefault&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">containers&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">agent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">registry.interno/ai-agent:pinned&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">securityContext&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">allowPrivilegeEscalation&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">readOnlyRootFilesystem&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">drop&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;ALL&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumeMounts&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">name: work, mountPath&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">/work } &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># único escribible&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">work&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">emptyDir&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Y el corte de egress por defecto —el gemelo cluster del &lt;code>--unshare-net&lt;/code>—. NetworkPolicy default-deny de salida en el namespace, abriendo solo DNS y lo imprescindible:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">networking.k8s.io/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">NetworkPolicy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">default-deny-egress&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">agentes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">podSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">policyTypes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;Egress&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">egress&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">to&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">namespaceSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>{&lt;span class="w"> &lt;/span>&lt;span class="nt">kubernetes.io/metadata.name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kube-system }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol: UDP, port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">53&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- {&lt;span class="w"> &lt;/span>&lt;span class="nt">protocol: TCP, port&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">53&lt;/span>&lt;span class="w"> &lt;/span>}&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="b1--runtimeclass-kata-el-pod-no-confiable-en-su-propia-microvm">B1 — RuntimeClass Kata: el pod no confiable en su propia microVM&lt;/h2>
&lt;p>Para código realmente no confiable, sácalo del kernel compartido. Con Kata desplegado existe un &lt;code>RuntimeClass&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">node.k8s.io/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">RuntimeClass&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kata&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">handler&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kata&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Y el pod lo pide con una línea —&lt;code>runtimeClassName: kata&lt;/code>—, ejecutándose en su propia microVM con kernel dedicado en lugar de compartir el del nodo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">runtimeClassName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kata &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># ← el pod corre en una microVM, no en el kernel del nodo&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># ...resto igual que B0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Es el gemelo cluster del aislamiento por construcción: un exploit de kernel dentro del pod no alcanza al nodo.&lt;/p>
&lt;h2 id="b2--tetragon-fase-observación-post">B2 — Tetragon, fase observación (Post)&lt;/h2>
&lt;p>Ahora la capa que distingue una plataforma con visibilidad de runtime. &lt;strong>Primero observar, nunca matar de entrada.&lt;/strong> Una &lt;code>TracingPolicyNamespaced&lt;/code> —scoped al namespace y a la etiqueta del agente— que reporta (no mata) tres cosas: ejecuciones de proceso, conexiones de red y aperturas de rutas sensibles. &lt;code>action: Post&lt;/code> solo emite el evento.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cilium.io/v1alpha1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TracingPolicyNamespaced&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">agente-observa&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">agentes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">podSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ai-agent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kprobes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># --- conexiones salientes ---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">call&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;tcp_connect&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">syscall&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">index&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;sock&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selectors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">matchActions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Post&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># --- aperturas de ficheros sensibles ---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">call&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;security_file_open&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">syscall&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">index&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;file&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selectors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">matchArgs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">index&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">operator&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Prefix&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">values&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;/var/run/secrets&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;/work/.git/config&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchActions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Post&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>(Las ejecuciones de proceso no necesitan kprobe: Tetragon emite &lt;code>process_exec&lt;/code>/&lt;code>process_exit&lt;/code> de forma nativa.) Despliega y observa los eventos en vivo desde el pod de Tetragon del nodo:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f agente-observa.yaml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># eventos legibles, filtrando por el namespace:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kubectl &lt;span class="nb">exec&lt;/span> -n kube-system ds/tetragon -c tetragon -- &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> tetra getevents -o compact --namespace agentes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Deja esto rodando una jornada típica del agente. Apunta a &lt;strong>qué destinos&lt;/strong> conecta de verdad (tu registry interno, tu mirror de HF, tu endpoint de vLLM) y &lt;strong>qué rutas&lt;/strong> abre. Eso es tu baseline: la lista de lo legítimo. Sin este paso, un &lt;code>Sigkill&lt;/code> mata trabajo bueno y te genera un incidente de disponibilidad —justo lo que el ENS te pide evitar—.&lt;/p>
&lt;h2 id="b3--tetragon-fase-enforcement-sigkill">B3 — Tetragon, fase enforcement (Sigkill)&lt;/h2>
&lt;p>Con el baseline en la mano, promueve a bloqueo. Dos reglas. La primera: &lt;strong>mata cualquier conexión cuyo destino no esté en la allowlist&lt;/strong> —&lt;code>NotDAddr&lt;/code> invierte el match: dispara para todo lo que &lt;em>no&lt;/em> sea esas redes—. La segunda: &lt;strong>mata cualquier intento de abrir una ruta de secretos&lt;/strong>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cilium.io/v1alpha1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">TracingPolicyNamespaced&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">agente-enforce&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">agentes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">podSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ai-agent&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kprobes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># --- egress: mata todo lo que NO sea la allowlist ---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">call&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;tcp_connect&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">syscall&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">index&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;sock&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selectors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">matchArgs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">index&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">operator&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;NotDAddr&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">values&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;127.0.0.1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;10.0.0.0/8&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># red interna del cluster&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;172.16.10.20&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># registry interno (ejemplo)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchActions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Sigkill&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># --- lectura de secretos: mata el proceso ---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">call&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;security_file_open&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">syscall&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">false&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">args&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">index&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">type&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;file&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">selectors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">matchArgs&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">index&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">operator&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;Prefix&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">values&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;/var/run/secrets/kubernetes.io/serviceaccount/token&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;/work/.ssh&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchActions&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">action&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Sigkill&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">kubectl apply -f agente-enforce.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Ahora el agente puede hacer lo que quiera dentro del pod, pero &lt;strong>en el instante&lt;/strong> en que intenta conectar a un destino no permitido o leer el token de la service account, Tetragon lo mata en el kernel —antes de que el paquete salga o el &lt;code>read&lt;/code> devuelva bytes—. Es el gemelo cluster de la blocklist de &lt;code>curl&lt;/code> y del &lt;code>~/.ssh&lt;/code> no montado, pero aplicado en runtime y sobre &lt;em>cualquier&lt;/em> binario, no solo los que conoces.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Aviso operativo.&lt;/strong> El enforcement con &lt;code>Sigkill&lt;/code> requiere kernel reciente con soporte de la acción en eBPF (5.10+ es seguro). Despliega &lt;code>agente-enforce&lt;/code> primero en un namespace de pruebas, y mantén &lt;code>agente-observa&lt;/code> activo en paralelo: si el bloqueo dispara, el evento &lt;code>Post&lt;/code> te dice exactamente qué lo provocó. Adopta primero, bloquea después.&lt;/p>
&lt;/blockquote>
&lt;h2 id="la-tabla-de-equivalencias-cliente--cluster">La tabla de equivalencias cliente ↔ cluster&lt;/h2>
&lt;p>El mismo vector, las dos primitivas. Esto es &amp;ldquo;extrapolar la tecnología&amp;rdquo; hecho explícito:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Vector de amenaza&lt;/th>
&lt;th>Cliente (workstation)&lt;/th>
&lt;th>Cluster (RKE2)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>$HOME&lt;/code> / raíz escribible&lt;/td>
&lt;td>&lt;code>$HOME&lt;/code> como tmpfs efímero (&lt;code>bwrap&lt;/code>)&lt;/td>
&lt;td>&lt;code>readOnlyRootFilesystem: true&lt;/code> + &lt;code>emptyDir&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Egress arbitrario&lt;/td>
&lt;td>blocklist &lt;code>curl&lt;/code>/&lt;code>wget&lt;/code> · &lt;code>--unshare-net&lt;/code>&lt;/td>
&lt;td>NetworkPolicy default-deny + Tetragon &lt;code>NotDAddr&lt;/code>→&lt;code>Sigkill&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Lectura de secretos&lt;/td>
&lt;td>&lt;code>~/.ssh&lt;/code>/&lt;code>~/.aws&lt;/code>/&lt;code>~/.gnupg&lt;/code> no montados&lt;/td>
&lt;td>secretos fuera del pod + Tetragon &lt;code>security_file_open&lt;/code>→&lt;code>Sigkill&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Escape del kernel&lt;/td>
&lt;td>Landlock (2ª barrera VFS)&lt;/td>
&lt;td>&lt;code>runtimeClassName: kata&lt;/code> (microVM, kernel propio)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sin escape hatch&lt;/td>
&lt;td>proceso dentro de &lt;code>bwrap&lt;/code>, sin salida&lt;/td>
&lt;td>sin &lt;code>privileged&lt;/code>, &lt;code>drop ALL&lt;/code>, &lt;code>allowPrivilegeEscalation:false&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Daño al código&lt;/td>
&lt;td>git remoto sin &lt;code>push&lt;/code> → &lt;code>git checkout .&lt;/code>&lt;/td>
&lt;td>GitOps + revisión de PR, el agente no aplica a &lt;code>main&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Visibilidad&lt;/td>
&lt;td>&lt;code>--dry-run --verbose&lt;/code> (estático, pre-run)&lt;/td>
&lt;td>Tetragon &lt;code>tetra getevents&lt;/code> (dinámico, en runtime)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="checklist-de-gotchas">Checklist de gotchas&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>No metas un &lt;code>Sigkill&lt;/code> sin pasar por &lt;code>Post&lt;/code>.&lt;/strong> El baseline de observación no es opcional: es lo que separa &amp;ldquo;bloquear un C2&amp;rdquo; de &amp;ldquo;tirar tu propio job de fine-tuning&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>El &lt;code>.ai-jail&lt;/code> se commitea; los secretos no.&lt;/strong> El TOML es política, no credenciales. Verifica que no metes rutas con datos sensibles en &lt;code>rw_maps&lt;/code>.&lt;/li>
&lt;li>&lt;strong>&lt;code>readOnlyRootFilesystem&lt;/code> rompe apps que escriben en &lt;code>/tmp&lt;/code>.&lt;/strong> Monta un &lt;code>emptyDir&lt;/code> en &lt;code>/tmp&lt;/code> además del de trabajo.&lt;/li>
&lt;li>&lt;strong>NetworkPolicy sin regla de DNS deja al pod ciego.&lt;/strong> Abre el puerto 53 a &lt;code>kube-system&lt;/code> o nada resuelve.&lt;/li>
&lt;li>&lt;strong>Kata no es gratis.&lt;/strong> Añade latencia de arranque y no todo workload con dispositivos especiales (GPU passthrough) encaja; resérvalo para lo no confiable, no para todo.&lt;/li>
&lt;li>&lt;strong>El &lt;code>/sandbox&lt;/code> de Claude Code no cubre MCP ni hooks&lt;/strong> salvo que actives &lt;code>sandbox-runtime&lt;/code>. Si tu agente usa servidores MCP, asume que corren con permisos completos hasta que lo hagas.&lt;/li>
&lt;li>&lt;strong>&lt;code>NotDAddr&lt;/code> con IPs literales envejece mal.&lt;/strong> Documenta la allowlist y revísala cuando cambie el registry o el endpoint de inferencia; considera CIDRs internos estables en vez de IPs sueltas.&lt;/li>
&lt;/ul>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/hardening-secretos-stack-llm-soberano/">Hardening y secretos del stack LLM soberano: defensa en profundidad&lt;/a> — el hardening del cluster completo más allá del aislamiento del agente.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/aislar-agentes-ia-cliente-cluster/">El contratista con la llave maestra: aislar agentes de IA del workstation al cluster&lt;/a> — el panorama que este runbook ejecuta: modelo de amenaza, las cinco familias de aislamiento y por qué cliente y cluster usan primitivas distintas.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/cilium-ebpf-dranet-numa-de-red-inferencia/">La puerta de la cocina que el maître no miró: Cilium eBPF y DRANET&lt;/a> — la capa eBPF de Cilium sobre la que Tetragon engancha sus kprobes; el datapath que ya tienes en el cluster.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/controles-tecnicos-ens-42001-eu-ai-act/">Controles técnicos: ENS × ISO 42001 × EU AI Act&lt;/a> — los eventos de Tetragon como evidencia técnica de &lt;code>op.mon&lt;/code>/&lt;code>op.exp&lt;/code>; el enforcement como medida de protección.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/guardrails-safety-llm/">Guardrails y safety en LLM&lt;/a> — la mitigación en el plano del contenido; este runbook, la del plano de la ejecución.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/posts/siete-fases-despliegue-plataforma-llm-on-premise/">Siete fases de despliegue de una plataforma LLM on-premise&lt;/a> — dónde encaja el endurecimiento de runtime en la secuencia de despliegue (F4 identidad/políticas, F5 plataforma).&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>ai-jail (Fabio Akita), GPL-3.0: &lt;a href="https://github.com/akitaonrails/ai-jail">https://github.com/akitaonrails/ai-jail&lt;/a>&lt;/li>
&lt;li>bubblewrap: &lt;a href="https://github.com/containers/bubblewrap">https://github.com/containers/bubblewrap&lt;/a>&lt;/li>
&lt;li>Landlock LSM: &lt;a href="https://landlock.io">https://landlock.io&lt;/a>&lt;/li>
&lt;li>Tetragon — TracingPolicy: &lt;a href="https://tetragon.io/docs/concepts/tracing-policy/">https://tetragon.io/docs/concepts/tracing-policy/&lt;/a>&lt;/li>
&lt;li>Tetragon — enforcement (Sigkill/Override): &lt;a href="https://tetragon.io/docs/concepts/enforcement/">https://tetragon.io/docs/concepts/enforcement/&lt;/a>&lt;/li>
&lt;li>Kata Containers — Kubernetes RuntimeClass: &lt;a href="https://katacontainers.io">https://katacontainers.io&lt;/a>&lt;/li>
&lt;li>Kubernetes — Pod Security &amp;amp; seccomp: &lt;a href="https://kubernetes.io/docs/tutorials/security/seccomp/">https://kubernetes.io/docs/tutorials/security/seccomp/&lt;/a>&lt;/li>
&lt;li>Kubernetes — Network Policies: &lt;a href="https://kubernetes.io/docs/concepts/services-networking/network-policies/">https://kubernetes.io/docs/concepts/services-networking/network-policies/&lt;/a>&lt;/li>
&lt;li>Cilium: &lt;a href="https://cilium.io">https://cilium.io&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>