# TEE reference

Source: https://nodedocs.mor.org/providers/full/tee-reference

This document covers two perspectives:

1. **Provider** — how to deploy a TEE-hardened P-Node on a Confidential VM.
2. **Consumer** — how to verify a provider's image is genuine and untampered.

For the shortest path to deploying on SecretVM, jump to [SecretVM quickstart](/providers/full/secretvm-quickstart). For a conceptual overview of the two-hop trust chain, see [TEE overview](/concepts/tee-overview).

## What is the TEE image?

The `-tee` Docker image (`ghcr.io/morpheusais/morpheus-lumerin-node-tee`) is a hardened version of the standard proxy-router image with all non-secret configuration **baked in at build time**:

- Blockchain config (contract addresses, chain ID, blockscout URL) is frozen and cannot be changed at runtime.
- Chat context storage is disabled (`PROXY_STORE_CHAT_CONTEXT=false`).
- Logging is set to production mode (JSON, no color, minimal verbosity).
- Environment is locked to `production`.

Two network variants are produced by CI/CD:

| Branch | Image tag | Network | Chain ID |
|--------|-----------|---------|----------|
| `main` | `...-tee:vX.Y.Z` / `...-tee:latest` | BASE Mainnet | 8453 |
| `test` | `...-tee:vX.Y.Z-test` / `...-tee:latest-test` | BASE Sepolia | 84532 |

Both share identical hardened settings; only the baked-in network's contracts and chain ID differ. The attestation manifest's `baked_env.network` field identifies which variant an image is.

Only **5 variables** are configurable at runtime — the per-provider secrets:

| Variable | Description |
|---|---|
| `WALLET_PRIVATE_KEY` | Provider's wallet private key |
| `ETH_NODE_ADDRESS` | RPC endpoint for BASE Mainnet (e.g., Alchemy, Infura) |
| `MODELS_CONFIG_CONTENT` | JSON model configuration (model IDs, backend URLs, slots) |
| `WEB_PUBLIC_URL` | Public-facing URL for the API (default: `http://localhost:8082`) |
| `COOKIE_CONTENT` | API auth credentials (default: `admin:admin`) |

Because configuration is frozen in the image, when run inside a TEE (Intel TDX or AMD SEV-SNP) the hardware can measure and attest that the software has not been modified.

---

## Part 1: Provider — Setting up a TEE P-Node

### Prerequisites

- Funded wallet (MOR + ETH) on the target network and access to its private key.
- An RPC endpoint for the target network (e.g. `wss://base-mainnet.g.alchemy.com/v2/<your_key>`).
- Your AI model backend reachable on a private endpoint.
- A `models-config.json` content (see [models-config schema](/reference/models-config)).
- Access to a Confidential VM provider supporting Intel TDX or AMD SEV-SNP (e.g., [SecretVM](https://secretai.scrtlabs.com/secret-vms/create)).

<Note>
**Mainnet vs testnet**: Use the mainnet TEE image (`:latest` or versioned tag without `-test`) for production. Use testnet (`:latest-test` / `*-test` tags) on Base Sepolia. Substitute the testnet image and a Sepolia RPC if testing.
</Note>

### Step 1: Prepare the Docker Compose file

Each CI/CD build produces a **deployed compose file** that pins the TEE image by immutable SHA-256 digest. RTMR3 is computed from the exact compose content.

Download `docker-compose.tee.deployed.yml` from the [GitHub Release](https://github.com/MorpheusAIs/Morpheus-Lumerin-Node/releases) for your target version, or use the template at [`proxy-router/docker-compose.tee.yml`](https://github.com/MorpheusAIs/Morpheus-Lumerin-Node/blob/main/proxy-router/docker-compose.tee.yml) and fill in the digest yourself.

The deployed compose looks like (digest differs per version):

```yaml
services:
  proxy-router:
    image: ghcr.io/morpheusais/morpheus-lumerin-node-tee@sha256:<digest>
    restart: unless-stopped
    ports:
      - 3333:3333
    volumes:
      - proxy_data:/app/data
    environment:
      - WALLET_PRIVATE_KEY=${WALLET_PRIVATE_KEY}
      - ETH_NODE_ADDRESS=${ETH_NODE_ADDRESS}
      - MODELS_CONFIG_CONTENT=${MODELS_CONFIG_CONTENT}
      - WEB_PUBLIC_URL=${WEB_PUBLIC_URL:-https://localhost}
      - COOKIE_CONTENT=${COOKIE_CONTENT:-admin:admin}
    env_file:
      - usr/.env
    networks:
      - traefik
    labels:
      - traefik.enable=true
      - traefik.http.routers.proxy-router.rule=PathPrefix(`/`)
      - traefik.http.routers.proxy-router.entrypoints=websecure
      - traefik.http.routers.proxy-router.tls=true
      - traefik.http.services.proxy-router.loadbalancer.server.port=8082
  traefik:
    image: traefik:v2.10
    command:
      - --api.insecure=false
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.websecure.address=:443
      - --entrypoints.websecure.http.tls.options=default@file
      - --providers.file.directory=/etc/traefik/dynamic
      - --providers.file.watch=true
    ports:
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /mnt/secure/cert:/certs:ro
    networks:
      - traefik
    configs:
      - source: tls_config
        target: /etc/traefik/dynamic/tls.yml
    env_file:
      - usr/.env
volumes:
  proxy_data: null
networks:
  traefik:
    driver: bridge
configs:
  tls_config:
    content: |-
      tls:
        certificates:
          - certFile: /certs/secret_vm_fullchain.pem
            keyFile: /certs/secret_vm_private.pem
        stores:
          default:
            defaultCertificate:
              certFile: /certs/secret_vm_fullchain.pem
              keyFile: /certs/secret_vm_private.pem
```

The **only line that changes between versions** is the proxy-router image digest. The Traefik sidecar, TLS config, and network setup stay constant.

<Warning>
**Why digest, not tag?** Tags are mutable. The digest (`@sha256:...`) is an immutable content hash. Using the digest in the compose file guarantees RTMR3 is cryptographically bound to one specific image binary.

**Exact byte content matters.** The compose file must end with exactly one newline after `proxy_data: null`. RTMR3 is computed from the exact byte content — a single extra or missing byte changes the hash. The SecretVM portal normalizes trailing whitespace down to one newline, so pasting as-is is safe.
</Warning>

Published images and digests: [GHCR packages](https://github.com/orgs/MorpheusAIs/packages?repo_name=Morpheus-Lumerin-Node).

### Step 2: Prepare your secrets

Set 5 values in your TEE platform's encrypted secrets section:

```
WALLET_PRIVATE_KEY=<your_private_key>
ETH_NODE_ADDRESS=wss://base-mainnet.g.alchemy.com/v2/<your_alchemy_key>
MODELS_CONFIG_CONTENT=<your_models_json_single_line>
WEB_PUBLIC_URL=https://your-public-domain.com:8082
COOKIE_CONTENT=admin:<your_secure_password>
```

`MODELS_CONFIG_CONTENT` must be a **single-line JSON string**:

```
MODELS_CONFIG_CONTENT={"models":[{"modelId":"0x626bcb19...","modelName":"hermes-4-14b","apiType":"openai","apiUrl":"http://your-model:8080/v1/chat/completions","concurrentSlots":6,"capacityPolicy":"simple"}]}
```

Schema: [models-config.json](/reference/models-config).

### Step 3: Deploy on SecretVM (or other TEE platform)

For SecretVM specifics (web portal vs CLI, advanced features, recommended platform = Intel TDX), see [SecretVM quickstart](/providers/full/secretvm-quickstart).

For other TEE platforms, consult their documentation for Docker Compose with encrypted secret injection. The image and compose are platform-agnostic — any environment supporting `linux/amd64` Docker inside a TEE works.

### Step 4: Verify the node is running

```bash
curl https://<your-node-url>/healthcheck
```

Expected:

```json
{ "status": "healthy", "version": "<current-version>", "uptime": "1m30s" }
```

Then proceed with [Register on chain](/providers/full/register-onchain) — prefer [MyProvider](/providers/full/myprovider-gui) on SecretVM; bid on an existing `tee` model when one exists, otherwise mint with the `tee` tag.

### Step 5: Verify attestation (SecretVM)

After deployment, SecretVM exposes attestation endpoints on `:29343`. Verify your own deployment:

1. Quick verification at https://secretai.scrtlabs.com/attestation — paste your compose and the VM URL.
2. Programmatic verification — see Part 2.

---

## Part 2: Consumer — Verifying a TEE provider image

As a consumer you can independently verify a provider before using it. Verification will eventually be built into the C-Node automatically for `tee`-tagged models, but you can do it manually today.

### Install cosign

```bash
# macOS
brew install cosign
# Linux (Debian/Ubuntu)
sudo apt install cosign
# Or: https://github.com/sigstore/cosign/releases
```

### Step 1: Verify the image signature

Confirms the image was built by the official MorpheusAIs CI/CD pipeline.

```bash
cosign verify \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'MorpheusAIs/Morpheus-Lumerin-Node' \
  ghcr.io/morpheusais/morpheus-lumerin-node-tee:<version>
```

Successful output mentions: cosign claims validated, transparency log verified, code-signing cert verified by trusted CA. The output JSON includes the exact GitHub commit, branch, and workflow that produced the image.

If verification fails, **do not trust the image.**

### Step 2: Inspect the TEE attestation manifest

```bash
cosign verify-attestation \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'MorpheusAIs/Morpheus-Lumerin-Node' \
  --type https://morpheusais.github.io/tee-attestation/v1 \
  ghcr.io/morpheusais/morpheus-lumerin-node-tee:<version> \
  2>/dev/null | jq -r '.payload' | base64 -d | jq '.predicate'
```

Key fields:

| Field | What to check |
|---|---|
| `tee_image` | Full image reference with `@sha256:` digest |
| `tee_image_digest` | Immutable `sha256:...` of this image |
| `compose_sha256` | Hash of the deployed compose (digest-pinned) |
| `compose_image_reference` | The exact `image@sha256:` reference inside the compose |
| `measurements.intel_tdx.rtmr3` | Expected RTMR3 — compare against live hardware quote |
| `measurements.intel_tdx.secretvm_release` | SecretVM release the RTMR3 was computed against |
| `build.commit` | Git commit that produced this image |
| `build.run_url` | Direct link to the GitHub Actions run |
| `baked_env.network` | `mainnet` or `testnet` |
| `baked_env.PROXY_STORE_CHAT_CONTEXT` | Should be `false` |
| `baked_env.ENVIRONMENT` | Should be `production` |
| `baked_env.ETH_NODE_CHAIN_ID` | `8453` or `84532` |
| `baked_env.DIAMOND_CONTRACT_ADDRESS` | Diamond proxy for the target network |
| `baked_env.MOR_TOKEN_ADDRESS` | MOR token for the target network |
| `runtime_secrets_only` | The 5 variables that can differ per provider |

### Step 3: View all supply-chain artifacts

```bash
cosign tree ghcr.io/morpheusais/morpheus-lumerin-node-tee:<version>
```

Shows signatures (`.sig`), attestations (`.att`), and SBOMs (`.sbom`).

### Step 4: Verify the running provider (SecretVM attestation)

If the provider is on SecretVM:

1. Go to https://secretai.scrtlabs.com/attestation
2. Paste the `docker-compose.tee.yml` (exact byte content — see warning above).
3. Enter the provider's VM URL or paste their attestation quote.
4. Click **Verify**.

Three layers must pass: hardware (genuine Intel TDX / AMD SEV-SNP), VM (firmware + kernel + initramfs match a known SecretVM release), software (RTMR3: rootfs + Docker Compose match what was deployed).

The expected RTMR3 is published in the signed manifest at `measurements.intel_tdx.rtmr3`. Compare it against the value reported by the hardware quote.

### Step 5: Verify RTMR3 independently (optional)

```bash
# Check the pinned version and rootfs variant
cat .github/tee/secretvm.env
# → SECRETVM_RELEASE=v0.0.25
# → SECRETVM_ROOTFS_VARIANT=rootfs-prod-tdx

# Download the same rootfs used by CI/CD
curl -L -o rootfs-prod-tdx.iso \
  "$(grep SECRETVM_ROOTFS_TDX_URL .github/tee/secretvm.env | cut -d= -f2-)"

# Compute RTMR3 from the deployed compose + rootfs
python3 proxy-router/scripts/compute-rtmr3.py docker-compose.tee.deployed.yml rootfs-prod-tdx.iso
```

Output should match `measurements.intel_tdx.rtmr3`. If not, check that you're using the same rootfs version as the CI/CD build.

---

## What this guarantees (and what it does not)

See the conceptual breakdown in [TEE overview](/concepts/tee-overview), which mirrors and summarizes the per-phase guarantees and remaining gaps documented here.

## Upgrading SecretVM artifacts

When SCRT Labs publishes a new SecretVM release (e.g. `v0.0.25 → v0.0.26`), the rootfs changes and all RTMR3 values must be recomputed. The CI/CD pipeline is fully variabilized so updating requires only editing `.github/tee/secretvm.env`.

### When to upgrade

SCRT Labs publishes at [github.com/scrtlabs/secret-vm-build/releases](https://github.com/scrtlabs/secret-vm-build/releases). No push notifications — check periodically:

```bash
curl -s https://api.github.com/repos/scrtlabs/secret-vm-build/releases \
  | jq '[.[] | {tag: .tag_name, prerelease: .prerelease, date: .published_at}] | .[0:5]'
```

<Note>
SCRT Labs sometimes marks new releases as **pre-release** on GitHub while already deploying them to the SecretVM portal. The `/releases/latest` API only returns non-prerelease versions, so always check the full list.
</Note>

### How to upgrade

<Steps>
  <Step title="Edit secretvm.env">
    ```bash
    SECRETVM_RELEASE=v0.0.26
    SECRETVM_ROOTFS_TDX_URL=https://github.com/scrtlabs/secret-vm-build/releases/download/v0.0.26/rootfs-prod-v0.0.26-tdx.iso
    SECRETVM_ROOTFS_TDX_SHA256=
    ```
    `SECRETVM_ROOTFS_VARIANT` stays `rootfs-prod-tdx` unless naming changes.
  </Step>
  <Step title="Push and run CI/CD">
    The pipeline downloads the new rootfs, computes its SHA-256, computes the new RTMR3, and embeds it in the signed manifest.
  </Step>
  <Step title="Pin the rootfs SHA-256">
    Copy the SHA-256 from the GitHub Actions step summary back into `secretvm.env`:
    ```bash
    SECRETVM_ROOTFS_TDX_SHA256=<sha256-from-step-summary>
    ```
  </Step>
  <Step title="Rebuild providers">
    Providers running older SecretVM versions will have mismatched RTMR3. They should redeploy on the current SecretVM release.
  </Step>
</Steps>

<Warning>
**Always use the `prod` rootfs variant.** SecretVM runs "environment prod" even for developer-portal deployments. Using `dev` produces a wrong RTMR3.
</Warning>

---

## Reference

| Resource | Link |
|---|---|
| TEE images on GHCR | https://github.com/orgs/MorpheusAIs/packages?repo_name=Morpheus-Lumerin-Node |
| RTMR3 computation script | [`proxy-router/scripts/compute-rtmr3.py`](https://github.com/MorpheusAIs/Morpheus-Lumerin-Node/blob/main/proxy-router/scripts/compute-rtmr3.py) |
| SecretVM artifact config | [`.github/tee/secretvm.env`](https://github.com/MorpheusAIs/Morpheus-Lumerin-Node/blob/main/.github/tee/secretvm.env) |
| Sigstore cosign | https://github.com/sigstore/cosign |
| SCRT Labs reproduce-mr | https://github.com/scrtlabs/reproduce-mr |
| SecretVM documentation | https://docs.scrt.network/secret-network-documentation/secretvm-confidential-virtual-machines |
| SecretVM CLI docs | https://docs.scrt.network/secret-network-documentation/secretvm-confidential-virtual-machines/secretvm-cli |
| SecretVM attestation portal | https://secretai.scrtlabs.com/attestation |
| Phase 2 backend developer reference | [`proxy-router/docs/tee-backend-verification.md`](https://github.com/MorpheusAIs/Morpheus-Lumerin-Node/blob/main/proxy-router/docs/tee-backend-verification.md) |
