Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ Agent -> cMCP Runtime -> Cedar Policy Engine (TEE) -> Tool
| `sev-snp` | AMD SEV-SNP (Azure DCasv5, AWS C6a Nitro) | High | AMD KDS |
| `tdx` | Intel TDX (Azure DCedsv5, GCP C3) | High | Intel PCS |
| `gpu-cc` _(v0.2)_ | NVIDIA H100/H200/Blackwell (CC mode) | High | NVIDIA Remote Attestation Service (NRAS) |
| `opaque` _(opt-in)_ | OPAQUE Confidential Runtime | n/a _(not yet implemented)_ | Placeholder: `detect()` returns `False`, so it is never auto-selected until implemented |
| `opaque` _(opt-in)_ | OPAQUE Confidential Runtime | n/a _(not yet implemented)_ | Placeholder: excluded from auto-detect; selecting it explicitly raises a not-implemented error |

Provider auto-detect probe order: `tpm -> sev-snp -> tdx -> opaque` — the first provider whose `detect()` succeeds is selected. `opaque` is a not-yet-implemented placeholder and is never auto-selected. If no hardware provider is detected, the gateway starts only under `CMCP_DEV_MODE=1` (a non-attested software-only fallback) and otherwise refuses to start.
Provider auto-detect probe order: `tpm -> sev-snp -> tdx` — the first provider whose `detect()` succeeds is selected. `opaque` is a not-yet-implemented placeholder: it is excluded from auto-detect, and selecting it explicitly raises `ATTESTATION_PROVIDER_NOT_IMPLEMENTED` rather than falling through silently. If no hardware provider is detected, the gateway starts only under `CMCP_DEV_MODE=1` (a non-attested software-only fallback) and otherwise refuses to start.

```python
from cmcp_runtime.config import TEEProvider

# Auto-detect (default)
# attestation.provider: auto -> tpm -> sev-snp -> tdx -> opaque
# attestation.provider: auto -> tpm -> sev-snp -> tdx
# (software-only is used only under CMCP_DEV_MODE=1)

# Explicit hardware selection
Expand Down
4 changes: 2 additions & 2 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ picture is stated once. Developer Preview: interfaces may change before v1.0.

| Setting | Default |
|---|---|
| `attestation.provider` | `auto` (probe order `tpm -> sev-snp -> tdx -> opaque`) |
| `attestation.provider` | `auto` (probe order `tpm -> sev-snp -> tdx`) |
| `attestation.enforcement_mode` | `enforcing` |
| `attestation.staleness_policy` | `fail_closed` |
| `attestation.validity_seconds` | `86400` |
Expand All @@ -25,7 +25,7 @@ picture is stated once. Developer Preview: interfaces may change before v1.0.
| Offline verification (`cmcp_verify`) | Shipped | No operator trust required when the verifier independently checks the attestation report. |
| Agent Manifest identity binding | Shipped | Optional; trust in the issuer key is an out-of-band PKI concern. |
| Attestation verifiers: `tpm`, `sev-snp`, `tdx` | Partial | Report parsing + certificate-chain verification against real vendor roots; report-signature paths validated with synthetic vectors. End-to-end validation against a real hardware quote on a confidential VM is pending — do not describe as fully hardware-attested until then. |
| `opaque` provider | Not implemented | Opt-in placeholder; `detect()` returns `False`, so it is never auto-selected until implemented. |
| `opaque` provider | Not implemented | Opt-in placeholder; excluded from auto-detect. Selecting it explicitly raises `ATTESTATION_PROVIDER_NOT_IMPLEMENTED` rather than falling through silently. |
| `gpu-cc` (NVIDIA H100/H200/Blackwell, via NRAS) | Planned (v0.2) | |
| Transparency-log anchoring for TRACE Claims | v0.2 | Write and lookup. |
| Server-side (provider) attestation | Not yet (Phase 2) | Phase 1 attests the gateway boundary only. |
Expand Down
11 changes: 6 additions & 5 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,12 @@ Across the four problems and 13 shapes, Phase 1 covers 11 outright and partially
| tdx | Intel TDX (Azure DCedsv5, GCP C3) | High |
| opaque | OPAQUE Managed Runtime (opt-in; not yet implemented) | n/a |

Auto-detection probe order: `tpm -> sev-snp -> tdx -> opaque`. The first provider whose
`detect()` succeeds is selected. `opaque` is a placeholder whose `detect()` currently returns
`False`, so it is never auto-selected until it is implemented. If no hardware provider is
detected, the gateway starts only under `CMCP_DEV_MODE=1` (a non-attested software-only
fallback) and otherwise refuses to start. Default `enforcement_mode` is `enforcing`.
Auto-detection probe order: `tpm -> sev-snp -> tdx`. The first provider whose `detect()`
succeeds is selected. `opaque` is a not-yet-implemented placeholder: it is excluded from
auto-detect, and selecting it explicitly raises `ATTESTATION_PROVIDER_NOT_IMPLEMENTED` rather
than falling through silently. If no hardware provider is detected, the gateway starts only
under `CMCP_DEV_MODE=1` (a non-attested software-only fallback) and otherwise refuses to
start. Default `enforcement_mode` is `enforcing`.

---

Expand Down
11 changes: 9 additions & 2 deletions docs/spec/attestation.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ The cMCP Runtime produces TRACE Claims: signed, hardware-attested proof artifact
At runtime startup, the process probes for TEE providers in the following fixed order. The first provider whose conditions are satisfied is selected. Only one provider is active per runtime instance.

```
probe_order = ["tpm", "sev-snp", "tdx", "opaque"]
probe_order = ["tpm", "sev-snp", "tdx"]
```

The `opaque` (OPAQUE managed-runtime) provider is a recognized but not-yet-implemented placeholder. It is intentionally excluded from `probe_order`, so it is never auto-selected. Selecting it explicitly (`attestation.provider: opaque`) raises `ATTESTATION_PROVIDER_NOT_IMPLEMENTED` rather than reporting itself as "not detected".

The detection loop:

```
Expand Down Expand Up @@ -97,7 +99,12 @@ The full TD report and quote are stored in `attestation_report.raw_evidence` for

#### OPAQUE (Highest Assurance)

Detection conditions:
> **Not yet implemented.** This subsection describes the intended design. The current
> `OpaqueProvider` is a placeholder: it is excluded from auto-detect and raises
> `ATTESTATION_PROVIDER_NOT_IMPLEMENTED` when selected explicitly. The conditions below
> are the planned detection behavior, not shipped behavior.

Detection conditions (planned):
- The environment variable `OPAQUE_RUNTIME_ENDPOINT` is set and non-empty.

What goes in `attestation_report.measurement`:
Expand Down
1 change: 1 addition & 0 deletions docs/spec/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This is the normative registry for all error codes used across the cMCP Runtime.
|---|---|---|---|---|
| `ATTESTATION_REPORT_UNAVAILABLE` | 503 | FATAL | TEE provider did not return an attestation report within timeout | [failure-modes.md FM-1](failure-modes.md) |
| `ATTESTATION_PROVIDER_UNSUPPORTED` | 500 | FATAL | No supported TEE provider detected and `CMCP_DEV_MODE` is not set | [attestation.md §1.1](attestation.md) |
| `ATTESTATION_PROVIDER_NOT_IMPLEMENTED` | 501 | FATAL | A recognized provider was explicitly selected but is not yet implemented (e.g. `opaque`) | [attestation.md §1.1](attestation.md) |
| `POLICY_HASH_MISMATCH` | 500 | FATAL | Measured policy bundle hash does not match deployment manifest | [failure-modes.md FM-4](failure-modes.md) |
| `CATALOG_HASH_MISMATCH` | 500 | FATAL | Measured catalog hash does not match deployment manifest | [attestation.md §5](attestation.md) |
| `AGENT_MANIFEST_BINDING_FAILED` | 500 | FATAL | Signed Agent Manifest signature, authenticated subject, policy hash, or catalog hash did not match the runtime session inputs | [session-policy.md](session-policy.md) |
Expand Down
13 changes: 13 additions & 0 deletions src/cmcp_runtime/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ class AttestationProviderUnsupported(CMCPError):
http_status = 500


class AttestationProviderNotImplemented(AttestationProviderUnsupported):
"""A recognized provider was explicitly selected but is not yet implemented.

Distinct from AttestationProviderUnsupported (hardware simply not present):
this signals a known placeholder provider (e.g. ``opaque``) so an operator who
selects it gets an explicit error instead of a silent fall-through. Subclasses
AttestationProviderUnsupported so the gateway still refuses to start.
"""

code = "ATTESTATION_PROVIDER_NOT_IMPLEMENTED"
http_status = 501


class PolicyHashMismatch(CMCPError):
code = "POLICY_HASH_MISMATCH"
http_status = 500
Expand Down
33 changes: 27 additions & 6 deletions src/cmcp_runtime/tee/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@

from cmcp_runtime.config import Config
from cmcp_runtime.config import TEEProvider as TEEProviderEnum
from cmcp_runtime.errors import AttestationProviderUnsupported
from cmcp_runtime.errors import (
AttestationProviderNotImplemented,
AttestationProviderUnsupported,
)
from cmcp_runtime.tee.base import SoftwareOnlyProvider, TEEProvider

logger = logging.getLogger(__name__)

# Detection probe order from docs/spec/attestation.md §1.1
_PROBE_ORDER: list[str] = ["tpm", "sev-snp", "tdx", "opaque"]
# Detection probe order from docs/spec/attestation.md §1.1. The `opaque` provider is
# intentionally excluded: it is a not-yet-implemented placeholder, so it is never
# auto-selected. Selecting it explicitly raises AttestationProviderNotImplemented.
_PROBE_ORDER: list[str] = ["tpm", "sev-snp", "tdx"]


def _get_provider_impl(name: str, config: Config | None = None) -> TEEProvider | None:
Expand Down Expand Up @@ -50,7 +55,7 @@ def detect_provider(config: Config) -> TEEProvider:
Detect and return the active TEE provider.

Follows the probe order from docs/spec/attestation.md §1.1:
tpm -> sev-snp -> tdx -> opaque
tpm -> sev-snp -> tdx

If no hardware provider is found:
- CMCP_DEV_MODE=1: returns SoftwareOnlyProvider with a WARN log
Expand All @@ -74,7 +79,15 @@ def detect_provider(config: Config) -> TEEProvider:
)
return SoftwareOnlyProvider()
impl = _get_provider_impl(name, config)
if impl is None or not impl.detect():
if impl is None:
raise AttestationProviderUnsupported(
f"Requested provider '{name}' not available on this host",
detail="Check that the TEE hardware is present and accessible",
)
# A placeholder provider (e.g. opaque) raises AttestationProviderNotImplemented
# from detect(); let that explicit error propagate rather than collapsing it into
# a generic "not available on this host".
if not impl.detect():
raise AttestationProviderUnsupported(
f"Requested provider '{name}' not available on this host",
detail="Check that the TEE hardware is present and accessible",
Expand All @@ -85,7 +98,15 @@ def detect_provider(config: Config) -> TEEProvider:
# Auto-detection
for name in _PROBE_ORDER:
impl = _get_provider_impl(name, config)
if impl is not None and impl.detect():
if impl is None:
continue
try:
available = impl.detect()
except AttestationProviderNotImplemented:
# Defensive: a not-yet-implemented provider must never be auto-selected.
logger.debug("Provider %s is not implemented; skipping in auto-detect", name)
continue
if available:
logger.info("TEE provider: %s (auto-detected)", name)
return impl

Expand Down
25 changes: 21 additions & 4 deletions src/cmcp_runtime/tee/opaque.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
"""Opaque Systems TEE provider stub: not yet implemented."""
"""OPAQUE managed-runtime TEE provider: not yet implemented.

Selecting this provider raises AttestationProviderNotImplemented rather than
silently reporting "not detected". It is excluded from the auto-detect probe order.
"""

from __future__ import annotations

from cmcp_runtime.errors import AttestationProviderNotImplemented
from cmcp_runtime.tee.base import AttestationReport, TEEProvider

_NOT_IMPLEMENTED_MSG = (
"The OPAQUE managed-runtime attestation provider is not yet implemented. "
"Select tpm, sev-snp, or tdx, or set CMCP_DEV_MODE=1 for software-only development."
)


class OpaqueProvider(TEEProvider):
"""Placeholder for the Opaque Systems TEE provider (not yet implemented)."""
"""Placeholder for the OPAQUE managed-runtime provider (not yet implemented).

Unlike a hardware provider that is simply absent, ``detect`` and
``get_attestation_report`` raise AttestationProviderNotImplemented rather than
reporting "not detected", so explicitly selecting this provider yields a clear
error instead of a silent fall-through. It is intentionally excluded from the
auto-detect probe order (see ``tee/detect.py``).
"""

def provider_name(self) -> str:
return "opaque"

def detect(self) -> bool:
return False
raise AttestationProviderNotImplemented(_NOT_IMPLEMENTED_MSG)

def get_attestation_report(self, nonce: bytes) -> AttestationReport:
raise NotImplementedError("Opaque provider not yet implemented")
raise AttestationProviderNotImplemented(_NOT_IMPLEMENTED_MSG)
20 changes: 19 additions & 1 deletion tests/unit/test_tee.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

from cmcp_runtime.config import Config
from cmcp_runtime.config import TEEProvider as TEEProviderEnum
from cmcp_runtime.errors import AttestationProviderUnsupported
from cmcp_runtime.errors import (
AttestationProviderNotImplemented,
AttestationProviderUnsupported,
)
from cmcp_runtime.tee.base import SoftwareOnlyProvider, jwk_thumbprint, make_nonce
from cmcp_runtime.tee.detect import detect_provider

Expand Down Expand Up @@ -138,6 +141,21 @@ def test_detect_explicit_software_only_with_dev_mode(dev_config):
assert isinstance(provider, SoftwareOnlyProvider)


def test_detect_explicit_opaque_raises_not_implemented(dev_config):
"""Explicitly selecting the opaque provider raises an explicit not-implemented error,
not a silent fall-through or a generic 'unsupported'."""
dev_config.attestation.provider = TEEProviderEnum.OPAQUE
with pytest.raises(AttestationProviderNotImplemented):
detect_provider(dev_config)


def test_opaque_excluded_from_auto_probe_order():
"""The not-yet-implemented opaque provider must never be in the auto-detect order."""
from cmcp_runtime.tee.detect import _PROBE_ORDER

assert "opaque" not in _PROBE_ORDER


# ── HW-001: AttestationReport provider validation ────────────────────────────

def test_attestation_report_unknown_provider_raises():
Expand Down
10 changes: 6 additions & 4 deletions tests/unit/test_tee_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@

import pytest

from cmcp_runtime.errors import AttestationProviderNotImplemented
from cmcp_runtime.tee.opaque import OpaqueProvider
from cmcp_runtime.tee.sev_snp import SEVSNPProvider, _SnpAttestationReport
from cmcp_runtime.tee.tdx import TDXProvider, _TdxReportReq
from cmcp_runtime.tee.tpm import TPMProvider

# ── OpaqueProvider ─────────────────────────────────────────────────────────────

def test_opaque_detect_returns_false() -> None:
assert OpaqueProvider().detect() is False
def test_opaque_detect_raises_not_implemented() -> None:
with pytest.raises(AttestationProviderNotImplemented):
OpaqueProvider().detect()


def test_opaque_get_report_raises() -> None:
with pytest.raises(NotImplementedError):
def test_opaque_get_report_raises_not_implemented() -> None:
with pytest.raises(AttestationProviderNotImplemented):
OpaqueProvider().get_attestation_report(b"\x00" * 32)


Expand Down