Skip to main content
Verify that your CVM runs unmodified code on genuine TEE hardware, from fetching the attestation quote to integrating verification into your CI/CD pipeline. This guide ties together the attestation workflow end-to-end with working code examples. If you’re new to attestation, start with the Quickstart to understand the basics through the dashboard. This guide assumes you want programmatic verification.

The verification flow

Every CVM attestation verification follows three steps: get a quote from your running CVM, verify the quote’s hardware signature, and check that the measurements match your expected application. The entire process runs in seconds from anywhere outside the CVM.

Step 1: Get the attestation quote

Your CVM must expose attestation endpoints. If you haven’t set this up yet, see Get Attestation for the dstack SDK setup. Fetch the quote with a fresh challenge nonce to prevent replay attacks.
import crypto from 'crypto';

const CVM_URL = 'https://your-app.example.com';

// Generate a fresh 32-byte nonce
const nonce = crypto.randomBytes(32).toString('hex');

// Fetch attestation with nonce as reportData
const attestResponse = await fetch(`${CVM_URL}/attestation?reportData=${nonce}`);
const attestData = await attestResponse.json();

// Fetch application info for compose verification
const infoResponse = await fetch(`${CVM_URL}/info`);
const appInfo = await infoResponse.json();

console.log('Quote length:', attestData.quote.length);
console.log('Event log events:', JSON.parse(attestData.event_log).length);
Always generate a fresh nonce per verification request. Without it, an attacker could replay an old valid quote from compromised hardware.

Step 2: Verify the hardware signature

The TDX quote is signed by Intel hardware. Verify this signature to confirm the quote came from a genuine TEE. You have two options: use Phala’s verification API for convenience, or verify locally for trustless verification.

Option A: Phala Cloud verification API

The fastest approach. Sends your quote to Phala’s service which checks it against Intel’s root certificates.
const verifyResponse = await fetch(
  'https://cloud-api.phala.com/api/v1/attestations/verify',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ hex: attestData.quote }),
  }
);

const result = await verifyResponse.json();

if (!result.quote.verified) {
  throw new Error('Hardware verification failed');
}

console.log('Hardware signature: valid');
console.log('TDX version:', result.quote.body.tee_tcb_svn);

Option B: Local verification with dcap-qvl

For trustless verification without relying on Phala’s API, use the open-source dcap-qvl verifier. This checks Intel’s signature chain locally.
# Install dcap-qvl
cargo install dcap-qvl

# Verify a quote locally
dcap-qvl verify --quote-hex "YOUR_QUOTE_HEX"
You can also paste your quote into the TEE Attestation Explorer for an interactive visual breakdown of all quote fields.

Step 3: Verify your application code

Hardware verification proves the TEE is genuine. Now verify that your specific application is running inside it. The compose-hash in RTMR3 is a SHA256 hash of your entire Docker Compose configuration.
import { createHash } from 'crypto';

// Calculate the compose hash from the app info
const appCompose = appInfo.tcb_info.app_compose;
const calculatedHash = createHash('sha256')
  .update(appCompose)
  .digest('hex');

// Extract the attested hash from the RTMR3 event log
const events = JSON.parse(attestData.event_log);
const composeEvent = events.find(
  (e: { event: string }) => e.event === 'compose-hash'
);
const attestedHash = composeEvent.event_payload;

if (calculatedHash !== attestedHash) {
  throw new Error(
    `Compose hash mismatch: expected ${calculatedHash}, got ${attestedHash}`
  );
}

console.log('Application code: verified');
console.log('Compose hash:', calculatedHash);

Step 4: Verify the nonce binding

Confirm that the quote contains your original nonce. This proves the attestation was freshly generated for your request.
// The reportData field in the quote should start with your nonce
const reportData = attestData.report_data;
if (!reportData.startsWith(nonce)) {
  throw new Error('Nonce mismatch: possible replay attack');
}
console.log('Freshness: verified');

Complete verification script

Here’s everything combined into a single script you can drop into your project.
import crypto from 'crypto';
import { createHash } from 'crypto';

async function verifyCVM(cvmUrl: string): Promise<boolean> {
  // 1. Generate fresh nonce
  const nonce = crypto.randomBytes(32).toString('hex');

  // 2. Fetch attestation and app info
  const [attestRes, infoRes] = await Promise.all([
    fetch(`${cvmUrl}/attestation?reportData=${nonce}`),
    fetch(`${cvmUrl}/info`),
  ]);
  const attestData = await attestRes.json();
  const appInfo = await infoRes.json();

  // 3. Verify hardware signature
  const verifyRes = await fetch(
    'https://cloud-api.phala.com/api/v1/attestations/verify',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ hex: attestData.quote }),
    }
  );
  const verifyResult = await verifyRes.json();
  if (!verifyResult.quote.verified) {
    console.error('Hardware verification failed');
    return false;
  }

  // 4. Verify compose hash
  const appCompose = appInfo.tcb_info.app_compose;
  const calculatedHash = createHash('sha256').update(appCompose).digest('hex');
  const events = JSON.parse(attestData.event_log);
  const composeEvent = events.find(
    (e: { event: string }) => e.event === 'compose-hash'
  );
  if (calculatedHash !== composeEvent.event_payload) {
    console.error('Compose hash mismatch');
    return false;
  }

  // 5. Verify nonce freshness
  if (!attestData.report_data.startsWith(nonce)) {
    console.error('Nonce mismatch');
    return false;
  }

  console.log('All checks passed');
  return true;
}

// Usage
verifyCVM('https://your-app.example.com').then(console.log);

Integrate verification into CI/CD

Attestation verification fits naturally into deployment pipelines. Run it after every deployment to confirm your CVM booted correctly with the expected code.

GitHub Actions example

name: Verify CVM Attestation
on:
  workflow_dispatch:
  schedule:
    - cron: '0 */6 * * *'  # Every 6 hours

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: pip install requests

      - name: Verify attestation
        env:
          CVM_URL: ${{ secrets.CVM_URL }}
        run: python scripts/verify_attestation.py "$CVM_URL"

      - name: Alert on failure
        if: failure()
        run: |
          curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -d '{"text": "CVM attestation verification failed!"}'

Continuous monitoring

For production systems, run verification checks on a schedule. Here’s a lightweight monitoring approach:
import time
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("attestation-monitor")

CVM_URLS = [
    "https://app1.example.com",
    "https://app2.example.com",
]

CHECK_INTERVAL = 3600  # 1 hour

while True:
    for url in CVM_URLS:
        try:
            result = verify_cvm(url)
            if result:
                logger.info(f"PASS: {url}")
            else:
                logger.error(f"FAIL: {url}")
                # Send alert to your monitoring system
        except Exception as e:
            logger.error(f"ERROR: {url} - {e}")

    time.sleep(CHECK_INTERVAL)
Attestation verification must always run outside the CVM. Verification running inside the CVM provides no security guarantees because a compromised CVM could fake its own results.

What each check proves

Here’s a quick reference for what you’re verifying at each step and what attack it prevents.
CheckWhat it provesAttack prevented
Hardware signatureQuote came from genuine Intel TDXFake TEE simulation
Compose hashYour exact Docker images are runningCode substitution
Nonce freshnessQuote was generated for this requestReplay of old quotes
reportData bindingCustom data is attested by hardwareData tampering
For advanced verification including RTMR3 event log replay, Docker digest pinning, on-chain governance, and source code provenance, see the Complete Chain of Trust.

Next steps

Verify Your Application

Advanced verification including RTMR3 replay and image digest checks

Verify the Platform

Verify OS, KMS, and infrastructure without trust assumptions

Field Reference

Understand every field in the attestation quote

GPU TEE Attestation

Verify GPU-specific attestation for Confidential AI workloads