diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f62be6..d7f58e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,51 @@ 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 - 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. + # + # --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: 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 run: ruff check src/ tests/ 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..4b4672c 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" } @@ -32,6 +32,11 @@ 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", + # 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", 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"