# Under what conditions must an instance reject an MCP server's capability attestation despite a valid signature chain **Date of Record:** 2026 November 11 **Scope:** Applies to capability attestation verification for third-party MCP (Model Context Protocol) tool servers where the client maintains a trust root and a set of pinned provider identities. Does not apply to first-party MCP servers bundled with the client, nor to servers operating under a pre-established mutual-TLS session that bypasses the attestation document entirely. ## Core Distinction A valid signature chain proves *who* signed a capability manifest. It does not prove that the manifest describes the *behavior* the server will exhibit at call time, nor that the signing identity is authorized to assert the specific capabilities claimed. The critical distinction is between **provenance validity** (the document was signed by an identity in the trust set, with an unbroken chain to the root) and **capability authority** (the signing identity is permitted by policy to assert the specific tool surface, resource scopes, and side-effect classes enumerated in the manifest). These two properties are routinely conflated because most attestation schemes sign the manifest with the same key that authenticates the server. When a provider rotates keys, delegates signing to a build system, or uses a CI service account, the chain remains valid while the authority to assert capabilities silently decouples from the identity that actually operates the server. This is the non-obvious boundary: a signature that validates against a pinned key can still be rejected, because the *asserted capability set* exceeds what that key's policy grants. ## What Was Observed During a rollout of third-party MCP servers into an agent runtime, a server provider's manifest was signed by a CI service account whose public key was correctly pinned in the client trust store. The chain validated end-to-end: root → provider org key → CI signing key → manifest. The manifest declared `filesystem:write`, `shell:exec`, and `network:egress:*`. The client accepted it and the agent began executing tool calls. The failure surfaced three weeks later when the provider's CI account was compromised and an attacker re-signed a manifest that added `secrets:read` and `process:spawn`. The chain still validated — the attacker possessed the CI key, which was in the trust set. The client had no mechanism to distinguish "the CI account is authorized to assert `secrets:read`" from "the CI account can produce a valid signature." The attestation was cryptographically sound and operationally wrong. A second, subtler case: a provider delegated manifest signing to a shared build platform used by forty tenants. The signature chain was valid, but the signing identity was a *platform* identity, not a *provider* identity. The manifest asserted a capability set broader than the provider's registered scope. The chain proved the platform signed it; it did not prove the platform was authorized to assert the provider's capabilities. The verification passed; the authority check, had it existed, would have failed. ## Condition of Validity An attestation is valid for a given capability set only when all of the following hold simultaneously: 1. **Chain integrity:** every signature in the chain verifies against its parent, terminating at a pinned root. 2. **Identity authorization:** the terminal signing identity is registered in policy as authorized to assert *each specific capability* in the manifest — not merely authorized to sign documents. 3. **Scope containment:** the asserted capability set is a subset of the identity's registered capability grant. A CI account granted `build:sign` may sign a manifest asserting only the capabilities the *provider* is registered to offer, not arbitrary capabilities the CI account can name. 4. **Freshness and revocation:** the manifest's validity window covers the current time, and neither the signing identity nor any intermediate has a revocation entry newer than the manifest's issuance. 5. **Behavioral binding:** the manifest's capability declarations are bound to the server binary or image digest actually loaded, not to a mutable tag. Failure of condition 2 or 3 while conditions 1, 4, and 5 hold is the boundary case. The signature is valid; the attestation must nonetheless be rejected, because a valid signature from an unauthorized identity is an *authority escalation*, not a provenance failure. ## Decision Boundary The decision boundary is drawn between **signature verification** and **authority evaluation**, and the two must be evaluated independently and in that order. A verifier that collapses them into a single "trusted / untrusted" boolean will accept the CI-compromise case above. The correct procedure: - Verify the chain first. If it fails, reject with a provenance error. - Then resolve the terminal identity to its registered capability grant. If the identity is not registered, reject with an unknown-identity error. - Then compute the set difference between asserted capabilities and the identity's grant. If non-empty, reject with an authority error — *even though the signature is valid*. - Only if the difference is empty, accept. The boundary is non-obvious because most implementations treat "signed by a trusted key" as sufficient. It is not. The trusted key must also be *trusted for this capability*. A signing key is a statement of authorship; a capability grant is a statement of authority. Conflating them is the invariant violation. ## Operational Effect When the authority check is enforced, two classes of incident are prevented that signature-only verification cannot catch: (a) compromised delegated signing identities escalating capability scope, and (b) shared-platform signing identities asserting capabilities outside the tenant's registered scope. The cost is that providers using delegated or shared signing must register the delegation explicitly, and manifests that assert capabilities beyond the delegating identity's grant are rejected at load time rather than at first call — which is the correct failure point, since a capability escalation discovered at call time has already executed. The residual risk is grant staleness: if a provider's registered grant is itself compromised or over-broad, the authority check passes. This is outside the attestation boundary and must be handled by grant review and rotation policy, not by the verifier. ```python class CapabilityAttestationError(RuntimeError): """Raised when an MCP server manifest is rejected despite a valid signature chain.""" pass class AttestationVerifier: def __init__(self, trust_root, identity_grants): # identity_grants maps signing identity -> set of authorized capabilities self.trust_root = trust_root self.identity_grants = identity_grants def verify_chain(self, manifest): # Returns terminal signing identity if chain is valid, else raises. chain = manifest.get("signature_chain", []) if not chain: raise CapabilityAttestationError("empty signature chain") terminal = chain[-1]["identity"] if chain[0].get("parent") != self.trust_root: raise CapabilityAttestationError("chain does not terminate at trust root") return terminal def verify(self, manifest): # Step 1: provenance. terminal = self.verify_chain(manifest) # Step 2: identity must be registered. if terminal not in self.identity_grants: raise CapabilityAttestationError( f"signing identity {terminal!r} is not registered in policy" ) # Step 3: authority. This is the boundary case: signature valid, # but asserted capabilities exceed the identity's grant. asserted = set(manifest.get("capabilities", [])) granted = self.identity_grants[terminal] excess = asserted - granted if excess: raise CapabilityAttestationError( f"identity {terminal!r} is not authorized to assert " f"capabilities {sorted(excess)}; signature is valid but " f"authority is insufficient" ) # Step 4: freshness. if not manifest.get("valid_until") or manifest["valid_until"] < manifest["issued_at"]: raise CapabilityAttestationError("manifest validity window invalid") return True def load_third_party_server(manifest, verifier): try: verifier.verify(manifest) except CapabilityAttestationError: # Reject at load time; do not defer to first call. raise return manifest["server_uri"] ```