Security Guide

MCP server trusted execution environment security — AWS Nitro Enclaves, SGX attestation, remote attestation for credential isolation, TEE side-channel limitations

Trusted Execution Environments provide hardware-enforced memory isolation for MCP server credential handling — the enclave's memory is encrypted at the hardware level, invisible to the host OS and hypervisor. But the security guarantee depends entirely on two things that are easy to get wrong: callers must verify the attestation document before sending secrets to the enclave, and the attested code measurement must be pinned to the specific known-good binary. Skip either step and the TEE provides security theater, not actual isolation.

What TEEs provide for MCP server secret handling

MCP servers that handle high-value credentials face a risk that conventional server hardening cannot fully address: a single remote code execution vulnerability in any tool handler gives the attacker access to every secret held in the server process's memory. API keys, OAuth tokens, private keys for code signing, and database credentials are all equally exposed once the attacker has arbitrary code execution in the server process. Defense-in-depth (least privilege, secret rotation, monitoring) reduces the blast radius but does not eliminate the fundamental problem — all secrets in a process are accessible to code running in that process.

A Trusted Execution Environment (TEE) creates a hardware-enforced isolated environment — the enclave — where code and data are encrypted at the hardware level using keys managed by the CPU itself. The property TEEs provide that software isolation cannot:

Memory encryption at rest in DRAM. Enclave memory pages are encrypted with keys held only in CPU registers. The host OS, hypervisor, and any process outside the enclave cannot read the plaintext contents of enclave memory, even by directly accessing the physical DRAM or DMA-remapping the memory. This is distinct from software-level memory isolation (which a privileged OS can bypass) — the encryption is enforced by the CPU's memory controller.

The three practical TEE platforms for MCP server deployments differ in their isolation model:

TEE Platform Isolation Model Attestation Mechanism Cloud Availability
AWS Nitro Enclaves Separate VM: no network, no persistent storage, vsock-only channel to parent EC2 instance Nitro Security Module generates signed attestation doc with PCR measurements AWS EC2 (most instance types)
Intel SGX In-process isolated memory region (enclave); host process continues running alongside DCAP quote signed by platform attestation key, verified against Intel PCS Azure DCsv2/DCsv3, some bare-metal providers
AMD SEV-SNP Full VM memory encryption; hypervisor cannot read guest memory SEV-SNP attestation report signed by AMD root key Azure CVM, GCP Confidential VMs

For MCP server credential isolation, Nitro Enclaves are the most operationally practical option on AWS because the vsock-only communication channel enforces the isolation boundary at the network level — the enclave literally cannot initiate outbound connections — making it structurally harder for a compromised enclave to exfiltrate secrets than an in-process SGX enclave where the host process continues running.

Remote attestation — proving the enclave is unmodified

The TEE security guarantee is only as strong as the attestation chain. A TEE provides strong memory isolation, but without attestation, nothing stops an attacker from spinning up their own process and claiming to be the trusted enclave. Remote attestation is the cryptographic mechanism that lets a verifier confirm three things simultaneously:

1. A specific code measurement (hash of the enclave binary) is running in the enclave. 2. The hardware producing the attestation is genuine TEE hardware, verified by the chip manufacturer's root of trust. 3. The attestation was produced during this specific boot — it is not a replay of a previous valid attestation.

For AWS Nitro Enclaves, the attestation flow works as follows:

# AWS Nitro Enclave attestation flow

# 1. Inside the enclave: request attestation document from Nitro Security Module
#    The NSM generates a CBOR-encoded document containing:
#    - PCR0: SHA-384 hash of the enclave image (EIF file)
#    - PCR1: SHA-384 hash of the Linux kernel and bootstrap used
#    - PCR2: SHA-384 hash of the application
#    - PCR8: Hash of the signing certificate (if the enclave is signed)
#    - user_data: arbitrary bytes provided by the enclave (e.g., a nonce)
#    - nonce: replay-prevention nonce from the verifier
#    - public_key: ephemeral public key for encrypted secret delivery

import aws_nitro_enclaves_nsm_api as nsm

# Inside the enclave process
def get_attestation_document(nonce: bytes, public_key: bytes) -> bytes:
    nsm_fd = nsm.lib.nsm_lib_init()
    response = nsm.lib.nsm_get_attestation_doc(
        nsm_fd,
        user_data=None,
        nonce=nonce,        # Provided by verifier to prevent replay
        public_key=public_key  # Enclave's ephemeral key for key exchange
    )
    nsm.lib.nsm_lib_exit(nsm_fd)
    return response.attestation_doc  # CBOR-encoded, signed by NSM

The verifier (the entity about to send secrets to the enclave) must validate the attestation document before trusting the enclave:

import boto3
import cbor2
from cryptography.hazmat.primitives import hashes
from cryptography.x509 import load_der_x509_certificate

def verify_nitro_attestation(attestation_doc: bytes, expected_pcr0: str, nonce: bytes) -> dict:
    """
    Verify a Nitro Enclave attestation document.
    Returns the parsed PCR values if valid, raises if not.
    """
    # 1. Parse the COSE_Sign1 structure
    cose_sign1 = cbor2.loads(attestation_doc)
    protected_header, unprotected_header, payload, signature = cose_sign1.value

    # 2. Parse the payload
    doc = cbor2.loads(payload)

    # 3. Verify the certificate chain against AWS Nitro root CA
    cert_chain = [load_der_x509_certificate(c) for c in doc['cabundle']]
    # ... verify chain against pinned AWS Nitro root CA public key ...

    # 4. Verify the COSE signature using the end-entity certificate's public key
    # ... verify signature over (protected_header || payload) ...

    # 5. CRITICAL: Check the nonce to prevent replay attacks
    if doc.get('nonce') != nonce:
        raise ValueError("Attestation nonce mismatch — possible replay attack")

    # 6. CRITICAL: Pin the PCR0 value to the expected enclave image hash
    pcr0 = doc['pcrs'][0].hex()
    if pcr0 != expected_pcr0:
        raise ValueError(
            f"PCR0 mismatch: got {pcr0}, expected {expected_pcr0}. "
            "Enclave image may have been tampered with or replaced."
        )

    return doc['pcrs']

The most common TEE security failure: callers verify that the attestation document is cryptographically valid (the signature chain is correct, the document is genuine Nitro hardware output) but do NOT verify that the PCR0 value matches the known-good enclave binary hash. A valid attestation from an attacker-substituted enclave binary passes signature verification — only PCR pinning catches it.

What TEEs do NOT protect against

The memory encryption boundary of a TEE is real and meaningful, but TEEs are not a universal security silver bullet. Several important attack classes survive the TEE boundary entirely.

Spectre and Meltdown class attacks. Speculative execution vulnerabilities allow code running outside the enclave — on the same physical CPU core — to observe the speculative side effects of enclave execution through shared microarchitectural state (cache lines, branch predictor tables, translation lookaside buffers). The Spectre v1 (bounds check bypass) and v2 (branch target injection) variants are particularly relevant because they can be triggered by code in the host process running on the same core as the enclave, with the enclave's speculative memory accesses leaving timing-observable traces in the shared L1 cache.

Cache timing attacks (Prime+Probe, Flush+Reload). An attacker process co-located on the same physical CPU can observe which cache sets are evicted by enclave execution. In a Prime+Probe attack: the attacker primes a cache set by filling it with attacker-controlled data, allows the enclave to execute (evicting some lines based on the enclave's memory access pattern), then probes the cache set to measure which lines were evicted. The eviction pattern leaks information about the enclave's memory access pattern — and cryptographic algorithms with data-dependent memory access patterns (lookup tables in AES, conditional branches in RSA) can have their secrets extracted this way.

Branch predictor side channels. The CPU's branch predictor is shared between the enclave and co-located processes. An attacker can observe the misprediction penalty of branches inside the enclave — branches that depend on secret data produce a misprediction pattern that leaks information about which branch was taken, without requiring memory access observation.

Cold boot attacks. If the enclave is not properly destroyed before system shutdown (or an attacker can induce a panic/reset while the enclave is running), DRAM memory remanence means that the physical DRAM contents persist for seconds to minutes at room temperature (longer if cooled). An attacker with physical access to the machine during this window can retrieve the plaintext contents of enclave memory pages that have been decrypted in CPU registers and written back to DRAM. Nitro Enclaves mitigate this by ensuring enclaves have no persistent storage and are destroyed on any connectivity loss to the parent instance — but physical access to the underlying EC2 host is an AWS-level concern, not a customer-level one.

TEE threat model scoping: TEEs are designed to protect against a compromised host OS and hypervisor reading enclave memory. They are not designed to protect against co-resident attackers exploiting microarchitectural side channels, or against hardware-level physical attacks. Know which threat you are defending against before relying on a TEE as your primary control.

AWS Nitro Enclave for MCP credential isolation

Nitro Enclaves are the most practical TEE deployment for MCP servers running on AWS. The enclave's architecture — no network interfaces, no persistent storage, vsock-only communication channel — reduces the attack surface significantly compared to in-process TEEs. The credential isolation pattern for an MCP tool server:

# Architecture: MCP server (parent EC2) + credential enclave (Nitro Enclave)
#
# Parent EC2 instance:
#   - Runs the MCP server process that handles tool invocations
#   - Has no access to plaintext credentials
#   - Communicates with enclave via vsock
#
# Nitro Enclave:
#   - Runs the credential decryption and tool execution logic
#   - Can call AWS KMS via the parent's vsock proxy (no direct network)
#   - Returns only tool outputs, never raw credentials

# In the parent EC2 MCP server:
import socket

ENCLAVE_CID = 16   # Nitro Enclave CID (configured at launch)
ENCLAVE_PORT = 5000

def call_enclave_tool(tool_name: str, params: dict, tenant_id: str) -> dict:
    """Send a tool invocation to the credential enclave via vsock."""
    sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
    try:
        sock.connect((ENCLAVE_CID, ENCLAVE_PORT))
        request = {
            'tool': tool_name,
            'params': params,
            'tenant_id': tenant_id,
        }
        sock.sendall(json.dumps(request).encode() + b'\n')
        response_bytes = b''
        while True:
            chunk = sock.recv(4096)
            if not chunk:
                break
            response_bytes += chunk
            if b'\n' in response_bytes:
                break
        return json.loads(response_bytes.strip())
    finally:
        sock.close()  # Always close — no resource leak on error
# Inside the Nitro Enclave:
# 1. Encrypted credential blob arrives via vsock from parent
# 2. Enclave calls KMS to decrypt, using its attestation as authorization context
# 3. KMS policy verifies PCR0 matches the known-good enclave image
# 4. Decrypted credential used for the tool call — never sent outside the enclave
# 5. Tool output (not the credential) returned via vsock

import boto3
import aws_nitro_enclaves_nsm_api as nsm

def decrypt_credential_with_kms(encrypted_credential: bytes) -> bytes:
    """
    Decrypt a credential blob using KMS with Nitro attestation as context.
    KMS key policy: kms:RecipientAttestation:PCR0 = sha384:
    """
    # Get attestation document from NSM
    nsm_fd = nsm.lib.nsm_lib_init()
    att_doc = nsm.lib.nsm_get_attestation_doc(nsm_fd, nonce=None, user_data=None, public_key=None)
    nsm.lib.nsm_lib_exit(nsm_fd)

    # KMS decrypt — KMS verifies the attestation document server-side
    # and checks PCR0 against the key policy condition
    kms_client = boto3.client('kms', region_name='us-east-1')
    response = kms_client.decrypt(
        CiphertextBlob=encrypted_credential,
        # Pass attestation document to KMS for policy evaluation
        Recipient={
            'KeyEncryptionAlgorithm': 'RSAES_OAEP_SHA_256',
            'AttestationDocument': att_doc.attestation_doc,
        }
    )
    # KMS returns the plaintext encrypted under the enclave's ephemeral key
    # (not in plaintext in the response) — decrypt with enclave's private key
    return decrypt_with_enclave_private_key(response['CiphertextForRecipient'])

KMS policy condition for PCR pinning: The KMS key policy condition kms:RecipientAttestation:PCR0 lets you pin decryption access to a specific Nitro Enclave image measurement. When the enclave image is updated, the PCR0 hash changes, KMS decryption fails, and the deployment must explicitly authorize the new measurement before the updated enclave can access credentials. This makes unauthorized enclave substitution a hard failure rather than a silent security bypass.

Sealing secrets to specific code measurements

Secrets provisioned to a TEE should be sealed to the specific code measurement of the expected enclave binary. "Sealing" means the credential is encrypted in such a way that only an enclave with a specific, known code measurement can decrypt it. If an attacker can substitute a different enclave binary — either by deploying a modified version or by exploiting the build pipeline — they should not be able to decrypt the sealed secret.

For AWS Nitro Enclaves, this sealing is achieved via KMS key policy conditions:

# KMS key policy: allow decrypt only from the specific Nitro Enclave image
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowNitroEnclaveDecrypt",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::123456789012:role/mcp-server-role" },
      "Action": "kms:Decrypt",
      "Resource": "*",
      "Condition": {
        "StringEqualsIgnoreCase": {
          # PCR0 = SHA-384 of the enclave image file (EIF)
          # This value must be updated when the enclave binary changes
          "kms:RecipientAttestation:PCR0":
            "b4e6b8b2f1c9a7d3e5f0a2c4d6b8e0f2a4c6d8e0f2a4c6d8e0f2a4c6d8e0f2"
        }
      }
    }
  ]
}

For Intel SGX, the equivalent mechanism is EGETKEY — the SGX instruction that derives a key from the enclave's identity measurements. The MRENCLAVE key policy binds the derived key to the exact binary hash of the enclave, so only the specific enclave version that sealed the data can unseal it. An enclave update requires a controlled re-sealing flow:

# SGX enclave upgrade re-sealing flow (pseudocode)
#
# Step 1: Launch old enclave version
# Step 2: Old enclave unseals the secret using its MRENCLAVE-bound key
# Step 3: Old enclave re-encrypts the secret under the new enclave's public key
#         (the new enclave's public key is extracted from its attestation quote)
# Step 4: Verify the new enclave's attestation quote before authorizing re-seal
# Step 5: Destroy old enclave; launch new enclave with re-sealed secret

def reseal_for_new_enclave(old_enclave_connection, new_enclave_attestation_quote):
    # Verify the new enclave's code measurement matches the expected new version
    new_pcr = verify_sgx_quote(new_enclave_attestation_quote, expected_mrenclave=NEW_ENCLAVE_HASH)

    # Request the old enclave to re-seal the secret for the new enclave
    # Only the old enclave (with access to the unsealed secret) performs this operation
    response = old_enclave_connection.reseal_secret({
        'target_public_key': new_enclave_attestation_quote['report_data']['public_key'],
        'authorized_mrenclave': new_pcr['mrenclave'],
    })
    return response['resealed_secret_ciphertext']

SkillAudit findings for TEE deployments

These findings represent the TEE configuration failures SkillAudit identifies most frequently in MCP server codebases and deployment configurations that use or claim to use TEE-based isolation.

CRITICAL -24 pts — No attestation verification by callers: secrets sent to the claimed TEE endpoint without the caller verifying the attestation document. Any process that opens the vsock port and responds to the credential request protocol receives the secrets — the TEE's isolation boundary is irrelevant if callers do not verify they are talking to the enclave.
CRITICAL -22 pts — Attestation PCR values not pinned: the attestation document is verified (signature chain is correct, hardware is genuine) but any valid Nitro or SGX enclave is accepted — the specific expected image measurement is not checked. An attacker can substitute their own enclave binary, get a valid attestation from genuine hardware, and receive the credentials intended for the legitimate enclave.
HIGH -20 pts — Enclave binary not pinned in deployment pipeline: CI/CD updates the enclave binary without re-validating the new PCR measurement against a known-good baseline. An attacker who can modify the enclave binary in the build pipeline changes the PCR values without triggering an alert, silently accepting a backdoored enclave or causing credential access to break without explanation.
HIGH -18 pts — vsock channel lacks additional application-layer authentication: the parent EC2 instance accepts connections on the vsock port from any process that can access the socket. On a compromised parent instance, a non-enclave process can impersonate the enclave at the vsock level and receive any data sent by the MCP server before attestation is requested.
MEDIUM -12 pts — No enclave restart on detected security event: when the enclave detects an anomaly (unexpected message format, protocol violation, repeated attestation requests without matching tool calls), the correct response is to tear down and re-initialize the enclave, which purges all in-memory secrets. Logging the event and continuing leaves the enclave potentially running with compromised state.
MEDIUM -10 pts — Side-channel mitigations not applied in enclave cryptographic code: credential comparison operations inside SGX enclaves use non-constant-time string comparison, making them vulnerable to cache timing side channels. All secret comparisons and cryptographic operations inside a TEE must use constant-time implementations to prevent information leakage via shared microarchitectural state.

Audit your MCP server for these issues

SkillAudit checks these security misconfigurations automatically — paste a GitHub URL and get a graded report in 60 seconds.

Run a free audit →