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
39 changes: 29 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# agentrust-io demos

Runnable demos for [cMCP](https://github.com/agentrust-io/cmcp) and [TRACE](https://github.com/agentrust-io/trace-spec). Five demos, ~6 minutes total.
Runnable demos for [cMCP](https://github.com/agentrust-io/cmcp), [TRACE](https://github.com/agentrust-io/trace-spec), and [WCM](https://pypi.org/project/weight-custody-manifest/). Six demos, ~7 minutes total.

---

## Prerequisites

```
pip install cmcp-runtime
pip install cmcp-runtime weight-custody-manifest
```

`cmcp-runtime` includes all dependencies (`starlette`, `uvicorn`, `cmcp-verify`). All demos use `CMCP_DEV_MODE=1` (software-only TEE, no hardware required). The local MCP server performs real filesystem operations on `./workspace/`.
`cmcp-runtime` includes all dependencies (`starlette`, `uvicorn`, `cmcp-verify`) and drives demos 1 through 5; `weight-custody-manifest` (from PyPI) drives demo 6. All demos use `CMCP_DEV_MODE=1` (software-only TEE, no hardware required). The local MCP server performs real filesystem operations on `./workspace/`.

## Quick start: one command

```
python demo.py # run all three demos, pausing before each (good for live talks)
python demo.py # run all demos, pausing before each (good for live talks)
python demo.py --no-pause # run straight through
python demo.py 2 # run only demo 2
```
Expand Down Expand Up @@ -141,6 +141,23 @@ What you see:

---

## Demo 6 -- Weight custody (~60 seconds)

Demos 1-5 govern what an agent *does*. Demo 6 is the layer beneath: the model *weights* themselves. A Weight Custody Manifest binds the exact weight hash, gates the decryption key behind attestation, and carries the fine-tune's lineage. It needs no hardware and no server, and depends only on the `weight-custody-manifest` package from PyPI.

```
python demo-06-weight-custody/run.py
```

What you see:
- a manifest jointly signed (builder + custodian) binds the checkpoint's exact `weights_hash`, and the signature verifies
- the attestation gate releases the key only for the certified serving stack (genuine nonce, approved platform, signed image measurement)
- a tampered checkpoint's hash does not match the manifest, so it is **refused before it ever loads**
- a fine-tune is a derivative whose lineage verifies back to the signed base (the derivative is the real IP)
- honest scope: this is accountability-grade against an operator who physically owns the silicon (see TEE.fail), not silicon-proof custody

---

## Structure

```
Expand Down Expand Up @@ -179,12 +196,14 @@ demos/
| +-- run.py # Cross-platform launcher (use this)
| +-- run.sh # bash-only launcher
+-- demo-05-compliance-domain/
+-- cmcp-config.yaml
+-- catalog.json # Tags tools with compliance_domain + BAA status
+-- policies/ # Cedar: forbid any tool when context.baa_covered == false
+-- call.py # Two BAA-covered tools allowed, one non-covered tool denied
+-- run.py # Cross-platform launcher (use this)
+-- run.sh # bash-only launcher
| +-- cmcp-config.yaml
| +-- catalog.json # Tags tools with compliance_domain + BAA status
| +-- policies/ # Cedar: forbid any tool when context.baa_covered == false
| +-- call.py # Two BAA-covered tools allowed, one non-covered tool denied
| +-- run.py # Cross-platform launcher (use this)
| +-- run.sh # bash-only launcher
+-- demo-06-weight-custody/
+-- run.py # WCM flow: sign, attestation gate, tamper refusal, lineage
```

---
Expand Down
187 changes: 187 additions & 0 deletions demo-06-weight-custody/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"""Demo 6: Weight custody - prove the model is the one the builder shipped.

Usage:
python demo-06-weight-custody/run.py # from repo root
python run.py # from the demo-06 directory

A pure in-process Weight Custody Manifest flow with a software (mock)
attestation provider, so it runs anywhere with no hardware. It shows the four
things a manifest gives you over a bare checkpoint download: provenance, an
attestation gate, tamper refusal, and derivative lineage. Possession is not
provenance.
"""
from __future__ import annotations

import hashlib
import sys

from wcm import (
EnclaveSession,
Ed25519Signer,
KeyBrokerService,
SoftwareProvider,
VerificationContext,
WeightCustodyManifest,
generate_ed25519,
is_root,
verify_lineage,
verify_manifest,
)

sys.stdout.reconfigure(line_buffering=True)


def rule(title: str) -> None:
print(f"\n{'-' * 66}\n{title}\n{'-' * 66}")


def sha256(data: bytes) -> str:
return "sha256:" + hashlib.sha256(data).hexdigest()


def build_manifest(
*,
weights_hash: str,
license_text: str,
serving_measurement: str,
org: str,
derivatives: str,
derived_from: str | None = None,
rights_holder: dict | None = None,
) -> dict:
m: dict = {
"manifest_version": "0.1",
"weights_hash": weights_hash,
"builder": {"identity": org, "signing_key": "ed25519:demo"},
"release_terms": {
"license": license_text,
"permitted_derivatives": "fine-tune-only",
"derivatives": derivatives,
"permitted_environments": ["enterprise-governed-enclave"],
},
"release_policy": {
"required_assurance_tier": "hardware-attested",
"trusted_time_source": "secure-tsc",
"required_hw_platform": ["amd-sev-snp", "nvidia-cc-gpu"],
"required_gpu_measurement": {"rim_pin": "nvidia-rim:demo-golden"},
"required_serving_image": {
"signer": "ed25519:demo",
"release_rule": "prefer-current",
"accepted_measurements": [
{"measurement": serving_measurement, "status": "current"}
],
},
"attestation_revocation_check": "live-per-release, max-cache-age: short-window",
"revocation_authority": "builder-and-opaque-joint",
},
"custody": {
"custodian": org,
"custodian_type": "customer-self-custody",
"kbs_image": {"measurement": sha256(b"reference-kbs-image"), "signer": "ed25519:demo"},
"enclave_id": "did:example:enterprise-enclave-01",
"attestation_cadence": "1h",
},
"base_confidentiality": "gated-open",
"deployment_model": "builder-to-customer",
}
if derived_from is not None:
m["derived_from"] = derived_from
if rights_holder is not None:
m["rights_holder"] = rights_holder
return m


def sign(manifest: WeightCustodyManifest, keypair, role: str, signer: str) -> dict:
return Ed25519Signer(keypair).sign(manifest.unsigned_dict(), role=role, signer=signer)


def _release(kbs: KeyBrokerService, manifest: WeightCustodyManifest, serving: str):
"""Issue a fresh single-use challenge, produce evidence, run the gate."""
challenge = kbs.issue_challenge()
evidence = SoftwareProvider().produce(
challenge,
serving_image_measurement=serving,
gpu_measurement="nvidia-rim:demo-golden",
)
return kbs.verify_and_release(manifest, evidence)


def main() -> None:
print("Weight Custody Manifest: possession is not provenance.")
print("Real WCM code with a software (mock) attestation provider, no hardware.")

builder = generate_ed25519() # the model builder
custodian = generate_ed25519() # the deploying customer / governance function

# The builder ships a checkpoint and a serving stack it certifies.
checkpoint = b"<the certified model checkpoint the builder shipped>"
base_hash = sha256(checkpoint)
serving = sha256(b"vllm-0.6.3 + policy-bundle-v2 (the certified serving stack)")
cadence = "1h"

rule("1. The builder signs a manifest binding the exact weight hash")
base_doc = build_manifest(
weights_hash=base_hash,
license_text="Frontier-Model-License (usage + field-of-use)",
serving_measurement=serving,
org="frontier-labs",
derivatives="fine-tune-only",
)
base = WeightCustodyManifest.model_validate(base_doc)
base = base.with_signatures([
sign(base, builder, "builder", "frontier-labs"),
sign(base, custodian, "custodian", "enterprise-governance"),
])
ctx = VerificationContext()
ctx.add_key(builder.public_bytes)
ctx.add_key(custodian.public_bytes)
print("weights_hash bound :", base_hash)
print("manifest signature :", verify_manifest(base, ctx).ok, "(jointly signed builder + custodian)")

rule("2. Attestation gate: the key releases only into the certified stack")
kbs = KeyBrokerService({base.weights_hash: b"the-model-decryption-key"})
print("gate released key :", _release(kbs, base, serving).released)
print("enforced: genuine attestation nonce, approved platform, and a serving")
print("image measurement matching what the builder signed.")

rule("3. A tampered checkpoint fails before it ever loads")
tampered = checkpoint.replace(b"certified", b"backdoored")
matches = sha256(tampered) == base.weights_hash
print("certified hash :", base.weights_hash)
print("downloaded hash :", sha256(tampered))
print("matches manifest :", matches, "-> load proceeds" if matches else "-> REFUSE to load")
print("no human reads 2.8T parameters; the hash does the reading.")

rule("4. The fine-tune is the real IP: lineage back to the signed base")
derivative = checkpoint + b"<+ proprietary fine-tune on private data>"
deriv_hash = sha256(derivative)
deriv_doc = build_manifest(
weights_hash=deriv_hash,
license_text="Frontier-Model-License + enterprise-proprietary-derivative",
serving_measurement=serving,
org="enterprise-governance",
derivatives="none",
derived_from=base.weights_hash,
rights_holder={"base": "frontier-labs", "derivative": "enterprise"},
)
deriv = WeightCustodyManifest.model_validate(deriv_doc)
deriv = deriv.with_signatures([
sign(deriv, builder, "builder", "enterprise-governance"),
sign(deriv, custodian, "custodian", "enterprise-governance"),
])
lineage = verify_lineage({base.weights_hash: base, deriv.weights_hash: deriv}, deriv.weights_hash)
print("derivative :", deriv_hash)
print("lineage verified :", lineage.ok, " depth", lineage.depth, " root is a base:", is_root(base))

rule("What this is, and is not")
session = EnclaveSession.from_release(base, _release(kbs, base, serving))
session.use_key()
print("custody active : key held under a", cadence, "cadence, wiped on lapse",
"(time_floor " + session.time_floor.value + ")")
print("honest scope : accountability-grade against an operator who physically")
print(" owns the silicon (see TEE.fail), not silicon-proof")
print(" custody. It IS the provenance the download never gave you.")


if __name__ == "__main__":
main()
13 changes: 11 additions & 2 deletions demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

python demo.py # run all, pausing before each (for live talks)
python demo.py --no-pause # run straight through, no prompts
python demo.py 2 # run only demo 2 (1 through 5)
python demo.py 2 # run only demo 2 (1 through 6)

The trust chain, end to end:
Demo 1 cMCP enforces Cedar on every tool call and signs a TRACE claim.
Expand All @@ -14,6 +14,10 @@
Demo 4 by call context -- the same tool, allowed in one workflow, denied in another.
Demo 5 by tool attribute -- a non-BAA-covered tool refused by one guardrail rule.

And the layer beneath it all, the weights themselves:
Demo 6 Weight custody -- a signed manifest binds the exact weight hash; a tampered
checkpoint is refused before load, and a fine-tune's lineage verifies to the base.

All demos run in software-only mode (CMCP_DEV_MODE=1). That is deliberate: software
proves the whole chain except the hardware root, so verification reads
'partially_verified'. On real TDX / SEV-SNP the hardware field verifies too and it
Expand Down Expand Up @@ -48,6 +52,11 @@
"demo-05-compliance-domain/run.py",
"A tool that is not BAA-covered is refused by one guardrail rule, whatever it is\n"
" named. The decision is on the tool's compliance attribute, not its identity."),
("6", "Weight custody",
"demo-06-weight-custody/run.py",
"A checkpoint signed to its exact weight hash. Attestation gates the key, a\n"
" tampered fork is refused before load, and a fine-tune's lineage verifies back to\n"
" the signed base. Possession is not provenance."),
]

GREEN = "\033[92m"; BLUE = "\033[96m"; DIM = "\033[90m"; BOLD = "\033[1m"; RST = "\033[0m"
Expand Down Expand Up @@ -147,7 +156,7 @@ def run(idx, title, script, blurb, pause):

def main():
ap = argparse.ArgumentParser(description="Run the agentrust-io trust-chain demos.")
ap.add_argument("only", nargs="?", choices=["1", "2", "3", "4", "5"], help="run only this demo")
ap.add_argument("only", nargs="?", choices=["1", "2", "3", "4", "5", "6"], help="run only this demo")
ap.add_argument("--no-pause", action="store_true", help="run straight through, no prompts")
args = ap.parse_args()

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
cmcp-runtime
weight-custody-manifest>=0.19.0