Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:

if [ -d docs ]; then cp -r docs $BUILD/docs; fi

for fname in index.md CHANGELOG.md CONTRIBUTING.md GOVERNANCE.md CHARTER.md CODE_OF_CONDUCT.md SECURITY.md PRIVACY.md ROADMAP.md LIMITATIONS.md CNAME; do
for fname in index.md CHANGELOG.md CONTRIBUTING.md GOVERNANCE.md CHARTER.md CODE_OF_CONDUCT.md SECURITY.md PRIVACY.md ROADMAP.md LIMITATIONS.md SPONSORS.md CNAME; do
if [ -f "$fname" ]; then cp "$fname" "$BUILD/$fname"; fi
done

Expand Down
49 changes: 35 additions & 14 deletions docs/concepts.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,63 @@
# How It Works

cA2A is a trust profile layered on A2A. It does not move tasks or replace the transport. It adds the trust guarantees A2A's Signed Agent Card leaves out, by composing four primitives.
cA2A layers delegation and peer trust checks onto A2A communication. Start with the [offline chain example](quickstart.md) to see grant validation; the live runtime adds caller authentication, local policy, optional appraisal, and payload handling.

## The gap cA2A closes

A2A's Signed Agent Card answers one question: did the domain owner issue this card. It does not establish that a peer runs attested code, that a delegating agent actually holds the authority it passes on, that the task payload is confidential to the peer, or that there is an unbroken record of who delegated what to whom. See [the threat model](spec/threat-model.md) for the adversary this admits.
A signed identity document does not establish every authority or runtime property needed for a delegated task. The relying party must decide which root issuers, capabilities, measurements, and evidence it accepts. See the [threat model](spec/threat-model.md).

## The four primitives

### 1. Attenuated delegation

Each hop carries a signed delegation credential. The scope granted at a hop must be a provable subset of its parent's scope. Depth is bounded, and a credential cannot be replayed into another chain. This is the hardest primitive to get right, and it is already implemented and tested in [agent-manifest](https://github.com/agentrust-io/agent-manifest); cA2A reuses those semantics. See [delegation chain](spec/delegation-chain.md).
Each child grant is signed by the parent subject and must narrow or preserve the parent's scope. Verification checks trusted roots, signatures, parent links, credential IDs, depth, and validity windows. A valid chain establishes a grant; it does not authenticate the caller currently presenting it. The live path separately checks proof of possession of the leaf key. See [delegation chains](spec/delegation-chain.md).

### 2. Runtime attestation

Before a peer is trusted with a delegated task, it proves it is running attested, measured code. cA2A reuses the pluggable TEE provider abstraction from cmcp: a provider produces an attestation report binding a public key to a hardware measurement. See [attestation](spec/attestation.md).
A provider produces evidence intended to bind a key to a measured runtime. The relying party must verify that evidence and apply its own measurement and assurance requirements. Software evidence does not establish hardware provenance. See [attestation](spec/attestation.md) and [limitations](../LIMITATIONS.md).

### 3. Sealed peer channel

The task payload is sealed to the peer's attested measurement, so it decrypts only inside the peer's verified enclave. A connectivity provider or a peer in another trust domain sees ciphertext. See [sealed channel](spec/sealed-channel.md).
The caller encrypts a payload to the callee's appraised channel key. Hardware isolation depends on the verified provider and key binding. In software mode, this does not protect the key or plaintext from a privileged host. See [sealed channels](spec/sealed-channel.md).

### 4. Provenance record

Each hop emits a TRACE record that references its parent record's hash and the delegation credential id. Across A to B to C this produces a delegation DAG that any verifier can check offline, without trusting an operator. See [the TRACE A2A profile](spec/trace-a2a-profile.md).
Linked TRACE records carry the decision and parent references. Verifiers check signatures and links separately from credential-chain validation. The offline grant example does not execute a task or establish a provenance DAG. See [the TRACE A2A profile](spec/trace-a2a-profile.md).

## How they compose on a peer call

The diagram follows the callee's inbound runtime checks. It shows the accepted path; a failed required check stops processing before payload opening.

Scroll the diagram horizontally on smaller screens. The text below explains the same boundaries.

<div class="at-diagram" role="region" aria-label="cA2A inbound checks; scroll horizontally" tabindex="0" markdown>

```mermaid
flowchart TB
caller[Caller: credential chain and request] --> chain
subgraph callee[Callee cA2A runtime]
chain[Verify chain against trusted roots] --> holder[Verify caller holds leaf key]
holder --> scope[Intersect grant with local policy]
scope --> appraisal[Appraise caller if required or offered]
appraisal --> decision[Enforce capability and create decision record]
decision --> payload[Open sealed payload if present]
decision --> record[Linked decision record]
end
roots[Approved root issuers] --> chain
policy[Local capability policy] --> scope
evidence[Appraisal requirements and evidence] --> appraisal
record --> result[Payload and decision returned to integration]
payload --> result
```
Agent A --(delegation cred, scope S_A)--> Agent B --(scope S_B ⊆ S_A)--> Agent C
```

1. A issues B a child credential with `S_B ⊆ S_A`, signed over the canonical form of the grant.
2. Before B accepts, the cA2A runtime verifies the chain, verifies B's attestation measurement, and intersects `S_B` with B's local Cedar policy.
3. The payload is sealed to B's measurement.
4. B emits a TRACE record linking to A's record.
</div>

Before sending a sealed payload, the caller must obtain and appraise the callee's channel offer. That outbound step and the callee's appraisal of the caller are distinct directions. The runtime box is a process boundary in software mode; verified confidential-computing deployments can add hardware isolation. The surrounding agents and their tools do not automatically move inside it.

The effective authority is the intersection of the delegated scope and local policy. Delegation cannot grant a capability that local policy refuses. Invalid chains or holder proofs are rejected before policy decisions and do not receive signed denial records; authenticated policy and appraisal denials have their own evidence behavior. See the [peer implementation](https://github.com/agentrust-io/ca2a/blob/main/src/ca2a_runtime/peer.py) for the exact ordering and failure paths.

## Profile, not protocol

cA2A binds to A2A the way TRACE binds to IETF RATS, EAT, and SCITT: it is an overlay, not a competitor. This keeps it neutral across org, cloud, and TEE-vendor boundaries, which is the claim a vendor-anchored verifier cannot make.
The profile defines trust fields and checks. The reference HTTP transport makes the peer path runnable, and an A2A SDK bridge is also available. Transport choice does not establish hardware assurance or replace the relying party's trust policy.

The profile mandates no wire protocol. A reference HTTP transport ships (`ca2a_runtime.transport.server`/`client`) so the peer path is runnable off hardware, but it is a convenience, not part of the profile: any A2A server can carry the extension fields instead.
Continue with [configuration](configuration.md), the [profile](spec/profile.md), or the [limitations](../LIMITATIONS.md).
137 changes: 92 additions & 45 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,125 @@
# Quick Start
# Verify your first delegation chain

This walkthrough builds a delegation chain and verifies it offline. It needs no hardware TEE and no network. It exercises the part of cA2A that is built today: attenuated delegation and offline chain verification.
Build a two-hop grant: a root gives an intermediary `read` and `write`, and the intermediary gives the leaf only `read`. Verify it, then reject an untrusted root and a correctly signed grant that exceeds its parent's authority. After installation, this example needs no network, hardware, or running peer.

## Install

```bash
pip install ca2a-runtime
```

Or run the published rootless container with a read-only configuration mount:
Use Python 3.11+, Git, and Bash on Linux, macOS, or Windows with WSL. Install from the current source checkout so the example uses the verification behavior documented here.

```bash
docker run --rm -p 8443:8443 \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=16m \
-v "$PWD/ca2a-config.yaml:/etc/ca2a/config.yaml:ro" \
ghcr.io/agentrust-io/ca2a-runtime:v0.2.0 \
start --config /etc/ca2a/config.yaml
git clone https://github.com/agentrust-io/ca2a.git ca2a-quickstart
cd ca2a-quickstart
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
```

The image runs as UID/GID 10001. Hardware-backed providers additionally need
the relevant device passed through with permissions for that identity; do not
run the whole container as root to obtain device access.
## Build an example chain

cA2A 0.2 is published as a normal release. Contributors working from a checkout can instead install from source: `pip install -e ".[dev]"`.
Save this complete block as `first_chain.py`, then run `python first_chain.py`:

## Build an example chain
```python
import json
import time
from pathlib import Path

The repo ships a generator that produces a valid three-hop chain (`admin` narrows to `read+write` narrows to `read`):
from ca2a_runtime.delegation import DelegationCredential, new_keypair, verify_chain
from ca2a_runtime.errors import CA2AError

```bash
python scripts/gen_example_chain.py
# wrote examples/minimal/chain.json
root_priv, root_pub = new_keypair()
mid_priv, mid_pub = new_keypair()
_, leaf_pub = new_keypair()
now = int(time.time())
root = DelegationCredential(
"demo-root", root_pub, mid_pub, frozenset({"cap:read", "cap:write"}), 0,
not_before=now - 5, not_after=now + 3600,
).sign(root_priv)
child = DelegationCredential(
"demo-child", mid_pub, leaf_pub, frozenset({"cap:read"}), 1,
parent_id="demo-root", not_before=now - 5, not_after=now + 1800,
).sign(mid_priv)

# Retain the approved issuer separately from the chain being inspected.
trusted_roots = {root_pub}
verify_chain([root, child], trusted_root_issuers=trusted_roots)
print("PASS: two-hop chain verified; leaf scope is cap:read")

try:
verify_chain([root, child], trusted_root_issuers=set())
except CA2AError as exc:
assert exc.code == "UNTRUSTED_DELEGATION_ROOT", exc.code
print("PASS: untrusted root rejected")
else:
raise AssertionError("An untrusted root was accepted")

# Sign the invalid grant so this tests authorization, not edited signature bytes.
escalated = DelegationCredential(
"demo-escalated", mid_pub, leaf_pub, frozenset({"cap:admin"}), 1,
parent_id="demo-root", not_before=now - 5, not_after=now + 1800,
).sign(mid_priv)
try:
verify_chain([root, escalated], trusted_root_issuers=trusted_roots)
except CA2AError as exc:
assert exc.code == "SCOPE_ESCALATION", exc.code
print("PASS: signed scope escalation rejected")
else:
raise AssertionError("An escalated grant was accepted")

Path("demo-chain.json").write_text(json.dumps({
"chain": [item.body() | {"signature": item.signature} for item in [root, child]]
}, indent=2))
Path("trusted-root.txt").write_text(root_pub)
print("Saved demo-chain.json and trusted-root.txt; private keys were not saved")
```

Each hop is a signed `DelegationCredential`. The scope of each hop is a subset of its parent, continuity is preserved (each issuer is the previous subject), and each hop links to its parent by `credential_id`.
Expected output:

```text
PASS: two-hop chain verified; leaf scope is cap:read
PASS: untrusted root rejected
PASS: signed scope escalation rejected
Saved demo-chain.json and trusted-root.txt; private keys were not saved
```

## Verify it

Use the separately retained issuer key:

```bash
ca2a verify-chain --chain examples/minimal/chain.json --trusted-root-issuer <trusted-root-issuer-hex>
# {"verified": true, "hops": 3, "leaf_scope": ["cap:read"]}
ca2a verify-chain --chain demo-chain.json --trusted-root-issuer "$(cat trusted-root.txt)"
```

Verification checks four invariants and fails on the first violation:
Expected exit code: `0`.

1. **Signature** on every hop against the issuer's Ed25519 public key.
2. **Continuity**: each hop's issuer is the previous hop's subject.
3. **Attenuation**: each hop's scope is a subset of its parent's scope.
4. **Anti-replay**: `parent_id` links to the previous `credential_id` and every `credential_id` is unique.
```json
{"verified": true, "hops": 2, "leaf_scope": ["cap:read"]}
```

In production, the relying party obtains trusted roots through its own approval process. Copying the issuer from an arbitrary incoming chain into the trust list would let that chain choose its own authority.

## Try to break it

Edit `examples/minimal/chain.json` so a child hop adds a capability its parent did not hold, then re-run:
The script signs an overbroad child credential and expects `SCOPE_ESCALATION`. Editing a signed JSON field instead should fail the signature check first. These are different checks; an invalid signature does not demonstrate scope attenuation.

```bash
ca2a verify-chain --chain examples/minimal/chain.json --trusted-root-issuer <trusted-root-issuer-hex>
# {"verified": false, "code": "SCOPE_ESCALATION", "error": "hop 1 scope exceeds parent grant"}
```
The verifier also checks issuer-to-subject continuity, parent links, unique credential IDs, depth, and validity windows. These offline checks do not keep a global ledger of previously accepted requests. Live request replay handling is a separate runtime concern.

## Build a chain in code

```python
from ca2a_runtime.delegation import DelegationCredential, new_keypair, verify_chain
The complete script above uses the same `DelegationCredential` and `verify_chain` APIs as the runtime. `trusted_root_issuers` is required for a successful verification. Keep the root grant and each child grant bounded by your own authorization policy.

root_priv, root_pub = new_keypair()
mid_priv, mid_pub = new_keypair()
_, leaf_pub = new_keypair()
## What is not in this walkthrough

root = DelegationCredential("c0", root_pub, mid_pub, frozenset({"cap:a", "cap:b"}), 0).sign(root_priv)
child = DelegationCredential("c1", mid_pub, leaf_pub, frozenset({"cap:a"}), 1, parent_id="c0").sign(mid_priv)
This example verifies signed grants. It does not execute tasks, appraise hardware, seal a network payload, or verify a TRACE provenance DAG. Follow [How It Works](concepts.md) for the live peer path and [Limitations](../LIMITATIONS.md) for the evidence that has been demonstrated.

verify_chain([root, child]) # raises on any violation
```
## Troubleshooting

## What is not in this walkthrough
- **Module not found:** activate `.venv` in the terminal running the example.
- **File not found:** run `first_chain.py` from the same directory as the CLI command.
- **Untrusted root:** use this demo's retained key. Do not trust keys solely because an incoming chain supplies them.
- **Expired credential:** the child lasts 30 minutes. Rerun the script to generate a fresh demo chain.

## Next steps

The runtime peer path accepts a delegation credential on a live inbound A2A call, appraises the peer, seals the payload, enforces local policy, and emits signed provenance. The walkthrough defaults to software assurance; see [ROADMAP.md](../ROADMAP.md) and [LIMITATIONS.md](../LIMITATIONS.md) before making hardware-backed claims.
- [Architecture](concepts.md): trust checks and runtime boundaries.
- [Delegation specification](spec/delegation-chain.md): validation rules.
- [Limitations](../LIMITATIONS.md): software and hardware assurance boundaries.
Loading