CLP — Capability Loop Protocol
A capability has three stages in its life, and today each one lives in a different ecosystem. It is advertised in agent cards, ANS entries, MCP schemas and ERC-8004 records. It is granted as an OAuth token, a biscuit, a UCAN. It is exercised into a log, an OTel span, a transparency ledger. None of those artifacts reference each other.
The consequence is that a routine audit question has no answer:
Was this action within what was granted, and was the grant within what was advertised?
CLP answers it. One canonical capability, a decidable containment relation over it, and a set of checks that verify the whole chain offline — across formats, with no registry and no trusted third party.
The problem it addresses
In multi-agent systems, instructions injected into a sub-agent propagate upward through the delegation chain and execute with the authority of the most privileged agent in the pipeline. The attack surface grows multiplicatively with pipeline depth.
Prompt injection cannot be reliably eliminated inside current model architectures. CLP does not try. It takes the other half of the problem — an agent's authority — and makes it cryptographically bounded, so that a compromised agent cannot exceed what it holds, and the attempt becomes signed evidence.
$ python examples/demo.py
2. INJECTED: worker tries to send mail
verdict: REFUSED
[FAIL] ACTION_WITHIN_GRANT action exceeds the granted authority
verbs: ['mail.send']
5. ENFORCED: the guard runs the checks BEFORE calling the tool
refused: verbs not covered
emails actually sent: 0
The last line is the distinction that matters. A check that runs after the message is sent is an audit record, not a control.
Install
pip install zanii-atn # Python
npm install @zanii-atn/atn # Node
The distribution is named for the project; the module is named for the protocol:
import clp # after pip install zanii-atn
import { verifyLoop } from "@zanii-atn/atn";
Neither package has runtime dependencies. Ed25519, RFC 8785 canonicalization and base58btc are implemented in-tree so that verification carries no supply chain of its own.
Running the suite
From a clone, with nothing installed:
python tests/run_interop.py # cross-language conformance suite, 18 checks
python examples/demo.py # end-to-end injection scenario
Requires Python 3.10+ and Node 16+.
Scope
CLP is not a capability-token format. Attenuated tokens are a mature and actively standardising
area — macaroons, biscuit, UCAN, and draft-niyikiza-oauth-attenuating-agent-tokens at the
IETF. Those systems guarantee attenuation syntactically: every delegation hop appends
restrictions. None of them decides semantically whether one capability is contained in
another, and none does it across formats.
That is the gap CLP fills, which makes each of those formats an input rather than a competitor.
Verification checks
| Check | Question |
|---|---|
GRANT_WITHIN_AD |
Did the provider grant only what it advertised? |
CHAIN_MONOTONIC |
Does every delegation hop narrow, back to a root authority? |
ACTION_WITHIN_GRANT |
Did the action stay inside its authority? |
PROVENANCE_CLEAN |
Did untrusted input drive an action that forbids it? |
Every refusal identifies the dimension that failed and the offending values. A refusal that cannot explain itself is not usable as audit evidence.
Obligations
Controls a grant may carry. Each is opt-in and enforced before the action executes.
| Obligation | Effect |
|---|---|
provenance:trusted-only |
Every input label must begin trusted:. This is an allowlist, so unlabelled and unrecognised-prefix inputs are refused. |
provenance:registered-only |
Those labels must appear in an owner-signed registry, so mislabelling requires the owner's key rather than a code change. |
freshness:<seconds> |
The verifier must hold a signed checkpoint no older than the window. An unprovable revocation view is refused rather than silently accepted. |
review:human |
Requires an approval signed by a registered approver and bound to that exact action. |
jurisdiction:<code> |
The holder asserts operation within the named jurisdiction. |
Repository layout
spec/clp-spec.md protocol specification
spec/containment.md the algebra: lattice semantics, completeness proof, decidability
spec/adapters.md normative mappings from A2A, OAuth 2.1 and Zanii artifacts
spec/threat-model.md attacker profiles, mitigations, and remaining exposure
impl/python/clp/ reference implementation, standard library only
impl/js/clp.mjs reference implementation, node:crypto only
tests/ conformance suite, property tests, golden and adversarial vectors
examples/demo.py end-to-end injection scenario
docs/ rendered specification and plan, project assets
tools/ documentation pipeline and pre-publish secret scanner
archive/ the retired ATN draft; see archive/README.md
Design of the constraint language
Biscuit expresses caveats in Datalog, where deciding whether one policy implies another is intractable. Macaroons provide no comparison operation at all. CLP restricts its constraint language to finite prefix globs, integer ceilings, a single interval and a finite label set, because a restricted language admits a total and inexpensive containment decision — and containment is the only operation the protocol requires.
spec/containment.md contains the completeness proof for pattern-set containment, together
with an account of what the restriction cannot express (data-dependent conditions, disjunctive
obligations, relative bounds) and why each belongs at a different layer.
Conformance
Given identical seeds, the Python and JavaScript implementations emit byte-identical vector files: the same signatures, hashes, containment decisions, verdicts and enforcement outcomes. Ed25519 is deterministic and RFC 8785 is total, so any divergence is a test failure.
This is a security requirement rather than a stylistic one. If two implementations disagreed about containment, or enforced differently, an attacker would route through the permissive one.
The suite also verifies that adversarial cases are refused at the correct check, that the containment relation is reflexive, antisymmetric and transitive in both languages, that no adapter widens authority during translation, and that a fixed enforcement sequence produces identical artifacts in both implementations.
Relationship to the Zanii platform
CLP composes with the Zanii ledger rather than duplicating it. It
uses the same did:key identities, the same RFC 8785 canonicalization and the same
sha256:<hex> encoding, so a Zanii delegation certificate maps onto a CLP grant and a Zanii
receipt onto a CLP exercise.
The ledger records what happened. CLP constrains what may happen, at the point of delegation. Anchoring a refusal turns an attempted escalation into permanent, timestamped evidence.
Known limitations
Documented in full in spec/threat-model.md §4a.
- An owner who signs a trust registry naming an unsafe origin, or who grants broadly without
review:human, defeats the corresponding control. These decisions are attributable to a key and a date, but they are not preventable. - Freshness enforcement requires a revocation feed to exist. CLP makes its absence fail closed; it cannot make it unnecessary.
- Refusal telemetry identifies the behavioural signature of an agent acting outside its authority. It does not inspect prompts.
- The pure-Python Ed25519 implementation is reference-grade: correct, not constant-time, and
roughly 30 ms per operation. Production signers should use a libsodium-backed library. The
JavaScript implementation uses OpenSSL through
node:cryptoand has neither limitation.
Security
Report vulnerabilities to info@zanii.agency. See SECURITY.md.
Before committing, pushing or sharing an archive of this directory:
python tools/scan_secrets.py
Project history
This repository began as ATN, a broader agent-trust protocol. A review against the existing
Zanii ledger showed that ATN reimplemented approximately 85% of functionality already shipped
across 60 packages, and did several parts less well. That work is retained under archive/
with the analysis, because the decision to narrow the scope to the capability loop came
directly from identifying the one gap the ledger does not fill.
License
Apache-2.0. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zanii_atn-0.1.0.tar.gz.
File metadata
- Download URL: zanii_atn-0.1.0.tar.gz
- Upload date:
- Size: 89.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34597e739c9d965fa5752af96c3e0c2f1a1dcc056b90e59c93241cac223ab530
|
|
| MD5 |
0d462d6f8f783faa3927461e6acf42d5
|
|
| BLAKE2b-256 |
33411a3ddff92851aa588028ec948a76834c156785fd92b1bff7635acb05ffa1
|
File details
Details for the file zanii_atn-0.1.0-py3-none-any.whl.
File metadata
- Download URL: zanii_atn-0.1.0-py3-none-any.whl
- Upload date:
- Size: 50.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f8e235b8f2c4e765c055cc2d68b512f3a68600c1040e63bf7cb72e0b40388a68
|
|
| MD5 |
adecc8952f306122686ed6c9c0975f0f
|
|
| BLAKE2b-256 |
26d58dc892b676c633e10b8d4a5e558625366d9fb960edea5932d94da75d678b
|