Security & Threat Model
Mero TEE’s security rests on one root of trust — a hardware-signed Intel TDX quote — and a chain of delegations built on top of it. This page states that chain honestly: what you must trust for the system to be secure, what an attacker in each position can and cannot do, which mitigations enforce those boundaries, and where the guarantees stop.
It complements the Trust Model, which walks the bidirectional node ⇄ KMS handshake step by step. Read that first for how attestation works; read this for what it protects against and what it does not.
Trust assumptions
Section titled “Trust assumptions”Every claim Mero TEE makes reduces to trusting these five things. If any one is false, the guarantees above it do not hold.
-
The TEE hardware (Intel TDX). The root of trust is a quote signed by the CPU. We assume the TDX implementation, CPU microcode, and DCAP quoting enclave are sound, and that the register measurements (MRTD, RTMR0–3) cannot be forged from inside the guest. Physical attacks, side channels, and compromised microcode are out of scope — no software mitigation in this repo defends against a broken CPU.
-
Intel Trust Authority (ITA). The public attestation verifier does not re-implement DCAP appraisal; it submits the quote to ITA (
api.trustauthority.intel.com) and trusts ITA’s signed JWT, verified against Intel’s published JWKS (portal.trustauthority.intel.com/certs, issuerhttps://portal.trustauthority.intel.com). Intel’s signature is the trust anchor (attestation-verifier/api/verify.js). If ITA is wrong or its signing key is compromised, the verifier’s verdict is wrong. -
The
calimero-tee-attestationcrate. The KMS itself delegates the DCAP quote-signature check, the nonce binding, theapplication_databinding, and the TCB-status extraction toverify_attestationin thecalimero-tee-attestationcrate — a git dependency oncalimero-network/corepinned by revision (mero-kms/Cargo.toml,mero-kms/src/handlers/get_key.rs). The KMS does not reimplement quote cryptography; it consumes aVerificationResultand layers policy on top. This is a real trust boundary: a bug in that crate is a bug in the KMS’s verification. -
Google’s TDX firmware and the KMS release workflow. The KMS runs on GCP Confidential VMs. Google’s firmware is measured (MRTD), so a change shows up, but it is Google’s; Google can stop VMs (availability) but cannot read TD memory. The release workflow that builds the KMS image defines what “same as me” means for the cluster join, and so which code may ever hold a cluster root. The build is not reproducible yet (the base image is pinned to a family, and apt installs from live repositories), so outsiders cannot yet rebuild the image to check its measurements.
-
The signed policy chain. The KMS measurements merod pins come from a release policy (
kms-attestation-policy[.<profile>].json) signed with keyless Sigstore (Fulcio certificate + Rekor transparency log), issued under the GitHub Actions OIDC identity (https://token.actions.githubusercontent.com) and verified by--certificate-identity-regexp+--certificate-oidc-issuer(scripts/release/verify-kms-release-assets.sh). The node allowlist the KMS enforces is baked into its image and so covered by those same measurements. Trusting the policy means trusting that pipeline identity and the Sigstore/Rekor infrastructure — there is no signing private key stored in the repository (SECURITY.md).
What an attacker can and cannot do
Section titled “What an attacker can and cannot do”Each row is an attacker position, not a single exploit. “Cannot” means the listed control blocks it given the trust assumptions above hold.
| Attacker position | Can do | Cannot do (and why) |
|---|---|---|
| On-path network (MITM) | Observe/drop traffic; replay a captured request once. | Obtain a key: /get-key binds a single-use, TTL-bound challenge nonce that is consumed before any crypto check, so a replay fails on the second attempt (get_key.rs, Trust Model). Impersonate the KMS: a fake KMS cannot produce a valid quote with the expected KMS measurements (Plane 1 /attest). |
| Malicious node operator (no TEE) | Send arbitrary key requests. | Get a key from outside TDX: a machine that is not genuine TDX cannot produce a DCAP-valid quote, and verify_attestation rejects it. Spoof another node’s identity: the request must carry a signature by the key that derives to the claimed peerId, and the quote’s report_data must contain SHA-256(peerId) (hash_peer_id, get_key.rs). |
| Tampered / unapproved node image (inside TDX) | Boot and attest as a real TDX guest. | Pass policy: a modified image yields different MRTD/RTMR measurements, and the KMS rejects any register value not on its allowlist (measurement_policy_rejected, 403). Escalate profile: each image profile has a distinct hardware-measured RTMR3; a debug node’s quote is rejected by a production policy. The root filesystem is covered too: it is a read-only EROFS image behind dm-verity, whose root hash is on the measured cmdline, so an offline edit to the boot disk fails every read of a changed block (#334). /etc and /var are a tmpfs overlay: nothing the running node writes reaches the boot disk. |
| Cloud project / host operator of a node VM | Snapshot, read and rewrite the node’s disks; set instance metadata (KMS URL, tee-release-version, log/metrics endpoints); read the serial port. |
Read the node’s data: the data disk is LUKS2 (aes-xts + hmac-sha256 integrity) with a key the KMS releases only to the attested TD, so a snapshot is ciphertext and an offline edit fails to read rather than decrypting into something merod acts on. Downgrade the KMS policy: tee-release-version below the image’s own version is refused (MERO_TEE_MIN_VERSION), and every profile pins its own policy profile (MERO_TEE_PROFILE), so a node is verified only against its own profile’s KMS policy. Harvest memory from the boot disk: the journal is volatile, core dumps and swap are off, and the boot log is not sent to the serial console on locked-read-only. |
| Node on outdated microcode / TCB | Produce a syntactically valid quote. | Get a key if its TCB status is not allow-listed: enforce_tcb_status rejects any status outside allowed_tcb_statuses (tcb_status_rejected, 403). |
| Cloud project / host operator of the KMS | Stop or delete replicas (deny service), set kms-bootstrap / kms-peers metadata, snapshot disks, start their own genuine cluster. |
Read the cluster root: it lives only in TD memory, is never written (read-only verity root, tmpfs, no swap or core dumps), and is given only to a TD with exactly the same measurements that is not a debug TD. Change what the KMS releases keys to: the node allowlist, profile and environment are baked into the image and measured, and metadata can only choose bootstrap-or-join and peer addresses. A cluster they start themselves holds a different root and derives no existing node’s keys. Deleting every replica loses the root: running nodes carry on, restarted nodes of that release are replaced from a new cluster. |
| Attacker with a captured valid quote | Present it again. | Reuse it: the report_data nonce binds a quote to one challenge, and the challenge is single-use. A quote pasted into the public verifier without a verifier-chosen nonce is flagged as not freshness-checked (Verification). |
| SSRF via the public verifier | Ask the verifier to fetch a URL. | Reach arbitrary internal hosts: /api/verify fetches only node URLs, constrained to the NODE_ALLOWED_HOSTS regex allowlist (default: a bare IPv4 address) and to public addresses, without following redirects or echoing a node’s error body, and never fetches from a KMS (verify.js). |
| Oversized / malformed request | Send junk to any endpoint. | Exhaust the KMS: request bodies are capped at 64 KiB (MAX_REQUEST_BODY_BYTES, handlers/mod.rs); malformed input maps to typed 4xx errors, never a panic. |
| Supply-chain (release artifacts) | Publish a fake release or tamper an asset. | Pass verification: assets ship with Sigstore .sig/.pem bundles and Rekor entries; cosign verify-blob checks the GitHub OIDC identity before an operator trusts a policy or MRTD set (scripts/release/, SECURITY.md). |
Mitigations
Section titled “Mitigations”The Trust Model’s mitigations table is the authoritative per-threat list (rogue KMS, rogue node, replay, peer-ID spoofing, profile escalation, policy tampering). Rather than duplicate it, the mitigations group into four mechanisms:
-
Hardware attestation on both sides. Neither the node nor the KMS trusts TLS identity or operator assertion — each proves it runs an approved image in genuine TDX via a CPU-signed quote before anything is released. See Verification and Trust Model.
-
Fail-closed policy. When
ENFORCE_MEASUREMENT_POLICYis true (the default), a misconfiguration cannot open the door: the service refuses to start unless MRTD, all four RTMRs, and the TCB allowlist are populated, and at request time an empty allowlist for any register rejects the quote (policy.rs). Debug bypasses live only behind the default-offmock-attestationfeature, which the production build cannot compile in. -
Binding and freshness. A single-use, TTL-bound challenge nonce (consumed before verification) blocks replay;
SHA-256(peerId)bound intoreport_dataplus a libp2p signature over the canonical payload blocks identity spoofing; the profile baked into the key-derivation path means the samepeerIdyields different keys per profile. -
Signed, verifiable supply chain. Keyless Sigstore signatures + Rekor transparency + SHA-256 pinning let an operator verify release policies and MRTD sets before trusting them, and re-verify continuously (the release-auditor workflow). See Policy Management and Configuration Reference for the knobs.
Rejections are typed: measurement_policy_rejected and tcb_status_rejected
return 403, challenge/signature/attestation failures return 401, a
replica without its root returns 503 (handlers/errors.rs). See
error handling for the full mapping.
Non-guarantees
Section titled “Non-guarantees”Being explicit about what Mero TEE does not promise:
- No defense against a broken TEE. Side-channel, physical, and microcode attacks against Intel TDX are out of scope.
- Client-side Plane 1 is advisory. The KMS makes an honest
/attestquote available, but whether a givenmerodclient verifies the KMS before requesting a key is a client decision, not something the KMS can force (Trust Model). - Pasted attestations are freshness-checked only against the nonce you give.
The public verifier can vouch that a quote was minted for this session only
when it chose the nonce (a node URL) or you pass the nonce you sent to
/attest(Verification). - Release provenance ≠ runtime state. Sigstore signatures prove an artifact is authentic; only a live attested quote proves what an instance is running. Full trust needs both.
- Trust anchors are assumed sound. Intel/ITA, Google’s TDX firmware, and
the
calimero-tee-attestationcrate are trusted, not verified, by this repo. - No backup of a cluster root. Losing every replica of a release’s cluster loses its root for good. That is a deliberate trade: a backup would add a reader. It costs node replacements (new cluster, new nodes that sync from peers), not data.
Reporting a vulnerability
Section titled “Reporting a vulnerability”Please do not open a public GitHub issue for a suspected vulnerability
(SECURITY.md).
- Contact:
info@calimero.network - Include: the affected component/path, reproduction steps or a proof-of-concept, the potential impact, and any proposed mitigations.
Receipt is acknowledged and triaged as quickly as possible.
Supported versions. Security fixes are generally applied to the latest
master branch and the latest released version stream.
No secrets in the repository. Private keys, GCP service-account keys, GitHub
tokens, credentials, and .env files must never be committed. Runtime secrets
flow through GitHub Actions secrets and GCP Secret Manager; release signing is
keyless (GitHub OIDC), so no signing private key is stored in the repo
(SECURITY.md).