Cameleer

Attestation in confidential VMs

When Cameleer runs inside a hardware TEE (an Intel TDX or AMD SEV-SNP confidential VM, or a dstack CVM such as Phala Cloud or dstack on Google Cloud TDX), it can prove to a client what is running: a signed hardware report, bound to the client's nonce, to the server's TLS key and to a manifest of the exact routes, configuration and tools. Clients get it over HTTP at /q/attestation and over MCP, in the handshake or as a tool.

Cargo feature: attest (in the default build) · Apache Camel: none (Cameleer extension) · Status: on main, in the next release (not in 0.5.1)

This is confidential computing applied to the integration engine itself: the evidence covers what Cameleer runs (its routes, configuration and MCP tools), not only the virtual machine it runs in.

Turn it on

In a confidential VM:

properties
camel.attestation.enabled=true

At start-up Cameleer looks for an evidence source and makes one report. If none works, start-up fails: a process that asked to be attested must not look attested when it isn't. The sources:

sourcewhereplatforms
Linux configfs-tsm/sys/kernel/config/tsm/report (Linux 6.7 and later)Intel TDX, AMD SEV-SNP
dstack guest agent/var/run/dstack.sockdstack CVMs (Intel TDX): Phala Cloud, dstack on Google Cloud

Then ask for a report with a nonce of 16 to 64 random bytes, in base64 or base64url:

bash
curl "https://orders.example.com/q/attestation?nonce=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')"

What a report proves, and how to check it

A report holds:

  • evidence: the TEE's signed report (a TDX quote or an SEV-SNP report), base64;
  • binding: what the 64 bytes the hardware signed (REPORTDATA) were made from;
  • manifest: Cameleer's version and features, every route file with its SHA-256, every route with the digest of its definition, the configuration (secret values replaced by <secret>), and the published tools with their fingerprints;
  • log: a hash-chained record that starts with the manifest and adds an entry for every change after start-up (routes added or removed, a tool disabled with the kill switch).

REPORTDATA is:

text
SHA-512( "cameleer-attest-v1" || 0x00
       || manifest digest (the log's head, SHA-384, 48 bytes)
       || SHA-256 of the server's TLS public key (32 bytes, zeros without TLS)
       || nonce length (2 bytes, big-endian) || nonce )

A client checks the report in two steps:

  1. The binding. Recompute REPORTDATA from the manifest digest in the report, the TLS key of the certificate it saw on its own connection, and the nonce it sent, and check that the log chain ends at that digest. A Rust program can call cameleer_attest::check_binding. A match means the evidence was made for this connection, now, by a process running this manifest. A terminating proxy in between changes the TLS key, and the check fails.
  2. The hardware evidence. Verify the quote's signature chain against Intel's or AMD's roots and the platform measurements, with a verifier for the platform: Intel Trust Authority, Microsoft Azure Attestation, Google's verifier, or dstack's verifier for dstack CVMs. Cameleer does not do this step for you yet.

The hardware vouches only that the measured code chose those 64 bytes. So the manifest means "this exact Cameleer release" when the platform measurement (the container image digest, or the dstack compose hash in RTMR3) shows a build you trust: pin the image by digest.

Over MCP

With the MCP server on, the handshake carries the attestation summary (platform, manifest digest, TLS key hash, endpoint) in _meta under ing.cameleer/attestation, both in server/discover (2026-07-28) and in initialize (earlier versions). A verifying client that sends a nonce gets the full report back in the same round trip:

json
{"jsonrpc": "2.0", "id": 1, "method": "server/discover",
 "params": {"_meta": {"ing.cameleer/attestation": {"nonce": "bm9uY2UtMDEyMzQ1Njc4OWFiY2RlZg"}}}}

The report is then in result._meta["ing.cameleer/attestation"].report.

For clients that only use tools, camel.attestation.mcp-tool=true adds the tool cameleer_attestation(nonce). Its description says plainly that a model showing evidence is not verifying it: only a program that checks the binding and the signature chain gets the guarantee.

Configuration

propertydefaultdescription
camel.attestation.enabledfalseProduce evidence; start-up fails when no source works
camel.attestation.providerautoauto (dstack first, then configfs-tsm), tsm or dstack
camel.attestation.tsm-path/sys/kernel/config/tsm/reportThe configfs-tsm report directory
camel.attestation.dstack-endpoint/var/run/dstack.sockThe dstack agent: a unix socket, or http://host:port for its simulator
camel.attestation.path/q/attestationThe HTTP path of reports, on camel.server.port
camel.attestation.mcp-toolfalsetrue for the tool cameleer_attestation, or a tool name
camel.attestation.exe-digesttruePut the SHA-384 of the running executable in the manifest (self-reported; the platform measurement is what counts)

The TLS key comes from the embedded server's certificate (camel.server.ssl.*); with camel.server.sslEnabled=true, /q/attestation is served over HTTPS on the same port as the HTTPS routes. Generate the key inside the TEE and never write it out, or the binding proves less.

Limits

  • No verification of other servers yet. Checking quotes against Intel and AMD roots, and a gateway policy that refuses tools from servers that don't attest, are the next phase.
  • No sealed secrets yet. Releasing secrets only after attestation ({{sealed:...}} with keys from dstack, AWS KMS, Azure Secure Key Release or Google Cloud) is planned.
  • AWS Nitro Enclaves, Azure vTPM attestation and Google Confidential Space are not sources yet.
  • Physical attacks. TEEs trust the cloud's physical security as well as the CPU vendor: memory bus interposers (TEE.fail, 2025) have extracted keys from TDX and SEV-SNP machines.
  • Not yet run on real TEE hardware. The tests use a simulated configfs-tsm directory and a simulated dstack agent; the report formats follow the kernel ABI and dstack's API.
  • No reproducible builds yet. Pin the image by digest (pmsudhi/cameleer@sha256:...) so the platform measurement identifies the build you reviewed.
  • Runtime changes are recorded, not prevented. The log shows a route added or a tool disabled after start-up; a policy that refuses them is a verifier's decision.

Common questions

Can an MCP client check what a Cameleer server is running?

Yes, inside a confidential VM: the client sends a nonce in server/discover or initialize and gets a hardware report bound to that nonce, to the server's TLS key and to a manifest of the routes, configuration and tools. The client checks the binding (cameleer_attest::check_binding in Rust) and verifies the hardware signature with the platform's verifier.

Does Cameleer verify the hardware evidence itself?

Not yet. It produces the evidence and the binding; verifying Intel's or AMD's signature chain is done by a verifier such as Intel Trust Authority, Microsoft Azure Attestation, Google's verifier or dstack's. Verification inside Cameleer, for remote MCP servers, is the next phase.

Is it enough for a model to call the cameleer_attestation tool?

No. A model that shows evidence is not checking it. Only a program that checks the binding and the signature chain gets the guarantee; the tool exists so that clients which only speak tools can fetch the report for such a program.

Which clouds work?

Any Intel TDX or AMD SEV-SNP confidential VM whose Linux kernel (6.7 or later) exposes configfs-tsm, and dstack CVMs such as Phala Cloud or dstack on Google Cloud TDX. AWS Nitro Enclaves, Azure vTPM attestation and Google Confidential Space are not supported yet.

What happens if attestation is on but the machine is not a TEE?

Start-up fails. A process that was asked to be attested never runs looking attested when it isn't.

Does it protect against someone with physical access to the server?

No. TEEs trust the cloud's physical security as well as the CPU vendor; memory-bus interposer attacks (TEE.fail, 2025) have extracted keys from TDX and SEV-SNP machines.

Found a problem?

If something on this page doesn't match what Cameleer does, please open an issue, or fork the repository, fix it, add a test, run cargo test --workspace and open a pull request.

Edit this page on GitHub