<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Spiffe on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/spiffe/</link><description>Recent content in Spiffe on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sat, 12 Sep 2026 07:30:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/spiffe/index.xml" rel="self" type="application/rss+xml"/><item><title>Keycloak in an AI platform: where it fits, what it solves and the two standards MCP requires and it does not implement</title><link>https://blog.lo0.es/en/posts/keycloak-identity-plane-ai-platform/</link><pubDate>Sat, 12 Sep 2026 07:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/keycloak-identity-plane-ai-platform/</guid><description>&lt;blockquote>
&lt;p>This post opens the identity block of the operations track. &lt;a href="https://blog.lo0.es/en/posts/gateway-jwt-audience-trace-identity-seams/">The previous one&lt;/a> covered the gateway side: the JWT that does not validate audience, the user who never reaches the trace. This one covers the piece on the other side. Verified against Keycloak 26.7.3, revision 2026-07-28 of the MCP specification and the text of Royal Decree 311/2022 in the BOE.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>&lt;strong>Keycloak 26.7.3 was released on 31 August 2026&lt;/strong> and is still incubating at the CNCF, which it entered in April 2023. The cadence is four minor releases a year and support in practice covers a single branch: the twenty security bugs fixed in 26.7.3 were not backported to the 26.4, 26.5 or 26.6 branches at the time of release.&lt;/p>
&lt;p>&lt;strong>The Quarkus-based distribution separates build and runtime configuration.&lt;/strong> What is set in the build phase is frozen into the image, and changing it at startup forces a rebuild. In containers, the pattern is to build into the image and start with the optimised flag.&lt;/p>
&lt;p>&lt;strong>Storage is relational and only relational.&lt;/strong> The alternative engine that was in development was withdrawn in version 25.0. Since 26.0 sessions are persisted to the database by default, which changes the load profile: the dominant cost in the official benchmarks is database CPU.&lt;/p>
&lt;p>&lt;strong>Organisations solves multi-tenancy inside a realm&lt;/strong>, fully supported since 26.0. It groups members, associates email domains and links identity providers per organisation. It is the alternative to replicating realms per customer.&lt;/p>
&lt;p>&lt;strong>The measured bottleneck is password hashing.&lt;/strong> Since 25.0 the default algorithm outside FIPS environments is Argon2, with around 7 MB of memory per operation. The official sizing guide gives one vCPU for every 15 password logins per second, against 120 per second for client credentials and for refreshes.&lt;/p>
&lt;p>&lt;strong>Keycloak does not implement two standards the MCP specification marks as mandatory.&lt;/strong> Protected resource metadata belongs to the MCP server, not the authorisation server, so there is nothing to hold against it there. Resource indicators do belong to it and are &lt;strong>not supported&lt;/strong>. Keycloak&amp;rsquo;s own documentation declares partial conformance with the last three MCP revisions for that reason. The practical substitute is scopes plus an audience mapper.&lt;/p>
&lt;p>&lt;strong>There is a recent critical bug worth looking at today.&lt;/strong> &lt;code>CVE-2026-18963&lt;/code>, scored 9.1, allowed resetting any user&amp;rsquo;s password without authenticating, administrators included. Fixed in 26.7.2, released on 19 August 2026.&lt;/p>
&lt;h2 id="you-are-here-the-cross-cutting-layer">You are here: the cross-cutting layer&lt;/h2>
&lt;p>Identity is not a stage of the pipeline, it is a layer that crosses all of them. It shows up in &lt;a href="https://blog.lo0.es/en/posts/hardening-secrets-sovereign-llm-stack-defence-in-depth/">stack hardening&lt;/a> as one of the seven layers of defence, in &lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-who-serves-the-model-which-machine-you-trust/">the model&amp;rsquo;s chain of trust&lt;/a> as workload identity, in &lt;a href="https://blog.lo0.es/en/posts/gateway-jwt-audience-trace-identity-seams/">the gateway&lt;/a> as token validation, and in &lt;a href="https://blog.lo0.es/en/posts/mcp-grows-up-authentication-keycloak/">the post about authenticating MCP servers&lt;/a> as authorisation server.&lt;/p>
&lt;p>What was missing was the post about the piece itself, and above all the decision of where to put it and what to expect from it.&lt;/p>
&lt;h2 id="the-analogy-the-civil-registry-and-building-permits">The analogy: the civil registry and building permits&lt;/h2>
&lt;p>A small town hall has a population register, which says who lives there, and it has permits, which say who can do what. They are two different things and it is worth not confusing them, because the register is good for the whole town while a permit is good for one specific job on one specific street.&lt;/p>
&lt;p>Keycloak is the register. It knows who each person is, identifies them reliably, knows which groups they belong to and issues a credential document that expires. What it does not do is decide whether this person can modify this particular file: that is the permit, and it lives in each service.&lt;/p>
&lt;p>Confusing the two produces the most expensive design mistake in platforms of this kind, which consists of putting into the token everything that will need to be decided later. The register document grows, gets handed out everywhere, and when a permission changes you have to wait for everyone&amp;rsquo;s document to expire.&lt;/p>
&lt;p>There is a third element the analogy captures well. A credential document issued for presenting at the tax office should not be good for entering the municipal depot. That is the token&amp;rsquo;s audience, and it is the central subject of the second half of this post.&lt;/p>
&lt;h2 id="part-1-what-keycloak-is-today">Part 1. What Keycloak is today&lt;/h2>
&lt;h3 id="project-status">Project status&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Item&lt;/th>
&lt;th>Value&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Latest stable version&lt;/td>
&lt;td>26.7.3, released on 31 August 2026&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Previous ones&lt;/td>
&lt;td>26.7.2 (19 August), 26.7.0 (9 July), 26.6.0 (8 April)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cadence&lt;/td>
&lt;td>four minor releases a year&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CNCF&lt;/td>
&lt;td>incubating since 10 April 2023, not graduated&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Support&lt;/td>
&lt;td>the current branch; earlier ones do not receive fixes reliably&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>That last point deserves underlining because it conditions the upgrade policy. The analysis of 26.7.3 shows that its twenty security fixes were not backported to earlier branches at release time. Operating Keycloak means keeping up to date with the current branch, not staying on a &amp;ldquo;stable&amp;rdquo; version for a year.&lt;/p>
&lt;p>Since 26.0 there is a backward compatibility guarantee across minor versions for features marked as fully supported, and breaking changes are opt-in.&lt;/p>
&lt;h3 id="the-internal-architecture-in-five-pieces">The internal architecture in five pieces&lt;/h3>
&lt;p>&lt;strong>Two startup modes.&lt;/strong> The development one enables HTTP, does not resolve the hostname strictly and disables the theme cache. The production one requires a hostname and TLS, or it will not start. Confusing them in production is the first antipattern on the final list.&lt;/p>
&lt;p>&lt;strong>The build phase.&lt;/strong> The build command materialises the optimisations into the image. Build options, such as the database engine, the enabled features or metrics, are frozen. Runtime ones, such as the database password or the hostname, are applied at startup. Changing a build option at startup forces a rebuild with the corresponding latency cost. The correct pattern in containers is to build into the image and start with the optimised flag.&lt;/p>
&lt;p>&lt;strong>Extensions via SPI.&lt;/strong> Authenticators, protocol mappers, user storage, event listeners, hashing algorithms and secret vaults. They are packaged as JARs and resolved in the build phase.&lt;/p>
&lt;p>&lt;strong>Infinispan cache.&lt;/strong> Embedded by default, with database-based discovery as the recommended mechanism. The discovery alternatives via Kubernetes DNS, TCP and UDP are deprecated. There are local caches, with default capacities of 10,000 entries in several of them, and replicated caches for sessions, client sessions, offline sessions, authentication sessions, login failures and action tokens. Remote mode, with an external Infinispan, is needed for first-generation multi-site deployment.&lt;/p>
&lt;p>&lt;strong>Relational storage.&lt;/strong> JPA over a database. The alternative engine was removed in version 25.0. The databases supported today include PostgreSQL from 14 to 18, MariaDB and MySQL in their extended support versions, SQL Server 2019 and 2022, Oracle 19c and 23.x, and the managed variants Aurora PostgreSQL and Azure SQL.&lt;/p>
&lt;h3 id="the-minimum-conceptual-model">The minimum conceptual model&lt;/h3>
&lt;p>Before touching anything you need to be clear on eight concepts, and the order in which they relate:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Realm&lt;/strong>: the isolation boundary. Its own users, its own signing keys, its own policies. Two realms share nothing.&lt;/li>
&lt;li>&lt;strong>Client&lt;/strong>: an application that asks for tokens. Public if it cannot keep a secret, such as a browser application; confidential if it can, such as a service; and bearer-only if it merely validates tokens without requesting them.&lt;/li>
&lt;li>&lt;strong>Client scope&lt;/strong>: a reusable bundle of scopes and mappers, assigned to clients as default or as optional. It is the unit with which you bound what a token carries.&lt;/li>
&lt;li>&lt;strong>Protocol mapper&lt;/strong>: what projects an attribute, a role or an audience into the token. The audience mapper is the one that solves the central problem of this post.&lt;/li>
&lt;li>&lt;strong>Role&lt;/strong>, realm or client, and &lt;strong>composite role&lt;/strong>, which aggregates others.&lt;/li>
&lt;li>&lt;strong>Group&lt;/strong>, hierarchical, with inherited roles and attributes.&lt;/li>
&lt;li>&lt;strong>Identity provider&lt;/strong>: federation towards another external OIDC or SAML. &lt;strong>User federation&lt;/strong>: LDAP, Active Directory and Kerberos, with synchronisation and attribute mapping.&lt;/li>
&lt;li>&lt;strong>Authentication flow&lt;/strong>: a sequence of executions with required, alternative, conditional or disabled requirements, and subflows. It is where you build a second factor or a check of your own.&lt;/li>
&lt;/ul>
&lt;p>And one feature that changes architectures: &lt;strong>organisations&lt;/strong>, fully supported since 26.0. It solves multi-tenancy inside a single realm, grouping members, associating email domains and linking identity providers per organisation, so that provider discovery is done by the user&amp;rsquo;s email domain. The classic alternative, one realm per customer, multiplies administration and shares no configuration.&lt;/p>
&lt;h2 id="part-2-who-can-authenticate-against-it">Part 2. Who can authenticate against it&lt;/h2>
&lt;p>This is the table you need before drawing anything. The right-hand column is usually the one that decides.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Piece&lt;/th>
&lt;th>Protocol&lt;/th>
&lt;th>Cost&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Kubernetes API&lt;/td>
&lt;td>native OIDC, or the structured authentication configuration file&lt;/td>
&lt;td>Free&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LiteLLM&lt;/td>
&lt;td>OIDC and JWT, plus UI SSO&lt;/td>
&lt;td>&lt;strong>JWT and SSO above five users: Enterprise&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>vLLM&lt;/td>
&lt;td>static key only&lt;/td>
&lt;td>No OIDC&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Langfuse&lt;/td>
&lt;td>generic OIDC and Keycloak provider&lt;/td>
&lt;td>SSO free; per-project access control: Enterprise&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Grafana&lt;/td>
&lt;td>generic OAuth &lt;strong>yes in the open edition&lt;/strong>&lt;/td>
&lt;td>Team synchronisation: Enterprise&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Backstage&lt;/td>
&lt;td>generic OIDC and community-maintained Keycloak provider&lt;/td>
&lt;td>Free&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Open WebUI&lt;/td>
&lt;td>OIDC, with role management by claim&lt;/td>
&lt;td>Free&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LibreChat&lt;/td>
&lt;td>generic OIDC, with Keycloak documented&lt;/td>
&lt;td>Free&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Argo CD&lt;/td>
&lt;td>native OIDC or Dex, with groups into its RBAC file&lt;/td>
&lt;td>Free&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MinIO&lt;/td>
&lt;td>OIDC with role assumption via web identity&lt;/td>
&lt;td>See the note below&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Two clarifications that change decisions.&lt;/p>
&lt;p>&lt;strong>Kubernetes.&lt;/strong> The modern mechanism is structured authentication configuration, which allows several JWT issuers plus validations and mappings with CEL expressions. The corresponding proposal, KEP-3331, went alpha in 1.29, beta in 1.30 and is &lt;strong>stable since 1.34&lt;/strong>. It is the route to use today, and it replaces the loose API server flags.&lt;/p>
&lt;p>&lt;strong>vLLM has no identity.&lt;/strong> All there is is a static key via environment variable. This is not a defect to fix, it is an architectural fact: the engine sits behind the gateway and its access control is network-based, not identity-based. Any design that exposes vLLM directly to users is giving up per-person traceability.&lt;/p>
&lt;p>About MinIO a note of honesty is in order: the public documentation now redirects to the commercial edition, so the detail of how it maps policies from a claim is verified there and not in the open edition. Anyone depending on that mechanism should confirm it against their own version before designing on top of it.&lt;/p>
&lt;h2 id="part-3-humans-and-machines">Part 3. Humans and machines&lt;/h2>
&lt;p>They are two different problems and it is worth solving them separately.&lt;/p>
&lt;h3 id="people">People&lt;/h3>
&lt;p>Authorization code with PKCE, with the S256 method. The MCP specification requires it when the client is technically capable, and it goes further: it mandates &lt;strong>rejecting the flow&lt;/strong> if the authorisation server metadata does not publish the supported challenge methods.&lt;/p>
&lt;h3 id="workloads-with-no-person-behind-them">Workloads with no person behind them&lt;/h3>
&lt;p>Here the obvious option is client credentials with a Keycloak service account, authenticated with a secret or, better, with a JWT signed by private key.&lt;/p>
&lt;p>But there is a less known and better option for workloads inside the cluster: &lt;strong>federated client authentication&lt;/strong>, introduced in January 2026 under the banner of doing away with secrets. It lets a client authenticate with a token issued by an external provider, and one of those providers is Kubernetes. The announcement placed full support for the Kubernetes service account variant and for OIDC in version 26.6; the SPIFFE variant, by contrast, expressly says it will stay in preview until the SPIFFE client authentication standard is finalised at the IETF.&lt;/p>
&lt;p>That means a pod can authenticate against Keycloak with the token the kubelet already projects into it, with no additional secret to rotate. It is worth confirming the exact status and the flag name against the release notes of whichever version you have deployed, because the feature is recent and has changed status between 26.5 and 26.6. It is preferable to token exchange, whose standard version is supported but is internal-to-internal only, while the old one, which covered external-to-internal and impersonation, is deprecated and in preview.&lt;/p>
&lt;h3 id="keycloak-against-spiffe">Keycloak against SPIFFE&lt;/h3>
&lt;p>The question always comes up and the answer is that they solve different things with a small overlap.&lt;/p>
&lt;p>SPIRE attests the node and the process, and issues short-lived identity documents with no prior secret. Keycloak does not attest workloads: it trusts a credential someone had to place there, except in the federated authentication case just mentioned, where the one attesting is Kubernetes. In exchange, Keycloak federates people, issues tokens with business data and governs consent, things SPIFFE does not do.&lt;/p>
&lt;p>The practical rule: SPIFFE for service-to-service identity inside the perimeter, Keycloak for anything that has a person behind it or crosses the perimeter. The SPIFFE detail is in &lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-who-serves-the-model-which-machine-you-trust/">the close of the chain of trust series&lt;/a>.&lt;/p>
&lt;h2 id="part-4-mcp-and-the-two-missing-standards">Part 4. MCP, and the two missing standards&lt;/h2>
&lt;p>This part has the most consequences and is the worst documented outside the primary sources.&lt;/p>
&lt;h3 id="what-the-specification-requires">What the specification requires&lt;/h3>
&lt;p>From revision 2026-07-28, with the normative words as they appear:&lt;/p>
&lt;ul>
&lt;li>The MCP server &lt;strong>must&lt;/strong> implement RFC 9728 protected resource metadata, and the client &lt;strong>must&lt;/strong> use it to discover the authorisation server.&lt;/li>
&lt;li>The client &lt;strong>must&lt;/strong> implement RFC 8707 resource indicators, including the &lt;code>resource&lt;/code> parameter &lt;strong>in both the authorisation request and the token request&lt;/strong>, and &lt;strong>must&lt;/strong> send it regardless of whether the authorisation server supports it.&lt;/li>
&lt;li>The MCP server &lt;strong>must&lt;/strong> validate that the access token was issued specifically for it as the audience, and &lt;strong>must not&lt;/strong> accept or relay any other token.&lt;/li>
&lt;li>Authorisation servers and clients &lt;strong>should&lt;/strong> support client identifier metadata documents. RFC 7591 dynamic registration becomes optional and is declared deprecated, retained for compatibility.&lt;/li>
&lt;/ul>
&lt;p>The revision also adds the issuer in the authorisation response, from RFC 9207, and a stateless model with state handlers where it is expressly stated that holding a state handler is not equivalent to being authenticated.&lt;/p>
&lt;h3 id="what-keycloak-supports">What Keycloak supports&lt;/h3>
&lt;p>From its own MCP integration guide:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Standard&lt;/th>
&lt;th>Keycloak&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>OAuth 2.1 and authorisation server metadata&lt;/td>
&lt;td>Supported&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RFC 9207, issuer in the response&lt;/td>
&lt;td>Supported&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RFC 7591, dynamic registration&lt;/td>
&lt;td>Supported&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Client identifier metadata document&lt;/td>
&lt;td>Supported, &lt;strong>experimental&lt;/strong>, after enabling the feature&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>RFC 8707, resource indicators&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Not supported&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>RFC 9728, protected resource metadata&lt;/strong>&lt;/td>
&lt;td>Out of scope, it belongs to the MCP server&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The conformance declared by the project itself is &amp;ldquo;partially supported, without resource indicators&amp;rdquo; for the last three MCP revisions.&lt;/p>
&lt;p>Two practical conclusions:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>The protected resource metadata document is published by the MCP server or a proxy in front of it&lt;/strong>, never by Keycloak. That is correct and there is nothing to fix, but you have to remember to implement it, because the client needs it to discover where to go.&lt;/li>
&lt;li>&lt;strong>Without resource indicators, the audience is bounded with scopes.&lt;/strong> The official route is to use the &lt;code>scope&lt;/code> parameter instead of &lt;code>resource&lt;/code>, and to hang from that client scope an audience mapper with the MCP server URL as the included custom audience.&lt;/li>
&lt;/ol>
&lt;p>It is a workaround, it works, and it has to be documented in the design because anyone coming after will look for the standard parameter and will not find it.&lt;/p>
&lt;h2 id="part-5-the-confused-deputy">Part 5. The confused deputy&lt;/h2>
&lt;p>With the above on the table, the classic mistake becomes obvious on its own. A token with a generic audience turns any compromised service into a master key: whoever obtains the token a user presented to the dashboard can present it to the gateway, and from there to the MCP server.&lt;/p>
&lt;p>The MCP specification is explicit in three sentences worth quoting verbatim in any architecture document:&lt;/p>
&lt;ul>
&lt;li>MCP servers &lt;strong>must not&lt;/strong> accept tokens that were not explicitly issued for them.&lt;/li>
&lt;li>If the MCP server calls upstream APIs, it &lt;strong>must not&lt;/strong> forward the token it received from the client.&lt;/li>
&lt;li>MCP proxies that use static client identifiers &lt;strong>must&lt;/strong> obtain user consent for each dynamically registered client before forwarding to third-party authorisation servers.&lt;/li>
&lt;/ul>
&lt;p>Translated into the specific figure this track has been dealing with: &lt;strong>the gateway must not forward the user&amp;rsquo;s JWT to third-party MCP servers&lt;/strong>. It has to obtain a different token with the right audience, or act as an OAuth client in its own right. By default, as already documented &lt;a href="https://blog.lo0.es/en/posts/litellm-mcp-gateway-second-front-door/">in the MCP gateway post&lt;/a>, the behaviour is to use the server&amp;rsquo;s own credential, which is the correct one; there are two modes that forward the caller&amp;rsquo;s token and both have a name in the specification.&lt;/p>
&lt;h2 id="part-6-two-hours-of-task-against-five-minutes-of-token">Part 6. Two hours of task against five minutes of token&lt;/h2>
&lt;p>The specification pushes towards short-lived tokens and mandatory refresh token rotation in public clients. A two-hour agentic task comfortably exceeds the life of an access token and may also exceed session idle time if nobody refreshes.&lt;/p>
&lt;p>Three real options, with their cost:&lt;/p>
&lt;p>&lt;strong>Offline tokens.&lt;/strong> They survive session expiry, and expire through their own idle timeout or through a maximum if that limit is enabled. The risk is obvious: a near-permanent credential, hard to inventory. And the MCP specification adds that MCP servers &lt;strong>should not&lt;/strong> include that scope either in the authentication header or in the supported scopes of their metadata.&lt;/p>
&lt;p>&lt;strong>Automatic refresh in the client.&lt;/strong> It is the right answer when there is a client that can implement it, with refresh token rotation and secure storage. For an agent with no person behind it, it is better to renew via client credentials.&lt;/p>
&lt;p>&lt;strong>Long-lived virtual keys in the gateway.&lt;/strong> It works, and it is what most people end up doing. The price has to be written down in the risk analysis: identity ends up decoupled from the provider, so deactivating someone in Keycloak does not invalidate their key, and traceability back to the real person depends on the discipline with which the keys were created. The levers that compensate are expiry, automatic rotation and a mandatory alias, covered &lt;a href="https://blog.lo0.es/en/posts/litellm-virtual-keys-budgets-limits/">in the virtual keys post&lt;/a>.&lt;/p>
&lt;p>A note of honesty about the default values. The numbers going around (five minutes of access token, thirty of session idle time, ten hours of maximum) do not appear published in the administration guide and I did not locate them in the code. Treat them as a console observation and confirm them in your own realm before quoting them in a document.&lt;/p>
&lt;h2 id="part-7-sizing-and-operating">Part 7. Sizing and operating&lt;/h2>
&lt;h3 id="on-kubernetes">On Kubernetes&lt;/h3>
&lt;p>The operator exposes three custom resources: the server, the realm import and the OIDC client. Probes and metrics live on &lt;strong>management port 9000&lt;/strong>, with the health and readiness paths, and metrics in Prometheus format provided the corresponding option was enabled in the build phase. That port is not exposed publicly.&lt;/p>
&lt;p>For high availability there are three shapes. A single cluster spread across zones, with no additional infrastructure. First-generation multi-cluster, with an external load balancer and an external Infinispan at each site. And second-generation multi-cluster, introduced in 26.7.0 and still in preview, which connects clusters &lt;strong>without external Infinispan&lt;/strong>, using the synchronously replicated database as the source of truth, with less cache and more database load.&lt;/p>
&lt;h3 id="the-official-sizing-figures">The official sizing figures&lt;/h3>
&lt;p>From the CPU and memory sizing concepts guide, which is still published:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>1 vCPU for every 15 password logins per second&lt;/strong>, tested up to 300 per second.&lt;/li>
&lt;li>&lt;strong>1 vCPU for every 120 client credential grants per second&lt;/strong>, tested up to 2,000.&lt;/li>
&lt;li>&lt;strong>1 vCPU for every 120 token refresh requests per second&lt;/strong>, tested up to 435.&lt;/li>
&lt;li>Leave a &lt;strong>150 % CPU headroom&lt;/strong> for spikes, startup and failover.&lt;/li>
&lt;li>&lt;strong>1,250 MB of memory per pod&lt;/strong> with realm data and 10,000 cached sessions. Keycloak allocates 70 % of the limit to the heap and around 300 MB outside it, so the limit comes from subtracting those 300 MB from the expected usage and dividing by 0.7.&lt;/li>
&lt;/ul>
&lt;p>The stated assumptions behind those figures include Argon2 with five iterations, sessions in the database and the default cache of 10,000 entries.&lt;/p>
&lt;p>The difference between 15 and 120 is the whole story of Keycloak&amp;rsquo;s performance: &lt;strong>the cost is in password hashing&lt;/strong>. Since 25.0, the default algorithm outside FIPS environments is Argon2, with around 7 MB of memory per operation. Before it was PBKDF2, whose iterations were multiplied by ten in version 24. In FIPS mode PBKDF2 is still used.&lt;/p>
&lt;p>From that comes an architectural consequence for an AI platform: &lt;strong>machine workloads, which go through client credentials, cost eight times less than a human login&lt;/strong>. Sizing by number of services is cheap; sizing by morning login peaks is the expensive part.&lt;/p>
&lt;h3 id="the-benchmark">The benchmark&lt;/h3>
&lt;p>From the 26.4 performance report, published on 1 October 2025: up to 2,000 logins per second and 10,000 refreshes per second with three pods, 74 vCPU in total and 8 GB per pod, over an Aurora PostgreSQL database, with almost linear vertical scaling.&lt;/p>
&lt;p>Two useful findings from that report. With 20 ms of round-trip time between sites, the 99th percentile rises from 51 to 130 ms, and the project &lt;strong>advises against deploying across different regions&lt;/strong>. And raising the cache from 10,000 to 200,000 entries brought the database CPU peak down from 77.77 % to 63.77 %, in exchange for somewhat longer garbage collection pauses.&lt;/p>
&lt;h3 id="security">Security&lt;/h3>
&lt;p>The urgent matter, if the deployment is below 26.7.2:&lt;/p>
&lt;p>&lt;strong>&lt;code>CVE-2026-18963&lt;/code>, score 9.1, critical.&lt;/strong> A bypass of the email action token in the credential reset flow allowed resetting any user&amp;rsquo;s password with no prior authentication, administrators included. Fixed in 26.7.2, released on 19 August 2026. The temporary mitigation, if you cannot upgrade, is to disable password recovery in all realms. No known exploitation was recorded as of the end of August.&lt;/p>
&lt;p>In 26.7.3 there are another nineteen, among them the missing hostname verification in the TLS certificate towards LDAP with a score of 8.8, two tenant restriction bypasses in token exchange with Microsoft and Google brokers, and two access control bypasses in organisations.&lt;/p>
&lt;p>Official hardening comes down to four decisions:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Strict hostname&lt;/strong>, which is the default value since the second generation of that configuration. The server does not infer the URL from headers, which prevents poisoning email links and redirects.&lt;/li>
&lt;li>&lt;strong>Administration console on a different hostname&lt;/strong>, and network-restricted.&lt;/li>
&lt;li>&lt;strong>Proxy headers declared explicitly&lt;/strong>, trusting only a controlled reverse proxy.&lt;/li>
&lt;li>&lt;strong>Separate management port, not exposed.&lt;/strong>&lt;/li>
&lt;/ol>
&lt;h2 id="part-8-the-mapping-to-the-ens">Part 8. The mapping to the ENS&lt;/h2>
&lt;p>The codes and titles below are verified against the official text of Royal Decree 311/2022 published in the BOE, Annex II.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Code&lt;/th>
&lt;th>Literal title&lt;/th>
&lt;th>What a central IdP contributes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>op.acc.1&lt;/td>
&lt;td>Identificación&lt;/td>
&lt;td>A unique identifier per entity, user or process that gains access&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>op.acc.2&lt;/td>
&lt;td>Requisitos de acceso&lt;/td>
&lt;td>Permissions are granted by role and group, not by loose credential&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>op.acc.3&lt;/td>
&lt;td>Segregación de funciones y tareas&lt;/td>
&lt;td>It supports it with roles, but the control is organisational&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>op.acc.4&lt;/td>
&lt;td>Proceso de gestión de derechos de acceso&lt;/td>
&lt;td>Least privilege and a specific remote access policy&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>op.acc.5&lt;/td>
&lt;td>Mecanismo de autenticación (usuarios externos)&lt;/td>
&lt;td>Flows and factors configurable per flow&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>op.acc.6&lt;/td>
&lt;td>Mecanismo de autenticación (usuarios de la organización)&lt;/td>
&lt;td>Reinforcements required already at the low level&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>op.exp.8&lt;/td>
&lt;td>Registro de la actividad&lt;/td>
&lt;td>It contributes part of the events, not all of them&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>op.exp.10&lt;/td>
&lt;td>Protección de claves criptográficas&lt;/td>
&lt;td>Management and rotation of the realm signing keys&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Two nuances an auditor is going to look for.&lt;/p>
&lt;p>The first, about &lt;strong>op.acc.1&lt;/strong>: the text requires every entity that accesses the system, whether user or process, to have a unique identifier. That includes agents. A virtual key shared between several agents does not comply, and that is exactly what usually happens when JWT authentication falls outside the budget.&lt;/p>
&lt;p>The second, about &lt;strong>op.exp.8&lt;/strong>: the control requires recording the identifier of the user or entity associated with the event, the date and time, what information it is performed on, the type and the result. Keycloak contributes the authentication events. The usage events are contributed by the gateway and the MCP servers, and there the two already documented gaps reappear: &lt;a href="https://blog.lo0.es/en/posts/litellm-mcp-gateway-second-front-door/">adding and removing MCP servers generates no audit log&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/gateway-jwt-audience-trace-identity-seams/">the trace user is set by the client&lt;/a>.&lt;/p>
&lt;p>The correspondence between control and product in this table is my own interpretation and must be validated by the certification body. The codes and titles are not: those are literal from the BOE.&lt;/p>
&lt;h2 id="part-9-the-configuration-to-leave-written-down">Part 9. The configuration to leave written down&lt;/h2>
&lt;p>Three concrete pieces, so as not to stay at the level of concepts.&lt;/p>
&lt;p>&lt;strong>Hardened startup&lt;/strong>, as container arguments:&lt;/p>
&lt;pre tabindex="0">&lt;code>kc.sh start --optimized \
--hostname=https://sso.ejemplo.es \
--hostname-admin=https://sso-admin.interna.ejemplo.es \
--proxy-headers=xforwarded \
--health-enabled=true \
--metrics-enabled=true \
--http-management-port=9000 \
--cache=ispn --cache-stack=jdbc-ping \
--db=postgres
&lt;/code>&lt;/pre>&lt;p>The metrics, health, cache and database options are build options: they go into the image, and passing them here forces a rebuild if they do not match.&lt;/p>
&lt;p>&lt;strong>The client scope with audience&lt;/strong>, which is the workaround forced by the lack of resource indicators. Created with the administration tool:&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"># one scope per protected resource&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kcadm.sh create client-scopes -r plataforma &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -s &lt;span class="nv">name&lt;/span>&lt;span class="o">=&lt;/span>mcp-inventario -s &lt;span class="nv">protocol&lt;/span>&lt;span class="o">=&lt;/span>openid-connect &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -s &lt;span class="s1">&amp;#39;attributes.&amp;#34;include.in.token.scope&amp;#34;=true&amp;#39;&lt;/span>
&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"># the mapper that sets the token audience&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">kcadm.sh create &lt;span class="s2">&amp;#34;client-scopes/&amp;lt;id&amp;gt;/protocol-mappers/models&amp;#34;&lt;/span> -r plataforma &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -s &lt;span class="nv">name&lt;/span>&lt;span class="o">=&lt;/span>aud-mcp-inventario &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -s &lt;span class="nv">protocol&lt;/span>&lt;span class="o">=&lt;/span>openid-connect &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -s &lt;span class="nv">protocolMapper&lt;/span>&lt;span class="o">=&lt;/span>oidc-audience-mapper &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -s &lt;span class="s1">&amp;#39;config.&amp;#34;included.custom.audience&amp;#34;=https://mcp.ejemplo.es/inventario&amp;#39;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -s &lt;span class="s1">&amp;#39;config.&amp;#34;access.token.claim&amp;#34;=true&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The client asks for that scope and receives a token whose audience is only that MCP server. The MCP server validates the audience and rejects any other token, as the specification requires.&lt;/p>
&lt;p>&lt;strong>Realm import via GitOps&lt;/strong>, with the operator&amp;rsquo;s custom resource, so that the configuration does not live only in the console:&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">k8s.keycloak.org/v2beta1&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">KeycloakRealmImport&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">plataforma&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">identidad&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">keycloakCRName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">sso&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">realm&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">realm&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">plataforma&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">enabled&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="c"># without this, the recovery email is attack surface&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">resetPasswordAllowed&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">bruteForceProtected&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">sslRequired&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">all&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="part-10-what-keycloak-does-not-solve">Part 10. What Keycloak does not solve&lt;/h2>
&lt;p>&lt;strong>Fine-grained authorisation over resources.&lt;/strong> The question &amp;ldquo;can this person see this particular trace&amp;rdquo; is a relation between objects, not a role. That problem is solved by Zanzibar-style engines, with OpenFGA and its object, relation and user tuples, or a policy language such as Cedar. Keycloak gives roles and scopes, and with those you do not model a graph of relations.&lt;/p>
&lt;p>&lt;strong>Machine secrets.&lt;/strong> Storage credentials, provider keys, database passwords. That is a secrets manager, covered in &lt;a href="https://blog.lo0.es/en/posts/hardening-secrets-sovereign-llm-stack-defence-in-depth/">stack hardening&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Cryptographic workload identity with attestation.&lt;/strong> That is SPIFFE and SPIRE. In Keycloak it exists as an identity provider in preview, which is a different thing.&lt;/p>
&lt;p>&lt;strong>Authorisation inside the model.&lt;/strong> Which MCP tool an agent may invoke, and with what arguments. The specification offers scopes, the insufficient scope error and step-up elevation, but the per-tool and per-argument decision is taken by the MCP server or a policy engine, not by the identity provider.&lt;/p>
&lt;h2 id="antipatterns">Antipatterns&lt;/h2>
&lt;ol>
&lt;li>Forwarding the user&amp;rsquo;s token from the gateway to third-party MCP servers. Explicitly forbidden by the specification.&lt;/li>
&lt;li>Static client identifier with dynamic registration and no per-client consent, which is the exact recipe for the confused deputy.&lt;/li>
&lt;li>General scopes, of the full-access kind, and publishing the entire catalogue of supported scopes in the metadata.&lt;/li>
&lt;li>Treating a state handler as authentication, which the new revision expressly forbids.&lt;/li>
&lt;li>Following OAuth discovery towards any URL, which opens the door to requests towards internal addresses. It also applies to the authorisation server when it downloads a client metadata document.&lt;/li>
&lt;li>Trust headers badly exposed in chat front ends. The Open WebUI documentation warns that an incorrect configuration allows authenticating as any user.&lt;/li>
&lt;li>Starting in development mode in production, with the hostname not resolved strictly.&lt;/li>
&lt;li>A single shared static key as the only control between the gateway and the engine.&lt;/li>
&lt;/ol>
&lt;h2 id="startup-checklist">Startup checklist&lt;/h2>
&lt;ol>
&lt;li>Check the version. Below 26.7.2 there is a critical password reset bug.&lt;/li>
&lt;li>Build the image with the build phase done and start with the optimised flag.&lt;/li>
&lt;li>Strict hostname, administration console on another hostname, separate management port and not exposed.&lt;/li>
&lt;li>Decide between organisations and one realm per tenant before creating the second tenant.&lt;/li>
&lt;li>One client scope per resource, with an audience mapper, because resource indicators are not supported.&lt;/li>
&lt;li>In the gateway, define the two audience and issuer variables. Without them none of that is validated.&lt;/li>
&lt;li>For workloads inside the cluster, look at the Kubernetes identity provider before handing out client secrets.&lt;/li>
&lt;li>Size by login peaks, not by number of services: the difference is eightfold.&lt;/li>
&lt;li>Export the Keycloak events to the same place the gateway&amp;rsquo;s go, because the activity logging control needs them together.&lt;/li>
&lt;li>Write into the design what the system does with a two-hour task, before someone solves the problem with an offline token.&lt;/li>
&lt;/ol>
&lt;h2 id="closing">Closing&lt;/h2>
&lt;p>Keycloak is a mature piece that solves one bounded problem well: knowing who the caller is, and saying so verifiably in a document that expires. In a sovereign inference platform that is worth a lot, because the alternative is an inventory of static keys spread across ten services that no audit can walk through.&lt;/p>
&lt;p>What is not advisable is asking more of it. Fine-grained authorisation lives in each service, workload identity with attestation lives in SPIFFE, and the decision of which tool an agent may call lives in the MCP server.&lt;/p>
&lt;p>And there is one concrete gap worth noting down, because it does not close on its own: the MCP specification marks two standards as mandatory, and of the two that would fall to an authorisation server, resource indicators are not implemented. The workaround with scopes and an audience mapper works, but it is a workaround, and whoever comes after will look for the standard parameter. Leaving it written in the design saves an afternoon.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/gateway-jwt-audience-trace-identity-seams/">The gateway does not live alone&lt;/a>: how the gateway validates the tokens this piece issues, and the two variables you have to define.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/mcp-grows-up-authentication-keycloak/">When MCP grows up: giving it authentication with Keycloak&lt;/a>: the concrete setup for MCP servers.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/litellm-mcp-gateway-second-front-door/">LiteLLM&amp;rsquo;s MCP gateway&lt;/a>: the second front door, its permissions and its lack of audit logging.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-who-serves-the-model-which-machine-you-trust/">The model&amp;rsquo;s chain of trust (4 of 4)&lt;/a>: SPIFFE, SPIRE and workload identity with attestation.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/hardening-secrets-sovereign-llm-stack-defence-in-depth/">Hardening and secrets of the sovereign LLM stack&lt;/a>: where identity fits among the seven layers.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/litellm-virtual-keys-budgets-limits/">Virtual keys, budgets and limits&lt;/a>: the alternative when JWT falls outside the budget.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/">Technical controls: ENS, ISO 42001 and the EU AI Act&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/completing-keycloak-for-mcp-protected-resource/">Completing Keycloak for MCP&lt;/a>: the follow-up: the protected resource side you have to build, token exchange and the enterprise authorisation extension.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.keycloak.org/2026/08/keycloak-2673-released">Keycloak 26.7.3&lt;/a>, 31 de agosto de 2026, y &lt;a href="https://www.keycloak.org/downloads">descargas&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.keycloak.org/server/configuration">Configuración del servidor&lt;/a>, &lt;a href="https://www.keycloak.org/server/caching">caché&lt;/a>, &lt;a href="https://www.keycloak.org/server/db">bases de datos&lt;/a> y &lt;a href="https://www.keycloak.org/server/hostname">nombre de host&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.keycloak.org/securing-apps/mcp-authz-server">Integración con Model Context Protocol&lt;/a>, &lt;a href="https://www.keycloak.org/securing-apps/token-exchange">intercambio de tokens&lt;/a> y &lt;a href="https://www.keycloak.org/2026/01/federated-client-authentication">autenticación federada de clientes&lt;/a>, 27 de enero de 2026.&lt;/li>
&lt;li>&lt;a href="https://www.keycloak.org/high-availability/multi-cluster/concepts-memory-and-cpu-sizing">Dimensionado de CPU y memoria&lt;/a> e &lt;a href="https://www.keycloak.org/high-availability/introduction">introducción a alta disponibilidad&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.keycloak.org/2025/10/keycloak-benchmark">Bancos de pruebas de rendimiento de Keycloak 26.4&lt;/a>, 1 de octubre de 2025.&lt;/li>
&lt;li>&lt;a href="https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization">Especificación de MCP, revisión 2026-07-28, autorización&lt;/a> y &lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices">buenas prácticas de seguridad&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/3331-structured-authentication-configuration">KEP-3331, configuración estructurada de autenticación&lt;/a>.&lt;/li>
&lt;li>&lt;a href="https://www.boe.es/diario_boe/xml.php?id=BOE-A-2022-7191">Real Decreto 311/2022, Anexo II&lt;/a>.&lt;/li>
&lt;li>Registro de &lt;code>CVE-2026-18963&lt;/code> en la base de datos de Red Hat y notas de la versión 26.7.3 en GitHub.&lt;/li>
&lt;li>&lt;a href="https://www.cncf.io/projects/keycloak/">CNCF, proyecto Keycloak&lt;/a>.&lt;/li>
&lt;/ul></description></item><item><title>Model chain of trust (4/4): who serves the model and which machine you trust</title><link>https://blog.lo0.es/en/posts/model-chain-of-trust-who-serves-the-model-which-machine-you-trust/</link><pubDate>Sun, 26 Jul 2026 17:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/model-chain-of-trust-who-serves-the-model-which-machine-you-trust/</guid><description>&lt;p>The three previous articles closed questions about the artefact: what contract the served model exposes, where its bytes come from and why you trust them. What remains is the one almost nobody asks until an auditor asks it: nothing proves that the process currently returning tokens on port 8000 is the inference engine and not something that slipped into the cluster and inherited its token. And a second one, worse: whether the machine it runs on is trustworthy, and against whom.&lt;/p>
&lt;p>These are different problems with different projects. &lt;strong>Workload identity&lt;/strong> is solved by SPIFFE/SPIRE, a CNCF graduated project since 2022. &lt;strong>Environment isolation&lt;/strong> is covered by Kata Containers and Confidential Containers, the latter promoted to CNCF &lt;em>incubating&lt;/em> on 22 July 2026, four days before this article was published. The first is cheap and almost always worth it. The second is expensive, more than the vendor&amp;rsquo;s press release suggests, and is only justified against a specific threat model.&lt;/p>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>User identity ≠ workload identity.&lt;/strong> OIDC says which person is behind a request; it does not say which process serves it. &lt;code>ServiceAccount&lt;/code> tokens, static keys and gateway API keys are &lt;em>bearer&lt;/em> credentials: whoever holds them, is them.&lt;/li>
&lt;li>&lt;strong>SPIFFE&lt;/strong> defines a URI identifier (&lt;code>spiffe://domain/path&lt;/code>) and a verifiable document (&lt;strong>SVID&lt;/strong>, X.509 or JWT) delivered by a Workload API that does not require the workload to hold any prior secret. SPIRE implements it with node and workload attestation; defaults: &lt;code>default_x509_svid_ttl&lt;/code> 1 h, &lt;code>default_jwt_svid_ttl&lt;/code> 5 min, &lt;code>ca_ttl&lt;/code> 24 h.&lt;/li>
&lt;li>Identity is only worth something if something enforces it: Istio/Envoy via SDS, Cilium with mutual authentication (with serious reservations) or an &lt;code>ext_authz&lt;/code> with OPA in front of the gateway.&lt;/li>
&lt;li>&lt;strong>Three rungs of isolation&lt;/strong>: container with a shared kernel → Kata Containers (its own kernel and VM; v3.32.0 from June 2026) → Confidential Containers (hardware TEE, hypervisor outside the trust base, remote attestation with Trustee per RFC 9334).&lt;/li>
&lt;li>&lt;strong>The overhead of confidential mode runs from ~0 % to ~28 %&lt;/strong> depending on what you measure: GPU-only in CC with large batches sits around 4-8 %, while the full stack (a CVM with Intel TDX plus an H100 in CC), measured independently in 2026, gives +21.8 % to +27.8 % on TTFT and −17.7 % to −21.1 % on throughput. Honest heuristic: reserve 15-25 % extra capacity.&lt;/li>
&lt;li>&lt;strong>MIG and vGPU are forbidden in CC mode&lt;/strong> according to NVIDIA&amp;rsquo;s reference architecture, and every GPU on a node goes in the same mode.&lt;/li>
&lt;li>&lt;strong>Closing criterion&lt;/strong>: the order is contract → digest → signature verified at admission → workload identity → TEE.&lt;/li>
&lt;/ul>
&lt;h2 id="the-analogy-the-biosafety-laboratory">The analogy: the biosafety laboratory&lt;/h2>
&lt;p>A laboratory handling pathogens solves two problems much like ours, separately.&lt;/p>
&lt;p>The first is who gets in. The company badge is not enough: at the airlock there is a specific credential, short-lived and automatically renewed (if lost, it expires on its own within an hour), issued only after independently checking that the applicant is where they say they are and is who they say they are. That credential is the &lt;strong>SVID&lt;/strong>; the prior check is &lt;strong>attestation&lt;/strong>.&lt;/p>
&lt;p>The second is which room you work in, and here there are containment levels. On the open bench the air is shared and anything aerosolised affects everyone: the normal container, with a kernel shared by all the neighbours. One rung up, the biological safety cabinet, with a physical barrier and its own airflow: Kata Containers. At the very top, the maximum containment laboratory, where maintenance staff change filters without ever seeing the sample: the TEE, where the operator maintains the machine but cannot read its memory. And before opening the airlock somebody verifies that the integrity of the suit and of the room is as expected: remote attestation with conditional secret release.&lt;/p>
&lt;p>The uncomfortable lesson: the maximum containment level is a cost decision before it is one more degree of security. Nobody builds a BSL-4 to grow yeast. The useful question is who it isolates from, not how much.&lt;/p>
&lt;h2 id="part-a--workload-identity">Part A — Workload identity&lt;/h2>
&lt;h3 id="six-services-talking-to-each-other-and-none-of-them-knows-who-the-other-is">Six services talking to each other and none of them knows who the other is&lt;/h3>
&lt;p>In a serious inference cluster, like the &lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">seven layers&lt;/a> one, the gateway talks to the serving engine, the engine queries the vector database, the trace collector receives &lt;em>spans&lt;/em> from everyone and the agents with MCP invoke tools that in turn call the gateway. Many east-west connections and, in most deployments, none of them authenticated.&lt;/p>
&lt;p>User identity says which person is behind the request and is resolved with OIDC, as when &lt;a href="https://blog.lo0.es/en/posts/mcp-grows-up-authentication-keycloak/">putting Keycloak in front of MCP&lt;/a>. Workload identity says which process issues it: there is no human on the other side, no browser, no consent, and the process is born and dies in seconds. Its three usual substitutes fail for different reasons:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Mechanism&lt;/th>
&lt;th>Where it fails&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Projected &lt;code>ServiceAccount&lt;/code> token&lt;/td>
&lt;td>SA granularity, not workload granularity: two pods with the same SA are indistinguishable. It does not cross the cluster boundary and does not federate. It is a &lt;em>bearer token&lt;/em> on the filesystem&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Static key&lt;/td>
&lt;td>It does not rotate, it gets shared over Slack, it does not distinguish issuer from bearer; once leaked, it is valid until somebody notices&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Gateway API key&lt;/td>
&lt;td>It authenticates the &lt;em>client&lt;/em>, not the process. LiteLLM&amp;rsquo;s virtual keys are for quota and budget, not for proving identity&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>There is no cryptographic link between credential and process: copy the credential, copy the identity. In a multi-tenant cluster like the &lt;a href="https://blog.lo0.es/en/posts/gpu-cluster-as-multi-tenant-platform/">H100 cluster&lt;/a> one, real isolation then depends on network topology and not on identity.&lt;/p>
&lt;h3 id="spiffe-the-standard">SPIFFE: the standard&lt;/h3>
&lt;p>&lt;a href="https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/">SPIFFE&lt;/a> and its implementation &lt;a href="https://github.com/spiffe/spire">SPIRE&lt;/a> graduated together in the CNCF on 20 September 2022. As of July 2026 they are settled infrastructure, not a bet.&lt;/p>
&lt;p>&lt;strong>The SPIFFE ID&lt;/strong> is a URI of the form &lt;code>spiffe://&amp;lt;trust-domain&amp;gt;/&amp;lt;workload-identifier&amp;gt;&lt;/code>, for example &lt;code>spiffe://inferencia.example/ns/inferencia/sa/vllm&lt;/code>: a name, not a credential. &lt;strong>The trust domain&lt;/strong> is the root of trust (organisation, environment or site), and the guidance recommends separating workloads from different sites or security environments into different domains.&lt;/p>
&lt;p>The SVID is the credential that proves that ID. The &lt;strong>X509-SVID&lt;/strong> carries the SPIFFE ID in the URI-type SAN, not in the &lt;code>CN&lt;/code>, which the specification discourages as a source of identity, and it is the preferred format. The &lt;strong>JWT-SVID&lt;/strong> exists for when there are L7 proxies terminating TLS in the middle, with the replay risk the documentation warns about:&lt;/p>
&lt;pre tabindex="0">&lt;code>Certificate:
Issuer: C=ES, O=SPIFFE
Validity
Not Before: Jul 26 08:00:00 2026 GMT
Not After : Jul 26 09:00:00 2026 GMT
Subject: C=ES, O=SPIRE, CN=vllm.inferencia
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Alternative Name:
URI:spiffe://inferencia.example/ns/inferencia/sa/vllm
&lt;/code>&lt;/pre>&lt;p>One hour of validity, and what authenticates is the SAN URI: the &lt;code>CN&lt;/code> is decorative.&lt;/p>
&lt;p>&lt;strong>The Workload API&lt;/strong> is the elegant piece: a local UNIX socket from which the workload obtains its SVID, its private key and the trust bundle, without presenting any secret. The documentation is explicit: &amp;ldquo;the Workload API does not require that a calling workload have any knowledge of its own identity, or possess any authentication token&amp;rdquo;. That solves the cold start of all identity cryptography: the secret you would need in order to obtain the first secret.&lt;/p>
&lt;h3 id="spire-how-that-document-is-issued">SPIRE: how that document is issued&lt;/h3>
&lt;p>SPIRE has a server (the authority that signs) and one agent per node (which exposes the Workload API), and it chains two checks.&lt;/p>
&lt;p>&lt;strong>Node attestation&lt;/strong> verifies that the agent runs where it says it does. In Kubernetes the reference attestor is &lt;code>k8s_psat&lt;/code>, which validates a projected token with an audience against the API server; on bare metal there are TPM-based attestors. A note of honesty: SPIRE v1.15.1, from 28 May 2026, is a security patch over an incorrect PKCS7 validation in the &lt;code>azure_imds&lt;/code> attestor that allowed attested documents to be forged and a virtual machine to be impersonated.&lt;/p>
&lt;p>&lt;strong>Workload attestation&lt;/strong> answers the opening question. The &lt;a href="https://github.com/spiffe/spire/blob/main/doc/plugin_agent_workloadattestor_k8s.md">&lt;code>k8s&lt;/code> attestor&lt;/a> receives the PID of the process that opens the socket, infers the pod from its cgroup membership and queries the kubelet for the metadata. From there come the &lt;strong>selectors&lt;/strong>: namespace, service account, pod name and UID, labels, owner, node and, above all, container and image by tag or by digest. From SPIRE v1.15.0 (19 May 2026) Sigstore support stopped being experimental, enabling selectors by signature verification status, certificate subject and issuer, and transparency log. That closes the series&amp;rsquo; circle: you can require that the engine&amp;rsquo;s SVID go only to the container whose image carries a valid cosign signature from the expected issuer, which is what was verified in &lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-signature-provenance-aibom-why-you-trust-those-bytes/">article 3/4&lt;/a>. Provenance goes from being a one-off admission check to a precondition of identity at runtime.&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">spire-server entry create &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -parentID spiffe://inferencia.example/spire/agent/k8s_psat/prod/nodo-gpu-a &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -spiffeID spiffe://inferencia.example/ns/inferencia/sa/vllm &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -selector k8s:ns:inferencia &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -selector k8s:sa:vllm &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -selector k8s:container-name:vllm &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -selector k8s:container-image:vllm/vllm-openai@sha256:aa11bb22cc33 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -x509SVIDTTL &lt;span class="m">3600&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -federatesWith spiffe://sede-b.example
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The selectors are &lt;strong>conjunctive&lt;/strong>: a pod in another namespace, with another SA or with another image does not obtain that SVID even if it shares the node.&lt;/p>
&lt;p>The default TTLs have three consequences. Rotation is continuous, not a one-off event: code that loads the certificate once at startup will break exactly one hour later. Server downtime has a clock: twenty minutes are invisible, two hours bring down the cluster&amp;rsquo;s authenticated communications, and the &lt;a href="https://spiffe.io/docs/latest/planning/scaling_spire/">scaling guide&lt;/a> admits that &amp;ldquo;a single SPIRE Server instance represents a single point of failure&amp;rdquo;, with the datastore as the bottleneck and indicative figures running from two replicas of 1 CPU and 1 GB for 10 agents to eight of 16 CPU and 16 GB for 5,000 agents and 10,000 workloads. And tag-based selectors are fragile: the runtime may report one tag or another depending on the node and the moment, so the correct selector is the digest, as in &lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-where-the-bytes-live-registry-oci-artefacts/">article 2/4&lt;/a>.&lt;/p>
&lt;h3 id="federation-between-trust-domains">Federation between trust domains&lt;/h3>
&lt;p>Two sites with independent clusters, or an external partner exposing a &lt;em>reranker&lt;/em>, should not share an authority. &lt;a href="https://spiffe.io/docs/latest/spiffe-specs/spiffe_federation/">SPIFFE federation&lt;/a> makes each domain publish a &lt;strong>bundle endpoint&lt;/strong> with its public trust material and the other poll it periodically: the &lt;code>https_web&lt;/code> profile authenticates it with web PKI and &lt;code>https_spiffe&lt;/code> with an X509-SVID from the domain itself, enabling automatic rotation and revocation of the root. The relationship is one-directional, bundles from different domains must never be merged and refresh is by polling with a default suggestion of five minutes. In practice, site A&amp;rsquo;s gateway accepts requests from site B&amp;rsquo;s engine without sharing a CA or an identity database, and cutting the relationship means deleting a bundle, not revoking certificates.&lt;/p>
&lt;h3 id="from-identity-to-effective-authorisation">From identity to effective authorisation&lt;/h3>
&lt;p>An SVID blocks nothing by itself; somebody has to compare the presented ID against a policy.&lt;/p>
&lt;p>&lt;strong>Istio + SPIRE&lt;/strong> is the best-worn integration: Istio detects a UNIX socket implementing Envoy&amp;rsquo;s SDS API and the proxy obtains its identities from there instead of from &lt;code>istiod&lt;/code>, mounted with the &lt;strong>SPIFFE CSI Driver&lt;/strong> (recommended over &lt;code>hostMount&lt;/code>). Two conditions break deployments: SPIRE&amp;rsquo;s trust domain and Istio&amp;rsquo;s must match exactly, and SPIRE only issues to previously registered workloads, including Istio&amp;rsquo;s own components. With that, policy is written in terms of identity and not IP:&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">security.istio.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">AuthorizationPolicy&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">vllm-solo-desde-gateway&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">inferencia&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">selector&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">vllm&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">ALLOW&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">rules&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">from&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">source&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">principals&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;inferencia.example/ns/inferencia/sa/gateway&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">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">operation&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">methods&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;POST&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">paths&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;/v1/chat/completions&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;/v1/completions&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It is a layer on top of the &lt;code>NetworkPolicy&lt;/code> from &lt;a href="https://blog.lo0.es/en/posts/hardening-secrets-sovereign-llm-stack-defence-in-depth/">layered hardening&lt;/a>: there we closed communication pairs by topology; here we also require who calls and on what path.&lt;/p>
&lt;p>&lt;strong>Cilium&lt;/strong> offers mutual authentication backed by SPIRE, tempting if you already use eBPF for the &lt;a href="https://blog.lo0.es/en/posts/ebpf-zero-to-cilium-kernel-tcp-ip-bypass/">data network&lt;/a>. It deserves criticism: it is still marked &lt;strong>beta&lt;/strong> in the stable documentation, and The New Stack published a reasoned critique arguing that the design does not preserve mTLS properties over the life of the connection (it uses the &lt;em>handshake&lt;/em> only to authenticate and discards the session keys) and that the identity model rests on eventually consistent per-node IP-identity caches, which can allow traffic that policy should deny.&lt;/p>
&lt;p>&lt;strong>The inference gateway and LiteLLM&lt;/strong> do not speak SPIFFE natively as of this article, and saying so is better than suggesting an integration that does not exist. The pattern that does work is the one the project itself documents: an Envoy in front that terminates mTLS with an X509-SVID or validates a JWT-SVID and delegates to OPA via &lt;code>ext_authz&lt;/code>. The gateway carries on with its own job (quota, budget, routing), as we saw when &lt;a href="https://blog.lo0.es/en/posts/choosing-oss-gateway-llm-inference/">choosing an OSS gateway&lt;/a> and in the &lt;a href="https://blog.lo0.es/en/posts/llm-inference-router-l7-switchboard/">L7 router&lt;/a>; workload identity is resolved one hop earlier.&lt;/p>
&lt;h3 id="ai-agents-and-mcp-the-missing-piece">AI agents and MCP: the missing piece&lt;/h3>
&lt;p>An agent invoking tools over MCP is a shared secret with legs: it is handed a static credential, the MCP server cannot know whether the caller is the legitimate agent or any process holding the same string, and the blast radius is the entire set of exposed tools. The &lt;a href="https://blog.lo0.es/en/posts/isolating-ai-agents-workstation-to-cluster/">threat model for agent isolation&lt;/a> warned that a granted permission is a usable permission; with bearer credentials it is also transferable.&lt;/p>
&lt;p>SPIFFE provides what is missing: the agent presents a short-lived SVID obtained through attestation and the MCP server authorises against the SPIFFE ID. In 2026 this stopped being theory: there is work at the IETF on dynamic OAuth client registration based on trusted SPIFFE issuers, and in June 2026 Google published a SPIFFE-based &lt;em>Agent Identity&lt;/em> in its IAM. None of it is fully standardised.&lt;/p>
&lt;p>Two honest caveats. SPIFFE answers &amp;ldquo;who&amp;rdquo;, not &amp;ldquo;why&amp;rdquo;: to know whether the action is the one the user asked for you need traceability (&lt;a href="https://blog.lo0.es/en/posts/mcp-deep-observability-opentelemetry/">MCP with OTel&lt;/a>) and runtime detection with &lt;a href="https://blog.lo0.es/en/posts/tetragon-cilium-security-syscalls-kernel/">Tetragon&lt;/a>. And both identities must be composed: the agent proves its own with an SVID and propagates the delegating user&amp;rsquo;s in the token, authorising on the pair. Collapsing them into one is how you end up with agents able to do, in the system&amp;rsquo;s name, things no user could.&lt;/p>
&lt;h2 id="part-b--isolation-of-the-execution-environment">Part B — Isolation of the execution environment&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Three rungs of isolation: container with a shared kernel, virtual machine sandbox with Kata Containers, and trusted execution environment with Confidential Containers, showing which components stay inside the trusted computing base in each case">
&lt;text x="410" y="22" text-anchor="middle" font-family="sans-serif" font-size="13" font-weight="700" fill="currentColor">What stays INSIDE the trusted computing base (TCB)&lt;/text>
&lt;rect x="20" y="44" width="240" height="200" rx="10" fill="none" stroke="currentColor" stroke-width="1.6"/>
&lt;text x="140" y="68" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="700" fill="currentColor">1. Container (runc)&lt;/text>
&lt;text x="140" y="92" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">namespaces + cgroups + seccomp&lt;/text>
&lt;line x1="40" y1="106" x2="240" y2="106" stroke="currentColor" stroke-width="1"/>
&lt;text x="140" y="126" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">host kernel (shared)&lt;/text>
&lt;text x="140" y="146" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">container runtime&lt;/text>
&lt;text x="140" y="166" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">hypervisor / firmware&lt;/text>
&lt;text x="140" y="186" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">platform operator&lt;/text>
&lt;text x="140" y="216" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="currentColor">startup: ~1-6 s&lt;/text>
&lt;text x="140" y="234" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="currentColor">cost: zero&lt;/text>
&lt;rect x="290" y="44" width="240" height="200" rx="10" fill="none" stroke="currentColor" stroke-width="1.6"/>
&lt;text x="410" y="68" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="700" fill="currentColor">2. Kata Containers&lt;/text>
&lt;text x="410" y="92" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">own kernel + lightweight VM&lt;/text>
&lt;line x1="310" y1="106" x2="510" y2="106" stroke="currentColor" stroke-width="1"/>
&lt;text x="410" y="126" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">host kernel: OUT&lt;/text>
&lt;text x="410" y="146" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">container runtime: OUT&lt;/text>
&lt;text x="410" y="166" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">hypervisor / firmware: in&lt;/text>
&lt;text x="410" y="186" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">platform operator: in&lt;/text>
&lt;text x="410" y="216" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="currentColor">startup: +1-2 s over runc&lt;/text>
&lt;text x="410" y="234" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="currentColor">cost: VM memory&lt;/text>
&lt;rect x="560" y="44" width="240" height="200" rx="10" fill="none" stroke="currentColor" stroke-width="2.2"/>
&lt;text x="680" y="68" text-anchor="middle" font-family="sans-serif" font-size="12" font-weight="700" fill="currentColor">3. Confidential Containers&lt;/text>
&lt;text x="680" y="92" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">TEE (SEV-SNP / TDX) + GPU in CC&lt;/text>
&lt;line x1="580" y1="106" x2="780" y2="106" stroke="currentColor" stroke-width="1"/>
&lt;text x="680" y="126" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">host kernel: OUT&lt;/text>
&lt;text x="680" y="146" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">hypervisor: OUT&lt;/text>
&lt;text x="680" y="166" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">platform operator: OUT&lt;/text>
&lt;text x="680" y="186" text-anchor="middle" font-family="sans-serif" font-size="10.5" fill="currentColor">CPU/GPU and their firmware: in&lt;/text>
&lt;text x="680" y="216" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="currentColor">startup: +10 s or more&lt;/text>
&lt;text x="680" y="234" text-anchor="middle" font-family="sans-serif" font-size="10.5" font-style="italic" fill="currentColor">cost: 0-25 % of performance&lt;/text>
&lt;text x="410" y="272" text-anchor="middle" font-family="sans-serif" font-size="11" fill="currentColor">Each rung takes components out of the TCB. What leaves the TCB can no longer read your memory.&lt;/text>
&lt;text x="410" y="290" text-anchor="middle" font-family="sans-serif" font-size="11" font-style="italic" fill="currentColor">The right question is not "how much does it isolate", but "from whom".&lt;/text>
&lt;/svg>
&lt;/div>
### Kata Containers: the virtual machine sandbox
&lt;p>Kata replaces &lt;code>runc&lt;/code> with a runtime that boots a lightweight virtual machine per pod, with its own kernel and a &lt;code>kata-agent&lt;/code> inside; for Kubernetes it is transparent via &lt;code>RuntimeClass&lt;/code>. As of July 2026 the stable branch is 3.32.x (3.32.0 is from June 2026, with Rust 1.94, Go 1.25.11, QEMU 11.0.1, guest kernel 6.18.35 and containerd 2.3), and in April 2026 the 4.0.0 preview was published, making the Rust runtime (&lt;code>runtime-rs&lt;/code>) the default and leaving the Go one deprecated until 5.0.0, because of memory footprint.&lt;/p>
&lt;p>For GPUs the route is &lt;strong>VFIO passthrough&lt;/strong>. The &lt;a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/deploy-kata-containers.html">NVIDIA GPU Operator&lt;/a> automates it with &lt;code>sandboxWorkloads.enabled=true&lt;/code> and &lt;code>sandboxWorkloads.mode=kata&lt;/code>, installing the VFIO Manager, the Sandbox Device Plugin, the Confidential Computing Manager and the Kata Manager. It requires virtualisation and ACS in the BIOS, IOMMU, kata-deploy 3.29.0 or later, containerd (not CRI-O), the &lt;code>KubeletPodResourcesGet&lt;/code> &lt;em>feature gate&lt;/em> (on by default from Kubernetes 1.34) and removing the NVIDIA drivers from the host: the GPU is handed over whole to the VM and the guest manages it.&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">vllm-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">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">inferencia&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">runtimeClassName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kata-qemu-nvidia-gpu&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">vllm&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">vllm/vllm-openai@sha256:aa11bb22cc33&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">resources&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">limits&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">nvidia.com/pgpu&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;1&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Two measurable costs. Memory: the VM reserves its own and you have to declare it as &lt;code>overhead&lt;/code> in the &lt;code>RuntimeClass&lt;/code> so the scheduler does not overcommit the node. And startup: according to the SESAME'24 study on serverless confidential containers, cold start goes from around 6 s with &lt;code>runc&lt;/code> to around 7 s with Kata, and warm start from 1 s to 2 s. Acceptable for an engine that takes minutes to load weights; unacceptable for ephemeral functions.&lt;/p>
&lt;h3 id="confidential-containers-the-tee">Confidential Containers: the TEE&lt;/h3>
&lt;p>&lt;a href="https://confidentialcontainers.org/">Confidential Containers&lt;/a> (CoCo) takes the hypervisor and the platform operator out of the trust base. The CNCF promoted it to &lt;em>incubating&lt;/em> on 22 July 2026, with more than 150 active contributors, 26 repositories and over 1,200 merged &lt;em>pull requests&lt;/em>, and with Microsoft Azure, Intel, AMD, IBM, NVIDIA, Alibaba and Red Hat behind it. A serious project, but &lt;em>incubating&lt;/em> means it is not graduated and that its integration surface changes between versions.&lt;/p>
&lt;p>The components are the CoCo pods (unmodified containers run in a TEE via Kata) and &lt;strong>Trustee&lt;/strong>: &lt;strong>KBS&lt;/strong> (&lt;em>Key Broker Service&lt;/em>, which releases secrets conditionally), &lt;strong>AS&lt;/strong> (&lt;em>Attestation Service&lt;/em>, which validates the hardware evidence) and &lt;strong>RVPS&lt;/strong> (&lt;em>Reference Value Provider Service&lt;/em>, which holds the reference values). Inside the guest, the Attestation Agent collects the evidence and the Confidential Data Hub consumes the secrets.&lt;/p>
&lt;p>The flow is a clean instance of the RATS architecture from RFC 9334: the agent inside the TEE is the &lt;em>attester&lt;/em>, the Attestation Service the &lt;em>verifier&lt;/em> and the Confidential Data Hub the &lt;em>relying party&lt;/em>. The evidence is a hardware-signed report with the boot measurements; the AS validates it against the RVPS values; and only if it matches does the KBS release the key that decrypts the image layers or the weights. Without valid attestation there is no key, and without a key there is no model: that is what stops the operator extracting it.&lt;/p>
&lt;p>A mapping warning: the &lt;code>confidential-containers/operator&lt;/code> repository was archived in February 2026 and deployment moved to the Helm charts and the &lt;code>trustee-operator&lt;/code>. As of July 2026 the latest chart release is v0.21.0, aligned with kata-deploy 3.31.0, and Trustee v0.20.0, with TLS 1.3 and post-quantum cryptography, external plugins for the KBS and multi-GPU support via Intel Trust Authority and NVIDIA NVSwitch.&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">confidentialcontainers.org/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">TrusteeConfig&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">trusteeconfig&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">operators&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">profileType&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">Restrictive&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">httpsSpec&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">tlsSecretName&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">trustee-tls-cert&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>Permissive&lt;/code> profile exists for development and should not leave it: it is the mode in which attestation does not block.&lt;/p>
&lt;h3 id="the-hardware-cpu-and-gpu">The hardware: CPU and GPU&lt;/h3>
&lt;p>On the CPU side the two relevant TEEs are &lt;strong>AMD SEV-SNP&lt;/strong> and &lt;strong>Intel TDX&lt;/strong>; NVIDIA&amp;rsquo;s reference architecture sets EPYC Milan/Genoa and Xeon Emerald/Granite Rapids as the baseline. Host support is no longer the hard part.&lt;/p>
&lt;p>On the GPU side, the &lt;a href="https://cacm.acm.org/practice/creating-the-first-confidential-gpus/">technical description of the design of the first confidential GPUs&lt;/a> explains what CC mode on an H100 buys you. Memory is split into a &lt;strong>Compute Protected Region&lt;/strong> that hardware firewalls stop both the CPU over PCIe and other GPUs over NVLink from accessing. Everything crossing the CPU-GPU boundary passes through &lt;strong>bounce buffers&lt;/strong> encrypted with AES-GCM-256, and command buffers and CUDA kernels are encrypted and signed before crossing the bus. There is a chain of trust from GPU boot with a signed attestation report, and only NVIDIA-signed firmware runs in CC mode, validated against NRAS or locally in isolated environments. And the performance counters are disabled in hardware as a side-channel mitigation: that is the telemetry much of &lt;a href="https://blog.lo0.es/en/posts/gpu-observability-llm-inference-twelve-dcgm-vllm-metrics/">observability with DCGM&lt;/a> depends on.&lt;/p>
&lt;p>What it does not protect: the package&amp;rsquo;s HBM memory is not encrypted (it is considered resistant to interposers, which is a claim about physical difficulty and not a cryptographic guarantee); nothing against denial of service, because the operator who cannot read you can still switch you off; nothing against timing or access-pattern side channels; and nothing against a bug in your own code inside the enclave or prompt injection, which remains the job of &lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">guardrails&lt;/a>.&lt;/p>
&lt;p>On Blackwell, NVIDIA announces (2 July 2026) NVLink encryption that extends confidential computing to up to 8 GPUs, non-existent on Hopper and a condition for serving a large model with tensor parallelism. The reference architecture lists H100, H200, RTX Pro 6000 Blackwell Server Edition and B200 in single-GPU passthrough, and H100/H200 in PPCIe mode and B200 for multi-GPU.&lt;/p>
&lt;h3 id="the-overhead-numbers-with-source-and-with-judgement">The overhead numbers, with source and with judgement&lt;/h3>
&lt;p>Here it pays to distrust everybody, papers included. The published spread runs from 0 % to 28 % because the studies do not measure the same thing.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Source (date)&lt;/th>
&lt;th>What it measures&lt;/th>
&lt;th>Result&lt;/th>
&lt;th>Nature&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>NVIDIA, Blackwell blog (Jul. 2026)&lt;/td>
&lt;td>B200 with CC enabled&lt;/td>
&lt;td>−1.0 % to −7.5 % throughput; per-token latency under 8 %; &amp;ldquo;up to 98 % of native performance&amp;rdquo;&lt;/td>
&lt;td>&lt;strong>Vendor claim&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Corvex, HGX B200 with TDX (2026)&lt;/td>
&lt;td>&amp;ldquo;Verified&amp;rdquo; deployment, encrypted NVSwitch&lt;/td>
&lt;td>&amp;ldquo;Near-native performance&amp;rdquo;, with no figures of its own&lt;/td>
&lt;td>&lt;strong>Commercial claim&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>arXiv 2409.03992 (2024)&lt;/td>
&lt;td>H100 GPU only in CC, Llama-3.1 8B/70B&lt;/td>
&lt;td>−0.36 % to 6.85 % throughput; TTFT up to +19 %; overhead → 0 as the model grows&lt;/td>
&lt;td>Independent, partial&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>arXiv 2509.18886 (Sep. 2025)&lt;/td>
&lt;td>CPU TEE (TDX/SGX) and GPU TEE (H100), Llama2 7B/13B/70B&lt;/td>
&lt;td>CPU TEE: under 10 % throughput and 20 % latency. GPU TEE: 4-8 %&lt;/td>
&lt;td>Independent&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>arXiv 2607.19353 (May 2026)&lt;/td>
&lt;td>&lt;strong>Full stack&lt;/strong>: H100 in CC inside a CVM with TDX, Mistral-7B and Qwen3-30B-A3B under load&lt;/td>
&lt;td>TTFT +21.8 % and +27.8 %; throughput −17.7 % and −21.1 %; closed-loop, 11.5-20.2 %&lt;/td>
&lt;td>Independent, &lt;strong>complete&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The reconciliation: the penalty is in the data path, not in the GPU&amp;rsquo;s compute. NVIDIA itself quantifies the bottleneck: the effective bandwidth of the CPU-GPU interconnect in CC mode is limited by the CPU&amp;rsquo;s encryption throughput, &amp;ldquo;around 4 GB/s&amp;rdquo;. From there, three rules. The higher the compute/I-O ratio, the lower the overhead: a 70B with large batches and long sequences amortises the toll almost entirely, and a 7B with small batches and short prompts pays it in full. Adding the CPU TEE brings its own cost, and that is where most of the difference between 4-8 % and 20 % lies. And model loading and cold start are the worst cases, with tens of gigabytes crossing an encrypted bus at 4 GB/s, which makes everything discussed in &lt;a href="https://blog.lo0.es/en/posts/pantry-lift-from-disk-to-hbm-why-the-kitchen-opens-late/">from disk to HBM&lt;/a> and in &lt;a href="https://blog.lo0.es/en/posts/speeding-up-model-cold-start-minutes-to-seconds/">speeding up cold start&lt;/a> more important.&lt;/p>
&lt;p>The recommendation from the authors of the 2026 study, reserving between 15 % and 25 % extra capacity, is the figure I would take to a &lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">capacity planning&lt;/a> exercise, and not the vendor&amp;rsquo;s &amp;ldquo;98 % of native performance&amp;rdquo;. Both can be true at once; only one is prudent for sizing.&lt;/p>
&lt;h3 id="when-each-rung-is-worth-it">When each rung is worth it&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Scenario&lt;/th>
&lt;th>Kata&lt;/th>
&lt;th>CoCo (TEE)&lt;/th>
&lt;th>Reason&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Tenants that do not trust each other&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>Depends&lt;/td>
&lt;td>Kata removes the shared kernel, a real escape vector&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Unaudited third-party code or models&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>Optional&lt;/td>
&lt;td>A shared kernel is too much surface for someone else&amp;rsquo;s code&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Model intellectual property against the &lt;strong>infrastructure operator&lt;/strong>&lt;/td>
&lt;td>Not enough&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>The canonical case: third-party hosting, cloud, a partner providing the iron&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Regulated third-party data on infrastructure you do not control&lt;/td>
&lt;td>Not enough&lt;/td>
&lt;td>&lt;strong>Yes&lt;/strong>&lt;/td>
&lt;td>See &lt;a href="https://blog.lo0.es/en/posts/infrastructure-defence-ai-compliance/">defence&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/infrastructure-healthcare-ai-compliance/">healthcare&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sovereign on-premise, one tenant, trusted operator&lt;/td>
&lt;td>Useful&lt;/td>
&lt;td>&lt;strong>Expensive over-engineering&lt;/strong>&lt;/td>
&lt;td>It protects you from an adversary you do not have&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Low latency with small batches&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>&lt;strong>Bad idea&lt;/strong>&lt;/td>
&lt;td>The worst point on the bus-encryption cost/benefit curve&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The nuance that saves the most money: the two axes are independent. Kata without a TEE is cheap and delivers most of the isolation between tenants; CoCo only adds something if your threat model includes the operator. If you build a sovereign factory with your own iron and your own staff and you answer &amp;ldquo;no&amp;rdquo; to that question, &lt;strong>the TEE is a cost with no return&lt;/strong>.&lt;/p>
&lt;h3 id="operational-traps">Operational traps&lt;/h3>
&lt;p>&lt;strong>MIG and vGPU against confidential mode.&lt;/strong> NVIDIA&amp;rsquo;s GA 1.0.0 reference architecture is blunt: MIG and vGPU are forbidden in CC mode, and mixed-mode nodes are not supported; every GPU on a host goes in CC or none does. The forums show the typical contradiction of a moving area: in February 2026 a moderator states that MIG+CC is not supported and in April a user cites the MIG page suggesting that Hopper and Blackwell already allow it. As of this article the reference architecture overrides the product page: if your multi-tenancy rested on &lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">partitioning the GPU with MIG&lt;/a>, enabling CC sends you back to &amp;ldquo;one whole GPU per tenant&amp;rdquo;.&lt;/p>
&lt;p>&lt;strong>Startup grows non-linearly.&lt;/strong> SESAME'24 breaks down where: SEV memory provisioning adds around one second per 2 GB of guest (SEV pins every page in advance, and an inference VM allocates a lot), the OVMF firmware about three seconds and layer decryption another three to five. Scaling from 0 to 16 instances goes from 16 s with &lt;code>runc&lt;/code> to 190 s: irrelevant for a stable &lt;code>Deployment&lt;/code>, a design change for aggressive autoscaling with &lt;a href="https://blog.lo0.es/en/posts/llm-inference-autoscaling-kubernetes-hpa-keda/">KEDA&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Version coupling is brutal.&lt;/strong> Kata, kata-deploy, GPU Operator, containerd, QEMU with its own patches, guest kernel, NVIDIA driver and Trustee are a matrix that has to be treated as a unit: the reference architecture fixes Kubernetes 1.32 or later, Kata 3.29, GPU Operator 26.3.1 or later, containerd 2.2.2 or later and QEMU 10.1. Add it to the weight the &lt;a href="https://blog.lo0.es/en/posts/llm-inference-operators-kubernetes/">stack&amp;rsquo;s operators&lt;/a> already carry.&lt;/p>
&lt;p>&lt;strong>Attestation fails after a firmware update, and that is normal.&lt;/strong> The RVPS reference values describe a specific boot state. When you patch the SEV-SNP firmware, the microcode or the GPU firmware, the TCB version changes, the evidence stops matching and the KBS does not release keys, so the pods do not start. Since these updates usually respond to a security bulletin, the typical scenario is &amp;ldquo;we applied the critical patch on a Friday and on Saturday inference would not start&amp;rdquo;. The correct procedure reverses the order: update the reference values first with an overlap window, and patch the iron afterwards.&lt;/p>
&lt;p>&lt;strong>The attestation service is a single point of failure by design.&lt;/strong> If the KBS does not answer, no new confidential pod gets its key; those already started survive, but any rescheduling or scaling does not. The same dependency as the SPIRE server and the same treatment: real high availability, its own alert and rehearsed degradation.&lt;/p>
&lt;h2 id="closing-the-series-the-complete-chain">Closing the series: the complete chain&lt;/h2>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/kserve-open-inference-protocol-control-plane/">Article 1/4&lt;/a> set the contract: what the endpoint exposes and what control plane governs it reproducibly. In &lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-where-the-bytes-live-registry-oci-artefacts/">2/4&lt;/a> the subject was the provenance of the bytes: which registry they come from, by immutable digest and not by a moving tag. &lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-signature-provenance-aibom-why-you-trust-those-bytes/">3/4&lt;/a> added the cryptographic proof of what they are, with signatures, build attestations and an AIBOM verified at admission. This 4/4 closes with who runs them and where: an SVID that proves the serving process&amp;rsquo;s identity and an isolation rung chosen for the adversary you actually have. If one of the four fails, the other three are worth less than they look: an impeccably signed model served by an unidentified process on a machine a third party can dump is still a problem.&lt;/p>
&lt;p>With a limited budget, the order follows cost per unit of risk removed. First, digests everywhere: almost free, and it removes a whole family of substitution attacks. Second, signature verified at admission, low cost and the first thing an auditor asks to see. Third, a declarative contract and control plane, which make everything else auditable. Fourth, workload identity with SPIFFE/SPIRE: expensive to operate, but it is the only thing that turns east-west traffic into something authorisable, and it becomes mandatory as soon as agents with MCP arrive. And last, the TEE, only if the infrastructure operator is in your threat model; Kata without a TEE can jump ahead to fourth place if there are tenants that do not trust each other.&lt;/p>
&lt;h3 id="mapping-the-series-to-ens-isoiec-42001-and-the-eu-ai-act">Mapping the series to ENS, ISO/IEC 42001 and the EU AI Act&lt;/h3>
&lt;p>The detail is in &lt;a href="https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/">technical controls ENS × ISO 42001 × EU AI Act&lt;/a>; this table is the summary by link.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Link&lt;/th>
&lt;th>ENS measure (RD 311/2022)&lt;/th>
&lt;th>ISO/IEC 42001 (Annex A)&lt;/th>
&lt;th>EU AI Act&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1/4 Contract and control plane&lt;/td>
&lt;td>&lt;code>op.exp.2&lt;/code>; &lt;code>op.mon.1&lt;/code>&lt;/td>
&lt;td>A.6 life cycle&lt;/td>
&lt;td>Art. 12 (record-keeping); Art. 13 (transparency)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2/4 Registry and distribution by digest&lt;/td>
&lt;td>&lt;code>op.ext.3&lt;/code>&lt;/td>
&lt;td>A.10 third parties&lt;/td>
&lt;td>Art. 11 and Annex IV (technical documentation)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3/4 Signature, attestations and AIBOM&lt;/td>
&lt;td>&lt;code>op.exp.6&lt;/code>; &lt;code>op.ext.3&lt;/code>&lt;/td>
&lt;td>A.6.2 and A.7 data and traceability&lt;/td>
&lt;td>Art. 15(5), data and model poisoning&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4/4-A Workload identity&lt;/td>
&lt;td>&lt;code>op.acc.1/2/5&lt;/code>; &lt;code>op.exp.11&lt;/code>; &lt;code>mp.com.2-3&lt;/code>&lt;/td>
&lt;td>A.9 use of the AI system&lt;/td>
&lt;td>Art. 15(5), unauthorised third parties&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4/4-B Isolation and TEE&lt;/td>
&lt;td>&lt;code>mp.info.3&lt;/code>; &lt;code>op.exp.2&lt;/code>; &lt;code>mp.com.4&lt;/code>&lt;/td>
&lt;td>A.6 life cycle controls&lt;/td>
&lt;td>Art. 15(4) robustness; Art. 15(5)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Art. 15(5) carries the most weight: it requires high-risk systems to be &amp;ldquo;resilient against attempts by unauthorised third parties to alter their use, outputs or performance&amp;rdquo;, and it enumerates data and model poisoning and confidentiality attacks. Links 3 and 4 are its technical implementation. For the management framework, see &lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">ISO/IEC 42001 as an AIMS&lt;/a> and the &lt;a href="https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/">AI Act mapping&lt;/a>.&lt;/p>
&lt;h2 id="for-an-inference-factory">For an inference factory&lt;/h2>
&lt;p>&lt;strong>Start workload identity with the gateway↔engine pair and with the agents, not with the whole cluster.&lt;/strong> Registering all forty workloads on day one is the most reliable way to abandon the project: register two entries, set up the Istio policy that requires the gateway&amp;rsquo;s principal and live with it for a couple of weeks to learn what breaks when the certificate rotates. Then the agents with MCP, where static credentials are the entire threat model.&lt;/p>
&lt;p>&lt;strong>Separate the Kata decision from the TEE decision, and take them in that order.&lt;/strong> Kata is a &lt;code>RuntimeClass&lt;/code> change with a second of startup and some memory. CoCo is a redesign: you lose MIG and the GPU&amp;rsquo;s performance counters, you gain ten seconds or more of startup, you pay 15-25 % of capacity and you add two dependencies whose failure prevents workloads from starting. Evaluate the second with a written threat model, not with a slide.&lt;/p>
&lt;p>&lt;strong>If you go to CoCo, rehearse patch day before you need it.&lt;/strong> The failure that will take your inference down will not be an attack, it will be a firmware update that misaligns the RVPS reference values. Write the runbook (update the RVPS with overlap, patch, retire the old value), rehearse it on one node and monitor the attestation success rate as a first-class metric, just as for SPIRE: alert on attestation failures before they turn into 401s at the gateway.&lt;/p>
&lt;p>That closes the series: from the contract to the artefact, from the artefact to its proof, and from the proof to the process and the machine. What lies below is no longer a chain of trust but daily operation: seeing what each process does with &lt;a href="https://blog.lo0.es/en/posts/tetragon-cilium-security-syscalls-kernel/">Tetragon&lt;/a> and measuring whether what is served is still good with &lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">evals&lt;/a>. Trust is established once; vigilance is continuous.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kserve-open-inference-protocol-control-plane/">Model chain of trust (1/4): KServe and the Open Inference Protocol&lt;/a> — the API contract that opens the series.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-where-the-bytes-live-registry-oci-artefacts/">Model chain of trust (2/4): registry and distribution with OCI and ORAS&lt;/a> — why the digest is everything.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-signature-provenance-aibom-why-you-trust-those-bytes/">Model chain of trust (3/4): signature, provenance and AIBOM&lt;/a> — the proof that Sigstore selectors turn into a precondition of identity.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/mcp-grows-up-authentication-keycloak/">MCP grows up: authentication with Keycloak&lt;/a> — the user identity to compose with the workload one.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/isolating-ai-agents-workstation-to-cluster/">Isolating AI agents: from workstation to cluster&lt;/a> — the threat model that SPIFFE gives a non-transferable credential.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/tetragon-cilium-security-syscalls-kernel/">Tetragon: runtime security&lt;/a> — the vigilance that starts where the chain of trust ends.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/infrastructure-defence-ai-compliance/">Infrastructure and regulatory compliance in defence&lt;/a> — where the TEE stops being over-engineering.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/gpu-cluster-as-multi-tenant-platform/">H100 cluster: multi-tenant platform&lt;/a> — where MIG and confidential mode collide head-on.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/keycloak-identity-plane-ai-platform/">Keycloak in an AI platform&lt;/a> — the other identity plane, the one for people, and where it overlaps with SPIFFE.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/completing-keycloak-for-mcp-protected-resource/">Completing Keycloak for MCP&lt;/a> — the authorisation of the tools an agent invokes, which neither SPIFFE nor the IdP solves.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>CNCF, &lt;em>SPIFFE and SPIRE Projects Graduate from CNCF Incubator&lt;/em> (20 sep. 2022) — &lt;a href="https://www.cncf.io/announcements/2022/09/20/spiffe-and-spire-projects-graduate-from-cloud-native-computing-foundation-incubator/">https://www.cncf.io/announcements/2022/09/20/spiffe-and-spire-projects-graduate-from-cloud-native-computing-foundation-incubator/&lt;/a>&lt;/li>
&lt;li>SPIFFE, &lt;em>SPIFFE Concepts&lt;/em> (SPIFFE ID, trust domain, SVID, Workload API) — &lt;a href="https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/">https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/&lt;/a>&lt;/li>
&lt;li>SPIFFE, &lt;em>SPIFFE Federation specification&lt;/em> — &lt;a href="https://spiffe.io/docs/latest/spiffe-specs/spiffe_federation/">https://spiffe.io/docs/latest/spiffe-specs/spiffe_federation/&lt;/a>&lt;/li>
&lt;li>SPIFFE, &lt;em>Scaling SPIRE&lt;/em> (dimensionamiento y punto único de fallo) — &lt;a href="https://spiffe.io/docs/latest/planning/scaling_spire/">https://spiffe.io/docs/latest/planning/scaling_spire/&lt;/a>&lt;/li>
&lt;li>spiffe/spire, &lt;em>Releases&lt;/em> (v1.15.0 de 19 may. 2026; v1.15.1 de 28 may. 2026, parche de &lt;code>azure_imds&lt;/code>) — &lt;a href="https://github.com/spiffe/spire/releases">https://github.com/spiffe/spire/releases&lt;/a>&lt;/li>
&lt;li>spiffe/spire, &lt;em>Kubernetes Workload Attestor plugin&lt;/em> (selectores, kubelet, cgroups, Sigstore) — &lt;a href="https://github.com/spiffe/spire/blob/main/doc/plugin_agent_workloadattestor_k8s.md">https://github.com/spiffe/spire/blob/main/doc/plugin_agent_workloadattestor_k8s.md&lt;/a>&lt;/li>
&lt;li>spiffe/spire, &lt;em>SPIRE Server configuration reference&lt;/em> (&lt;code>default_x509_svid_ttl&lt;/code>, &lt;code>default_jwt_svid_ttl&lt;/code>, &lt;code>ca_ttl&lt;/code>) — &lt;a href="https://github.com/spiffe/spire/blob/main/doc/spire_server.md">https://github.com/spiffe/spire/blob/main/doc/spire_server.md&lt;/a>&lt;/li>
&lt;li>Istio, &lt;em>SPIRE integration&lt;/em> (Envoy SDS, SPIFFE CSI Driver, trust domain) — &lt;a href="https://istio.io/latest/docs/ops/integrations/spire/">https://istio.io/latest/docs/ops/integrations/spire/&lt;/a>&lt;/li>
&lt;li>SPIFFE, &lt;em>OPA Authorization with Envoy and JWT-SVIDs&lt;/em> — &lt;a href="https://spiffe.io/docs/latest/microservices/envoy-jwt-opa/readme/">https://spiffe.io/docs/latest/microservices/envoy-jwt-opa/readme/&lt;/a>&lt;/li>
&lt;li>The New Stack, &lt;em>How Cilium&amp;rsquo;s Mutual Authentication Can Compromise Security&lt;/em> — &lt;a href="https://thenewstack.io/how-ciliums-mutual-authentication-can-compromise-security/">https://thenewstack.io/how-ciliums-mutual-authentication-can-compromise-security/&lt;/a>&lt;/li>
&lt;li>Riptides, &lt;em>Bringing SPIFFE to OAuth for MCP&lt;/em> — &lt;a href="https://riptides.io/blog/bringing-spiffe-to-oauth-for-mcp-secure-identity-for-agentic-workloads/">https://riptides.io/blog/bringing-spiffe-to-oauth-for-mcp-secure-identity-for-agentic-workloads/&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>Confidential Containers becomes a CNCF incubating project&lt;/em> (22 jul. 2026) — &lt;a href="https://www.cncf.io/blog/2026/07/22/confidential-containers-becomes-a-cncf-incubating-project/">https://www.cncf.io/blog/2026/07/22/confidential-containers-becomes-a-cncf-incubating-project/&lt;/a>&lt;/li>
&lt;li>Confidential Containers, &lt;em>Attestation with Trustee&lt;/em> (KBS, AS, RVPS, CDH) — &lt;a href="https://confidentialcontainers.org/docs/attestation/">https://confidentialcontainers.org/docs/attestation/&lt;/a>&lt;/li>
&lt;li>Confidential Containers, &lt;em>Deploy Trustee in Kubernetes&lt;/em> (11 feb. 2026) — &lt;a href="https://confidentialcontainers.org/blog/2026/02/11/deploy-trustee-in-kubernetes/">https://confidentialcontainers.org/blog/2026/02/11/deploy-trustee-in-kubernetes/&lt;/a>&lt;/li>
&lt;li>confidential-containers/trustee, &lt;em>Releases&lt;/em> (v0.20.0: TLS 1.3, PQC, multi-GPU ITA, NVSwitch) — &lt;a href="https://github.com/confidential-containers/trustee/releases">https://github.com/confidential-containers/trustee/releases&lt;/a>&lt;/li>
&lt;li>IETF, &lt;em>RFC 9334 — Remote ATtestation procedureS (RATS) Architecture&lt;/em> — &lt;a href="https://www.rfc-editor.org/info/rfc9334/">https://www.rfc-editor.org/info/rfc9334/&lt;/a>&lt;/li>
&lt;li>Kata Containers, &lt;em>Kata Containers 4.0.0 Preview&lt;/em> (28 abr. 2026, &lt;code>runtime-rs&lt;/code> por defecto) — &lt;a href="https://katacontainers.io/blog/release-4-0-0-preview/">https://katacontainers.io/blog/release-4-0-0-preview/&lt;/a>&lt;/li>
&lt;li>kata-containers, &lt;em>Release 3.32.0&lt;/em> — &lt;a href="https://github.com/kata-containers/kata-containers/releases/tag/3.32.0">https://github.com/kata-containers/kata-containers/releases/tag/3.32.0&lt;/a>&lt;/li>
&lt;li>NVIDIA, &lt;em>Deploy with Kata Containers — GPU Operator&lt;/em> — &lt;a href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/deploy-kata-containers.html">https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/deploy-kata-containers.html&lt;/a>&lt;/li>
&lt;li>NVIDIA, &lt;em>Deploying Proprietary Models Securely with Confidential Computing on Self-Hosted Kubernetes&lt;/em> (GA 1.0.0; MIG y vGPU prohibidos en CC) — &lt;a href="https://docs.nvidia.com/enterprise-reference-architectures/deploying-proprietary-models-confidential-compute-self-hosted-kubernetes/latest/reference-implementations.html">https://docs.nvidia.com/enterprise-reference-architectures/deploying-proprietary-models-confidential-compute-self-hosted-kubernetes/latest/reference-implementations.html&lt;/a>&lt;/li>
&lt;li>CACM, &lt;em>Creating the First Confidential GPUs&lt;/em> (CPR, bounce buffers, contadores deshabilitados, HBM no cifrada) — &lt;a href="https://cacm.acm.org/practice/creating-the-first-confidential-gpus/">https://cacm.acm.org/practice/creating-the-first-confidential-gpus/&lt;/a>&lt;/li>
&lt;li>NVIDIA, &lt;em>Confidential Computing on H100 GPUs for Secure and Trustworthy AI&lt;/em> (límite de ~4 GB/s, NRAS) — &lt;a href="https://developer.nvidia.com/blog/confidential-computing-on-h100-gpus-for-secure-and-trustworthy-ai/">https://developer.nvidia.com/blog/confidential-computing-on-h100-gpus-for-secure-and-trustworthy-ai/&lt;/a>&lt;/li>
&lt;li>NVIDIA, &lt;em>Hardware-Rooted AI Security That Won&amp;rsquo;t Slow You Down&lt;/em> (2 jul. 2026; Blackwell, NVLink cifrado hasta 8 GPU) — &lt;a href="https://developer.nvidia.com/blog/hardware-rooted-ai-security-that-wont-slow-you-down">https://developer.nvidia.com/blog/hardware-rooted-ai-security-that-wont-slow-you-down&lt;/a>&lt;/li>
&lt;li>Corvex, &lt;em>Confidential Computing Meets NVIDIA HGX B200&lt;/em> (claim comercial) — &lt;a href="https://www.corvex.ai/blog/confidential-computing-meets-nvidia-hgxtm-b200-secure-ai-without-the-performance-trade-off">https://www.corvex.ai/blog/confidential-computing-meets-nvidia-hgxtm-b200-secure-ai-without-the-performance-trade-off&lt;/a>&lt;/li>
&lt;li>arXiv 2409.03992, &lt;em>Confidential Computing on NVIDIA Hopper GPUs: A Performance Benchmark Study&lt;/em> — &lt;a href="https://arxiv.org/html/2409.03992v1">https://arxiv.org/html/2409.03992v1&lt;/a>&lt;/li>
&lt;li>arXiv 2509.18886, &lt;em>Confidential LLM Inference: Performance and Cost Across CPU and GPU TEEs&lt;/em> (23 sep. 2025) — &lt;a href="https://arxiv.org/abs/2509.18886">https://arxiv.org/abs/2509.18886&lt;/a>&lt;/li>
&lt;li>arXiv 2607.19353, &lt;em>Benchmarking Confidential GPU Inference on NVIDIA H100 under Intel TDX&lt;/em> (may. 2026; heurística del 15-25 %) — &lt;a href="https://arxiv.org/html/2607.19353">https://arxiv.org/html/2607.19353&lt;/a>&lt;/li>
&lt;li>Segarra et al., &lt;em>Serverless Confidential Containers: Challenges and Opportunities&lt;/em> (SESAME'24) — &lt;a href="https://carlossegarra.com/assets/papers/sesame24-serverlesscoco.pdf">https://carlossegarra.com/assets/papers/sesame24-serverlesscoco.pdf&lt;/a>&lt;/li>
&lt;li>EU Artificial Intelligence Act, &lt;em>Article 15: Accuracy, Robustness and Cybersecurity&lt;/em> — &lt;a href="https://artificialintelligenceact.eu/article/15/">https://artificialintelligenceact.eu/article/15/&lt;/a>&lt;/li>
&lt;li>EU Artificial Intelligence Act, &lt;em>Article 12: Record-Keeping&lt;/em> — &lt;a href="https://artificialintelligenceact.eu/article/12/">https://artificialintelligenceact.eu/article/12/&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>