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
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,38 @@ jobs:
with:
fail_ci_if_error: false

governance:
runs-on: ubuntu-latest
needs: test

steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Install dependencies
run: python -m pip install --upgrade pip setuptools && pip install -e ".[dev]" agent-compliance

- name: Generate evidence file
run: python scripts/gen_agt_evidence.py

- name: AGT governance verify (strict)
run: agt verify --evidence agt-evidence.json

- name: Save attestation JSON
run: agt --json verify --evidence agt-evidence.json > agt-attestation.json

- name: Upload governance artifacts
uses: actions/upload-artifact@v7
with:
name: agt-governance-${{ github.sha }}
path: |
agt-evidence.json
agt-attestation.json
if-no-files-found: warn

benchmark:
runs-on: ubuntu-latest
needs: test
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,34 @@ jobs:
path: dist/

- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0

governance-release:
needs: publish
runs-on: ubuntu-latest
permissions:
contents: write # upload release assets

steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"

- name: Install package and AGT
run: python -m pip install --upgrade pip && pip install -e "." agent-governance-toolkit-core

- name: Generate evidence file
run: python scripts/gen_agt_evidence.py

- name: AGT governance verify (strict)
run: agt verify --evidence agt-evidence.json

- name: Save attestation JSON
run: agt --json verify --evidence agt-evidence.json > agt-attestation.json

- name: Attach evidence to release
if: github.event_name == 'release'
run: gh release upload ${{ github.ref_name }} agt-evidence.json agt-attestation.json
env:
GITHUB_TOKEN: ${{ github.token }}
26 changes: 26 additions & 0 deletions governance/cmcp-enforcement.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Default-deny governance policy descriptor for the cMCP gateway.
# This YAML document is consumed by `agt verify --evidence` to establish that
# cMCP enforces deny-by-default semantics at the MCP tool-call boundary.
deny_by_default: true
default_action: deny

name: cmcp-gateway-governance
version: "1.0"
description: >
cMCP enforces default-deny Cedar policy on all MCP tool calls at the gateway
boundary inside a TEE. The Cedar policy bundle is measured into hardware at
startup — any tool call not matching an explicit permit rule is denied without
any code path the operator can override at runtime.

enforcement_model: cedar
enforcement_point: tee-gateway
hardware_roots:
- amd-sev-snp
- intel-tdx
- tpm2

owasp_coverage:
ASI-01: PromptInjectionDetector — blocks injected instructions in tool responses
ASI-02: catalog + Cedar permit rules — enforces approved tool list
ASI-03: Cedar scope boundary — agent cannot call tools outside explicit permit
ASI-06: hash-chained audit inside TEE — tamper-evident call log per session
94 changes: 94 additions & 0 deletions scripts/gen_agt_evidence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Generate agt-evidence.json describing cMCP's governance state.

Run from the repo root:
python scripts/gen_agt_evidence.py [output-path]

The output path defaults to agt-evidence.json in the current directory.
Policy file paths in the evidence are relative to the output file so that
`agt verify --evidence agt-evidence.json` can locate them.
"""

from __future__ import annotations

import importlib.metadata
import json
import sys
from datetime import datetime, timezone
from pathlib import Path

REPO_ROOT = Path(__file__).parent.parent


def _pkg_version(package: str) -> str:
try:
return importlib.metadata.version(package)
except importlib.metadata.PackageNotFoundError:
return "not-installed"


def generate_evidence() -> dict:
"""Return the evidence dict describing this cMCP deployment's governance state.

All policy file paths are relative to the evidence file location (repo root)
so that ``agt verify --evidence agt-evidence.json`` resolves them correctly
in any working directory.
"""
catalog_path = REPO_ROOT / "examples" / "bfsi-demo" / "catalog.json"
try:
catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
registered_tools = [
entry["tool_name"] for entry in catalog if isinstance(entry, dict) and "tool_name" in entry
]
except (FileNotFoundError, json.JSONDecodeError, KeyError):
registered_tools = []

return {
"schema": "agt-runtime-evidence/v1",
"generated_at": "", # populated by main()
"toolkit_version": _pkg_version("agent-governance-toolkit-core"),
"deployment": {
# Relative to this evidence file (repo root).
# governance/cmcp-enforcement.yaml has deny_by_default: true.
"policy_files_loaded": [
"governance/cmcp-enforcement.yaml",
],
"registered_tools": registered_tools,
"audit_sink": {
"enabled": True,
"target": "src/cmcp_runtime/audit/chain.py",
"type": "tee-hash-chained",
},
"identity": {
"enabled": True,
"type": "spiffe",
"backend": "agent_os",
},
"packages": [
{
"package": "cmcp-runtime",
"version": _pkg_version("cmcp-runtime"),
},
{
"package": "agent-governance-toolkit-core",
"version": _pkg_version("agent-governance-toolkit-core"),
},
{
"package": "agentrust-trace",
"version": _pkg_version("agentrust-trace"),
},
],
},
}


def main(out_path: str = "agt-evidence.json") -> None:
evidence = generate_evidence()
evidence["generated_at"] = datetime.now(timezone.utc).isoformat()
path = Path(out_path)
path.write_text(json.dumps(evidence, indent=2), encoding="utf-8")
print(f"Generated {path} ({path.stat().st_size} bytes)")


if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "agt-evidence.json")
Loading
Loading