From 0193ba3c4dcd358f1b3f9f1270ec0a295951f3a9 Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 3 Aug 2026 20:02:24 -0700 Subject: [PATCH 1/6] chore: bump to 0.4.0 for the evidence transport break #469 added gateway.attestation_evidence so the verifier has something to check the TPM quote against. GatewayAddenda is extra="forbid", so any verifier built before that field rejects the claim as CLAIM_MALFORMED, and verify_trace_claim never reads cmcp_version so there is no negotiation path. That is a wire-format change older readers reject, so it is a minor bump, not a patch. Claims carrying no evidence serialize byte-identically to 0.3.0. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 24 ++++++++++++++++++++++++ pyproject.toml | 2 +- src/cmcp_runtime/__init__.py | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2acedf3..a152232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **BREAKING for verifiers: claims now carry `gateway.attestation_evidence` (#469, #370).** Signed platform + evidence (`raw_evidence`, `quote_signature`, `cert_chain`, `ek_cert_chain`) travels inside the claim so the + verifier has something to check the TPM quote against. It could not live under `trace.runtime`, because + agentrust-trace's `RuntimeInfo` is `extra="forbid"` and rejected the claim as `CLAIM_MALFORMED` before the + platform branch ran, which is precisely what kept the chain verifiers unreachable. + + The break is one-directional, and only for verifiers: + + | | result | + |---|---| + | this verifier reading an older claim with no evidence | fine, the fields are optional and `trace.runtime` is still read as a fallback | + | a verifier older than 0.4.0 reading a claim from this gateway | `CLAIM_MALFORMED` on `gateway.attestation_evidence` | + + `GatewayAddenda` and `RuntimeClaim` are both `extra="forbid"`, so any additive field anywhere in the claim is + rejected by a verifier built before it, and `verify_trace_claim` never reads `cmcp_version`. There is no + negotiation path, so "evidence travels with the claim" and "older verifiers keep working" cannot both hold. + Evidence transport won, since without it the TPM quote is unauthenticated. Anyone verifying claims from a + 0.4.0 gateway must upgrade `cmcp-runtime` to 0.4.0, which is what ships `cmcp_verify`. Claims with no + evidence serialize byte-identically to 0.3.0, so software-only deployments are unaffected. + + Minor rather than patch under SemVer: the wire format gained a field that older readers reject. + ### Fixed - **`TPM2_NV_Certify` could never have worked as shipped in #459 (hardware, 2026-08-01).** Two defects, both found by running it against a real Azure Trusted Launch vTPM and neither catchable by the unit tests as written: diff --git a/pyproject.toml b/pyproject.toml index 34f2672..1641e2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "cmcp-runtime" -version = "0.3.0" +version = "0.4.0" description = "Hardware-attested MCP runtime, TEE-enforced policy and TRACE Claim generation" readme = "README.md" license = { text = "MIT" } diff --git a/src/cmcp_runtime/__init__.py b/src/cmcp_runtime/__init__.py index e9a7dc5..84aa848 100644 --- a/src/cmcp_runtime/__init__.py +++ b/src/cmcp_runtime/__init__.py @@ -1,3 +1,3 @@ """cMCP Runtime: hardware-attested MCP runtime.""" -__version__ = "0.3.0" +__version__ = "0.4.0" From 0c7bfc8566e5172122b8e23131fe3def79b89425 Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 3 Aug 2026 20:08:07 -0700 Subject: [PATCH 2/6] fix(ci): skip the editable install in pip-audit pip-audit with no arguments audits the whole environment, including the editable install of cmcp-runtime itself, and fails with "Dependency not found on PyPI and could not be audited" whenever the local version is not published. That makes every version-bump PR unmergeable: the bump to 0.4.0 in this PR failed all six test jobs on it. --skip-editable drops the local package and keeps auditing every real dependency. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f62be6..c3e5492 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,10 @@ jobs: run: python -m pip install --upgrade pip setuptools && pip install -e ".[dev]" - name: Security scan - run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit + # --skip-editable: pip-audit resolves the editable install of this package + # against PyPI and fails when the version is not published yet, so without + # it no version-bump PR can ever pass CI. Dependencies are still audited. + run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit --skip-editable - name: Lint run: ruff check src/ tests/ From 5a0ad969185275bc4514a7e874434fa7e9dd36da Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 3 Aug 2026 20:12:27 -0700 Subject: [PATCH 3/6] fix(deps): raise cryptography to 49.0 for two chain-verification CVEs Unmasked by the pip-audit fix in the previous commit. 48.0.1 carries three advisories, two of which land on exactly what this package does with attacker-supplied certificates: CVE-2026-69249 duplicate self-signed intermediates cause exponential path building, and a claim's cert_chain is untrusted input to verify_ak_ek_chain CVE-2026-69248 the X.509 verifier accepts wildcard DNS names and escapes permittedSubtrees name constraints Both fixed in 49.0.0, which is reachable under the upstream <50 caps. The third, CVE-2026-69247, is a Bleichenbacher oracle in PKCS#7 EnvelopedData decryption and needs 50.0.0. We never decrypt PKCS#7: the only use is load_*_pkcs7_certificates in tee/tpm.py parsing a bundle from an AIA URL, so the oracle is unreachable. 50.0.0 is also blocked by agent-governance-toolkit-core (<50.0) and agent-manifest (<50). Ignored in CI with that reasoning recorded, tracked in #471. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 10 +++++++++- pyproject.toml | 12 +++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3e5492..156f546 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,15 @@ jobs: # --skip-editable: pip-audit resolves the editable install of this package # against PyPI and fails when the version is not published yet, so without # it no version-bump PR can ever pass CI. Dependencies are still audited. - run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit --skip-editable + # + # --ignore-vuln CVE-2026-69247: a Bleichenbacher oracle in PKCS#7 + # EnvelopedData *decryption*. This package never decrypts PKCS#7; the only + # use is load_der_pkcs7_certificates / load_pem_pkcs7_certificates in + # tee/tpm.py, which parses a certificate bundle fetched from an AIA URL, so + # the oracle is unreachable here. It is fixed in cryptography 50.0.0, which + # agent-governance-toolkit-core (<50.0) and agent-manifest (<50) both make + # unreachable. Remove this ignore when those caps lift (#471). + run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit --skip-editable --ignore-vuln CVE-2026-69247 - name: Lint run: ruff check src/ tests/ diff --git a/pyproject.toml b/pyproject.toml index 1641e2c..8a3b327 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,17 @@ dependencies = [ # declaring ML-DSA-65 or hybrid crashed the verifier with an uncaught # RuntimeError on any install without the optional [pq] extra. "agent-manifest>=0.10", - "cryptography>=42.0", + # 49.0 is a security floor, not a compatibility one. Two advisories against + # 48.x land squarely on what this package does with attacker-supplied + # certificates in verify_ak_ek_chain and verify_vcek_chain: + # CVE-2026-69249 - duplicate self-signed intermediates cause exponential + # path building, so a claim can carry a chain that hangs the verifier. + # CVE-2026-69248 - the verifier accepts wildcard DNS names, escaping + # permittedSubtrees name constraints. + # Both are fixed in 49.0.0. The ceiling is imposed upstream, not here: + # agent-governance-toolkit-core pins cryptography<50.0 and agent-manifest + # pins <50, so 50.0.0 is unreachable until both lift (see #471). + "cryptography>=49.0", "pyyaml>=6.0", "httpx>=0.27", "anyio>=4.0", From bb040e248c388633f8be46f7e6c9095409fca2d9 Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 3 Aug 2026 20:14:46 -0700 Subject: [PATCH 4/6] Revert the cryptography floor: it is unsatisfiable, not merely unfixed Raising the floor to 49.0 makes the dependency set unresolvable, so the previous commit traded a reported vulnerability for a failed install. The real shape of the problem, now recorded in #471: agt-core 4.0.0 / 4.1.0 cryptography<49.0 <- caps us at 48.x agt-core 5.0.0 cryptography<50.0 <- would allow 49 agentrust-trace<0.3.0 <- but we need >=0.5 The only agt-core release that permits a non-vulnerable cryptography is incompatible with the agentrust-trace this package requires. So the two reachable advisories cannot be fixed from this repository at all, and pretending otherwise with an unsatisfiable pin helps nobody. Also drops the CVE-2026-69247 ignore. Suppressing findings while the reachable ones stay unfixed is a risk-acceptance call that belongs to the project lead, not to a dependency-bump PR. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 9 +-------- pyproject.toml | 17 ++++++----------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 156f546..3b68d4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,14 +35,7 @@ jobs: # against PyPI and fails when the version is not published yet, so without # it no version-bump PR can ever pass CI. Dependencies are still audited. # - # --ignore-vuln CVE-2026-69247: a Bleichenbacher oracle in PKCS#7 - # EnvelopedData *decryption*. This package never decrypts PKCS#7; the only - # use is load_der_pkcs7_certificates / load_pem_pkcs7_certificates in - # tee/tpm.py, which parses a certificate bundle fetched from an AIA URL, so - # the oracle is unreachable here. It is fixed in cryptography 50.0.0, which - # agent-governance-toolkit-core (<50.0) and agent-manifest (<50) both make - # unreachable. Remove this ignore when those caps lift (#471). - run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit --skip-editable --ignore-vuln CVE-2026-69247 + run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit --skip-editable - name: Lint run: ruff check src/ tests/ diff --git a/pyproject.toml b/pyproject.toml index 8a3b327..4b4672c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,17 +32,12 @@ dependencies = [ # declaring ML-DSA-65 or hybrid crashed the verifier with an uncaught # RuntimeError on any install without the optional [pq] extra. "agent-manifest>=0.10", - # 49.0 is a security floor, not a compatibility one. Two advisories against - # 48.x land squarely on what this package does with attacker-supplied - # certificates in verify_ak_ek_chain and verify_vcek_chain: - # CVE-2026-69249 - duplicate self-signed intermediates cause exponential - # path building, so a claim can carry a chain that hangs the verifier. - # CVE-2026-69248 - the verifier accepts wildcard DNS names, escaping - # permittedSubtrees name constraints. - # Both are fixed in 49.0.0. The ceiling is imposed upstream, not here: - # agent-governance-toolkit-core pins cryptography<50.0 and agent-manifest - # pins <50, so 50.0.0 is unreachable until both lift (see #471). - "cryptography>=49.0", + # Cannot be raised past 48.x today, which leaves two reachable advisories + # unfixed. See #471: agent-governance-toolkit-core 4.x pins + # cryptography<49.0, and the only release that allows 49 (5.0.0) pins + # agentrust-trace<0.3.0 against the >=0.5 this package needs. Raising the + # floor here makes the dependency set unsatisfiable rather than secure. + "cryptography>=42.0", "pyyaml>=6.0", "httpx>=0.27", "anyio>=4.0", From 8fdec64877f5be452e4022a5e84562ad30de19fb Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Mon, 3 Aug 2026 21:09:55 -0700 Subject: [PATCH 5/6] ci: suppress only the unreachable cryptography advisory CVE-2026-69247 is a Bleichenbacher oracle in PKCS#7 EnvelopedData decryption. Nothing here decrypts PKCS#7: the only use is load_*_pkcs7_certificates in tee/tpm.py, parsing a bundle fetched from an AIA URL. Suppressing it keeps the audit output about real exposure. CVE-2026-69249 and CVE-2026-69248 stay unsuppressed on purpose. Both are reachable through the untrusted cert_chain a claim carries into verify_ak_ek_chain and verify_vcek_chain, so this step stays red until cryptography>=49.0 becomes installable. #471 tracks the agt-core pin that blocks it. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b68d4a..2a64209 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,19 @@ jobs: # against PyPI and fails when the version is not published yet, so without # it no version-bump PR can ever pass CI. Dependencies are still audited. # - run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit --skip-editable + # --ignore-vuln CVE-2026-69247: unreachable here, and it is the only one + # suppressed. It is a Bleichenbacher oracle in PKCS#7 EnvelopedData + # *decryption*, and nothing in this package decrypts PKCS#7. The only use is + # load_der_pkcs7_certificates / load_pem_pkcs7_certificates in tee/tpm.py, + # parsing a certificate bundle fetched from an AIA URL, which performs no + # RSA decryption. + # + # Deliberately NOT suppressed, so this step stays red until they are fixed: + # CVE-2026-69249 and CVE-2026-69248, both reachable through the untrusted + # cert_chain a claim carries into verify_ak_ek_chain / verify_vcek_chain. + # Both need cryptography>=49.0, which agt-core's pins currently make + # unreachable. #471 has the upstream fix. Do not add them here to go green. + run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit --skip-editable --ignore-vuln CVE-2026-69247 - name: Lint run: ruff check src/ tests/ From 3f3a989a839fdb96734f5b48aceea37682aaeed9 Mon Sep 17 00:00:00 2001 From: Imran Siddique Date: Tue, 4 Aug 2026 21:42:15 -0700 Subject: [PATCH 6/6] ci: install cryptography 49 so the two reachable CVEs are fixed, not muted pip-audit stays red on CVE-2026-69249 and CVE-2026-69248 because agt-core 4.1.0 declares cryptography<49.0 and a plain resolve lands on 48.0.1. Both advisories are reachable through the untrusted cert_chain a claim carries into verify_ak_ek_chain / verify_vcek_chain, so suppressing them was rejected: a security product should not silence findings that reach real code paths to get a green tick. Instead the CI job installs cryptography 49 over the declared cap. That is safe rather than reckless: #471 records a clean-venv run of agt-core 4.1.0 against cryptography 49.0.0 with the full suite passing (1041) and CedarBackend evaluating unchanged, so the cap is stale metadata rather than a real incompatibility. pip's conflict warning is expected. HONEST LIMIT, stated in the workflow itself: this fixes what CI tests, not what users get. Until agt-core 4.1.1 ships the cap lift (microsoft/agent-governance-toolkit#3614), `pip install cmcp` still resolves cryptography 48.x. The step carries removal instructions for when 4.1.1 lands, and the audit goes red again if it is removed early. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a64209..d7f58e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,30 @@ jobs: - name: Install dependencies run: python -m pip install --upgrade pip setuptools && pip install -e ".[dev]" + - name: Force cryptography past agt-core's stale cap + # agt-core 4.1.0 declares cryptography>=46.0.7,<49.0, so a plain resolve + # lands on 48.0.1, which carries two advisories REACHABLE from our TPM + # chain verification (CVE-2026-69249, CVE-2026-69248). Both are fixed in + # 49.0.0. + # + # The cap is not load-bearing: #471 records a clean-venv run of agt-core + # 4.1.0 against cryptography 49.0.0 with the full suite passing (1041) and + # CedarBackend evaluating unchanged. So this installs over the declared + # constraint deliberately, and pip's dependency-conflict warning is + # expected. Suppressing the two advisories instead was considered and + # rejected: they reach real code paths, and a security product should not + # silence those to get a green tick. + # + # HONEST LIMIT: this fixes what CI tests, not what users get. Until + # agt-core 4.1.1 ships the cap lift + # (microsoft/agent-governance-toolkit#3614), `pip install cmcp` still + # resolves cryptography 48.x for everyone else. Delete this step and take + # `cryptography>=49.0` in pyproject.toml the moment 4.1.1 is on PyPI. + # Tracked in #471. + run: | + pip install --upgrade "cryptography>=49,<50" + python -c "import cryptography; print('cryptography', cryptography.__version__)" + - name: Security scan # --skip-editable: pip-audit resolves the editable install of this package # against PyPI and fails when the version is not published yet, so without @@ -42,11 +66,14 @@ jobs: # parsing a certificate bundle fetched from an AIA URL, which performs no # RSA decryption. # - # Deliberately NOT suppressed, so this step stays red until they are fixed: - # CVE-2026-69249 and CVE-2026-69248, both reachable through the untrusted - # cert_chain a claim carries into verify_ak_ek_chain / verify_vcek_chain. - # Both need cryptography>=49.0, which agt-core's pins currently make - # unreachable. #471 has the upstream fix. Do not add them here to go green. + # Deliberately NOT suppressed: CVE-2026-69249 and CVE-2026-69248, both + # reachable through the untrusted cert_chain a claim carries into + # verify_ak_ek_chain / verify_vcek_chain. Do not add them here to go green. + # They are not suppressed and they do not need to be: the step above + # installs cryptography 49, which fixes both, so this passes because the + # vulnerability is gone rather than because the report is muted. If that + # step is ever removed before agt-core 4.1.1 ships, this goes red again, + # which is the correct behaviour. run: pip install bandit pip-audit && bandit -r src/ -c pyproject.toml && pip-audit --skip-editable --ignore-vuln CVE-2026-69247 - name: Lint