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
15 changes: 14 additions & 1 deletion financial-services/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,20 @@ python financial-services/agent/credit_risk_agent.py --amount-eur 750000

## The Cedar policy

`policy/allow.cedar` — the escalation rule:
`policy/allow.cedar` has no catch-all permit: each tool is explicitly permitted only for the `credit-risk-analyst` workflow (declared by the agent via `_cmcp.workflow_id`). Wrong workflow, missing workflow, or an unlisted action is denied by Cedar's default-deny:

```cedar
permit (
principal,
action == Action::"Finance.documentReader",
resource
) when {
context has workflow_id &&
context.workflow_id == "credit-risk-analyst"
};
```

On top of the workflow-scoped permits, the escalation rule:

```cedar
@id("large-exposure-hitl")
Expand Down
42 changes: 37 additions & 5 deletions financial-services/policy/allow.cedar
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,44 @@
// Action names follow the cMCP convention: tool_name converted to
// PascalCase per underscore segment, e.g. finance.risk_report_writer
// becomes Action::"Finance.riskReportWriter".
//
// There is deliberately NO catch-all permit: anything not explicitly
// permitted below is denied (Cedar default-deny). The workflow_id is
// supplied by the agent via the _cmcp request metadata; calls without
// it are denied.

// Rule 1: the credit-risk-analyst workflow may read client documents.
permit (
principal,
action == Action::"Finance.documentReader",
resource
) when {
context has workflow_id &&
context.workflow_id == "credit-risk-analyst"
};

// Rule 2: the credit-risk-analyst workflow may query credit bureaus.
permit (
principal,
action == Action::"Finance.creditScoreLookup",
resource
) when {
context has workflow_id &&
context.workflow_id == "credit-risk-analyst"
};

// Rule 1: the attested catalog is the allow-list; permit catalog tools and
// let the forbid rules below carry the compliance constraints.
permit (principal, action, resource);
// Rule 3: the credit-risk-analyst workflow may write risk reports.
// Subject to the large-exposure forbid below.
permit (
principal,
action == Action::"Finance.riskReportWriter",
resource
) when {
context has workflow_id &&
context.workflow_id == "credit-risk-analyst"
};

// Rule 2: large-exposure escalation (MiFID II Art. 25).
// Rule 4: large-exposure escalation (MiFID II Art. 25).
// Any risk report above 500,000 EUR requires a human reviewer. The
// annotations are returned to the caller as structured advice on deny.
@id("large-exposure-hitl")
Expand All @@ -26,7 +58,7 @@ forbid (
context.arguments.amount_eur > 500000
};

// Rule 3: confidential financial data may only flow through an attested
// Rule 5: confidential financial data may only flow through an attested
// runtime. attestation_platform is supplied by the gateway from the TEE
// attestation report; "unknown" means no attestation evidence is present.
@id("require-attested-runtime")
Expand Down
39 changes: 20 additions & 19 deletions financial-services/trace-output/example-trust-record.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,46 @@
"cmcp_version": "1.0",
"trace": {
"eat_profile": "tag:agentrust.io,2026:trace-v0.1",
"iat": 1781144717,
"subject": "spiffe://cmcp.gateway/session/27b1aaa2-6972-4224-951e-80cb9c89003a",
"iat": 1781193684,
"subject": "spiffe://cmcp.gateway/session/7bab4703-706e-4f2e-80b3-c3611832cda8",
"runtime": {
"platform": "tpm2",
"measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"firmware_version": "software-only-dev-mode"
},
"policy": {
"bundle_hash": "sha256:eb87240ced9e589f93d4851f404f892690d2067039b90a4806a676e426bd674f",
"bundle_hash": "sha256:9650dec5dec7afbc8608f0bdb29b9266c1f05990be603255e806479a76e9e23e",
"enforcement_mode": "enforce",
"version": "credit-risk-v4.2"
},
"data_class": "confidential",
"tool_transcript": {
"hash": "sha256:f06d8771565a018e3f301cbe59e312cd9684eb2b3c84a7aa2d4a85f45075a6a3",
"call_count": 3
"hash": "sha256:bf92310550233e5363441d0cc3b4158480e86e1bee4b958123bb39e7c63be262",
"call_count": 4
},
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "uYElaypX9Nuhfdf-TXDGOMof81zP8HKJX2z6iWmAPKU",
"kid": "cmcp-b981256b"
"x": "ZlmbQ6r1FqVml6LsKDifDR4grPSBhhRYZMqc8o0DdAc",
"kid": "cmcp-66599b43"
}
}
},
"gateway": {
"session_id": "27b1aaa2-6972-4224-951e-80cb9c89003a",
"session_id": "7bab4703-706e-4f2e-80b3-c3611832cda8",
"gateway_version": "unknown",
"sequence_number": 1,
"sequence_number": 3,
"prev_claim_hash": "sha256:64a2493a624c547e12a5395a3bb1d23c8b42e077277bb88732ab5c7209da7e56",
"audit_chain": {
"root": "e09e8744127cd61f36bed71052928f793d9be22f4f3e3b498413816e9f8cffc2",
"tip": "f06d8771565a018e3f301cbe59e312cd9684eb2b3c84a7aa2d4a85f45075a6a3",
"length": 5
"root": "17e9e977e3a1307f2a738ef1dedc3a879be3065ef9111b06b3e0644517f59457",
"tip": "bf92310550233e5363441d0cc3b4158480e86e1bee4b958123bb39e7c63be262",
"length": 6
},
"call_summary": {
"tool_calls_total": 3,
"tool_calls_total": 4,
"tool_calls_allowed": 3,
"tool_calls_denied": 0,
"tool_calls_denied": 1,
"tool_calls_faulted": 0,
"tools_invoked": [
"finance.credit_score_lookup",
Expand All @@ -58,9 +59,9 @@
{
"from_domain": "pii",
"to_domain": "internal",
"call_id": "e9608498-ac0b-4c19-ba67-4d9519c72dde",
"call_id": "1fb8e8c8-d709-4fda-914b-81bbd9a872db",
"tool_name": "finance.risk_report_writer",
"sequence_number": 2
"sequence_number": 3
}
],
"edges_represent": "Edges represent temporal adjacency (call order), not data provenance. A -> B means B was called immediately after A within this session."
Expand All @@ -70,12 +71,12 @@
"hash": "sha256:02640e5bd370cd9e2d383f07dcab8d2d3c5e9e0155ee14c56556d6e7645d86ca",
"drift_detected": false
},
"attestation_generated_at": "2026-06-11T00:42:59.157791+00:00",
"attestation_generated_at": "2026-06-11T16:01:05.969369+00:00",
"attestation_validity_seconds": 86400,
"attestation_stale": false,
"catalog_exceptions": [],
"call_log_summary": {
"total_calls": 3,
"total_calls": 4,
"tools_called": [
"finance.document_reader",
"finance.credit_score_lookup",
Expand All @@ -84,5 +85,5 @@
"suspicious_sequences_detected": 0
}
},
"signature": "wBYpJ89EcFwtJsS4kp0wyVZfGIKZMcazJB4aMQ36mIuhAKtsO04v9KIGlCIk5k818u1PyDqP1gI6_aNDTxgkDQ"
"signature": "Eb4r3WoFBeJ86Zz2DNQPnCCLG11BegMgod-Kjyf6J9V3uKElF5ZiY-SJ5MQwj24EboeXFsU1lKf2Z29ui6FbBA"
}
15 changes: 14 additions & 1 deletion healthcare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,20 @@ python healthcare/agent/clinical_decision_agent.py --trigger-hitl

## The Cedar policy

`policy/allow.cedar` — two enforcement rules over a base permit. Annotations on a `forbid` are returned to the caller as structured advice when that rule causes a deny:
`policy/allow.cedar` has no catch-all permit: each EHR tool is explicitly permitted only for the `clinical-decision-support` workflow (declared by the agent via `_cmcp.workflow_id`), and anything else — wrong workflow, missing workflow, unlisted action — is denied by Cedar's default-deny:

```cedar
permit (
principal,
action == Action::"Ehr.patientRecordLookup",
resource
) when {
context has workflow_id &&
context.workflow_id == "clinical-decision-support"
};
```

On top of the workflow-scoped permits sit two forbid rules. Annotations on a `forbid` are returned to the caller as structured advice when that rule causes a deny:

```cedar
@id("hitl-high-risk")
Expand Down
42 changes: 37 additions & 5 deletions healthcare/policy/allow.cedar
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,44 @@
// Action names follow the cMCP convention: tool_name converted to
// PascalCase per underscore segment, e.g. ehr.treatment_plan_writer
// becomes Action::"Ehr.treatmentPlanWriter".
//
// There is deliberately NO catch-all permit: anything not explicitly
// permitted below is denied (Cedar default-deny). The workflow_id is
// supplied by the agent via the _cmcp request metadata; calls without
// it are denied.

// Rule 1: the clinical-decision-support workflow may look up patient records.
permit (
principal,
action == Action::"Ehr.patientRecordLookup",
resource
) when {
context has workflow_id &&
context.workflow_id == "clinical-decision-support"
};

// Rule 2: the clinical-decision-support workflow may run differential diagnosis.
permit (
principal,
action == Action::"Ehr.clinicalDecisionSupport",
resource
) when {
context has workflow_id &&
context.workflow_id == "clinical-decision-support"
};

// Rule 1: the attested catalog is the allow-list; permit catalog tools and
// let the forbid rules below carry the compliance constraints.
permit (principal, action, resource);
// Rule 3: the clinical-decision-support workflow may write treatment plans.
// Subject to the HITL forbid below.
permit (
principal,
action == Action::"Ehr.treatmentPlanWriter",
resource
) when {
context has workflow_id &&
context.workflow_id == "clinical-decision-support"
};

// Rule 2: EU AI Act Article 14 human oversight. Treatment plan writes for
// Rule 4: EU AI Act Article 14 human oversight. Treatment plan writes for
// high-risk patients are blocked until an attending physician approves.
// The annotations are returned to the caller as structured advice on deny.
@id("hitl-high-risk")
Expand All @@ -26,7 +58,7 @@ forbid (
context.arguments.patient_risk_category == "high"
};

// Rule 3: HIPAA PHI may only flow through an attested runtime.
// Rule 5: HIPAA PHI may only flow through an attested runtime.
@id("require-attested-runtime")
@reason("attested-runtime-required")
@regulation("hipaa-164-312")
Expand Down
25 changes: 13 additions & 12 deletions healthcare/trace-output/example-trust-record.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,40 @@
"cmcp_version": "1.0",
"trace": {
"eat_profile": "tag:agentrust.io,2026:trace-v0.1",
"iat": 1781138573,
"subject": "spiffe://cmcp.gateway/session/ec7cb161-d4a6-457d-b7e9-ff4e0f9a64f7",
"iat": 1781193632,
"subject": "spiffe://cmcp.gateway/session/f2574e45-58b2-480f-919d-50aa65d07d31",
"runtime": {
"platform": "tpm2",
"measurement": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"firmware_version": "software-only-dev-mode"
},
"policy": {
"bundle_hash": "sha256:746a35668e06c8b6a6a84aa0a06b603c26b286c3e9ec65b2562e1555077be936",
"bundle_hash": "sha256:bab3158a32ccadfecfd69d5edcc4078fcd1f2a0814c4f33069e352754ed69d9c",
"enforcement_mode": "enforce",
"version": "clinical-hipaa-v2.1"
},
"data_class": "confidential",
"tool_transcript": {
"hash": "sha256:6439db6d0d0b4aaab9fb6fbe81bd26ed75c42aeb4198cc980ce5892725fb27c0",
"hash": "sha256:237bef498e84cd658ca00a7673bb114192a110940ed821b44ae4f8c81cf247f5",
"call_count": 3
},
"cnf": {
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "i8Ia9guwYclt5cUUMRPcB6_EXiXOMN3iv8A0l8-CPQE",
"kid": "cmcp-8bc21af6"
"x": "g2zyVqHtTV9CtQBkS4PS971CMd1ilK0CtihOpuy9v14",
"kid": "cmcp-836cf256"
}
}
},
"gateway": {
"session_id": "ec7cb161-d4a6-457d-b7e9-ff4e0f9a64f7",
"session_id": "f2574e45-58b2-480f-919d-50aa65d07d31",
"gateway_version": "unknown",
"sequence_number": 1,
"sequence_number": 3,
"prev_claim_hash": "sha256:dc612793436ff47741f94d67fa3aebf3d126741990cf48a4a29ec7baf63db9f9",
"audit_chain": {
"root": "b6e5d7adff480101d3a466d6ab051509be22cf377c96447c43b22ca9bf27861b",
"tip": "6439db6d0d0b4aaab9fb6fbe81bd26ed75c42aeb4198cc980ce5892725fb27c0",
"root": "43656adea27dbd3cd2adefbd0364a70172938185cf6bb61397d21c6bcf4b1761",
"tip": "237bef498e84cd658ca00a7673bb114192a110940ed821b44ae4f8c81cf247f5",
"length": 5
},
"call_summary": {
Expand All @@ -60,7 +61,7 @@
"hash": "sha256:0c8009e105049f68d0f7054e254192e43eec7d567cd1fa45542450d268369647",
"drift_detected": false
},
"attestation_generated_at": "2026-06-11T00:42:42.456843+00:00",
"attestation_generated_at": "2026-06-11T16:00:17.584902+00:00",
"attestation_validity_seconds": 86400,
"attestation_stale": false,
"catalog_exceptions": [],
Expand All @@ -74,5 +75,5 @@
"suspicious_sequences_detected": 0
}
},
"signature": "2fHMRi8zIDMqT292wfhJ7RCPu56L5VgdwRw5bJebJ0znqjUYDUR8ctuFkOimwjgJH9ACJ_k-gS7w25Ha7Dr_AQ"
"signature": "49A-mPB9eY6y5396rtf3-2jn8IsCfvhf5OoRWlBLU9m94MUvK9UYZ6n76dDmqt2F9Bo4xYMe_t7ypuLfgqHhDQ"
}
46 changes: 45 additions & 1 deletion startup-tpm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,51 @@ The response is the signed `RuntimeClaim` (see `trace-output/example-trust-recor
You can also export the hash-chained audit log for a closed session:

```bash
curl "http://localhost:8443/audit/export?session_id=<session_id>" | python3 -m json.tool
curl "http://localhost:8443/audit/export?session_id=<session_id>" > bundle.json
```

---

## Step 7 — Verify the record (and try to tamper with it)

Save the claim from Step 6 to `claim.json`, then verify it:

```bash
cmcp verify claim.json --audit-bundle bundle.json
```

```
[cmcp verify] schema PASS
[cmcp verify] signature PASS
[cmcp verify] attestation_freshness PASS
[cmcp verify] audit_chain PASS
[cmcp verify] audit_bundle PASS (3 entries)
[cmcp verify] RESULT: PASS (verified)
```

Now change anything — a counter, a tool name, one character — and verify again:

```bash
python3 -c "
import json
c = json.load(open('claim.json'))
c['gateway']['call_summary']['tool_calls_total'] += 1
json.dump(c, open('claim.json', 'w'))
"
cmcp verify claim.json
```

```
[cmcp verify] signature FAIL
[cmcp verify] RESULT: FAIL (partially_verified)
```

The signature covers the whole canonical claim, so no field can be altered after the fact. The same holds for the audit bundle: editing any entry breaks both its hash chain and the bundle signature. This is the tamper-evidence guarantee in one command.

In production, pin the approved hashes so a substituted policy or catalog also fails:

```bash
cmcp verify claim.json --policy-hash sha256:<approved> --catalog-hash sha256:<approved>
```

---
Expand Down