Skip to content

TEE nodes

A TEE node proves what runs inside its trust domain (TD) with an attestation quote. The quote says nothing about who reads your traffic on the way there: TLS in front of a node usually ends at a proxy, load balancer or relay outside the TD, and everything crossing it is readable there, bearer tokens included. sealed=True closes that gap. The client verifies the node’s quote, then encrypts every request to a key the quote commits to, so only the attested TD can read it, whatever sits in front.

A TeePolicy says which TDs to trust. The quote is checked here, against Intel’s collateral, and never by asking the node:

from pathlib import Path
from calimero_client_py import TeePolicy
# The node release's published-mrtds.json, from a release you verified.
release = Path("trusted/mero-tee-v2.3.78.published-mrtds.json").read_text()
policy = TeePolicy.from_releases([release], "locked-read-only")

A policy names the image by all of its measurements: the MRTD and RTMR1–3, each required. The MRTD alone names no image. It measures the platform’s TD firmware, which on GCP every mero-tee image, profile and release shares, and so does any other TD there, whatever it runs. The image is in RTMR1–3: the kernel, the command line with the root-filesystem hash, and the role and profile the image records at boot. A policy that leaves any of them out raises ValueError. Mock quotes are never accepted.

TeePolicy.from_releases(releases, profile) reads each release’s published-mrtds.json:

  • one profile: locked-read-only for production. A debug image of the same release has other RTMRs and is refused;
  • every release your nodes may run. Each release has its own RTMRs, so during a rollout pass the old and the new one, and drop the old one once no node runs it;
  • only the TCB statuses every one of them accepts, which the release lists (today UpToDate and OutOfDate: GCP’s TDX hosts report OutOfDate).

Registers are allowed one by one, so a policy of two releases accepts any mix of their registers. Trust only releases you would accept any mix of. Where the file comes from is the trust decision: take it from a release you verified (cosign verify-blob against its .bundle.json), not from the node.

You can also give the lists yourself:

policy = TeePolicy(
allowed_mrtd=[mrtd],
allowed_rtmr1=[rtmr1],
allowed_rtmr2=[rtmr2],
allowed_rtmr3=[rtmr3],
allowed_tcb_statuses=["UpToDate", "OutOfDate"],
)

To also require that the node runs a particular application, name it with the bytecode hash it must have:

policy = TeePolicy.from_releases(
[release], "locked-read-only", application_id=app_id, application_hash=bytecode_hash
)
from calimero_client_py import create_connection, create_client
connection = create_connection(
"https://relay.example/node",
node_name="my-tee-node",
tee=policy,
sealed=True,
)
client = create_client(connection)

On the first request the node binds its X25519 transport key into a fresh quote, and the connection seals nothing until that quote verifies under your policy. A request that fails attestation raises RuntimeError and is never sent. After that, every request is sealed in a session to the attested key, and so is its response. Token refreshes are sealed too. A proxy carries traffic it cannot read, forge or replay. Each session’s keys come from throwaway keys on both sides, so once a session ends, at most an hour later, what was sent in it cannot be opened, even with the node’s transport key.

When the node restarts, it has a new transport key. The connection attests again, through the same policy, before it resends.

WebSockets cannot be sealed. A sealed connection is for the request/response API this client uses.

What a sealed request can reach depends on who checks requests. A node running embedded auth checks them itself, so everything this client calls can be sealed. A node whose auth is left to a proxy (auth_mode = "proxy", which is how hosted TEE nodes run) cannot let an opened request reach a route the proxy guards: the proxy sees only POST /sealed/v2, never the route inside. So such a node serves, sealed, only what it serves without a credential: the health and TEE probes, and a context’s .../intents where delegated execution is public. Anything else is refused inside the envelope with 403 and sealed_route_unguarded, and is never executed. Use sealed=True against embedded-auth nodes. Against a hosted node, the sealed path is delegated execution. See how a client reaches a node.

If you verified a node’s transport key yourself, pass it instead of a policy:

create_connection(url, sealed=True, transport_public_key="<hex>")

A connection with a fixed key cannot follow a restart. Its requests then fail until you reconnect with the node’s new key.

create_connection refuses combinations that would not protect anything: a key or a policy without sealed=True, or sealed=True with neither.