# delegated
`delegated` is a small Rust library for evaluating issuer-signed bearer capability tokens.
[](https://crates.io/crates/delegated)
[](https://docs.rs/delegated)
[](https://github.com/abendrothj/delegated/actions/workflows/ci.yml)
It answers one question: does a token issued by a key this service explicitly trusts authorize
the operation this service is about to execute?
It does not discover issuers, authenticate users, validate OIDC or SPIFFE identities, terminate
TLS, operate a control plane, or turn caller-supplied request metadata into trusted context.
## What the crate provides
- A strict `0.2` JSON capability format and Rust data model.
- `DelegationTokenBuilder` for issuing Ed25519-signed capabilities.
- `Evaluator` for binding a capability to the audience, action, resource, and delegation depth
supplied by the host.
- `IssuerKeyResolver` for connecting evaluation to explicitly trusted issuer keys.
- `TrustStateStore` for connecting evaluation to revocation, agent-deny, and atomic replay state.
- `AuditEvent` and `AuditSink` for recording allow and deny decisions.
- In-memory key, state, and audit implementations for tests and single-process use.
The crate provides an authorization decision primitive. It does not provide an HTTP server,
middleware, database, identity provider, multi-hop delegation protocol, or proof that the bearer
is the agent named in the token.
## When it is useful
Use `delegated` when one component is allowed to mint narrowly scoped, short-lived authority that
another component must verify locally and consistently. Typical examples include an agent gateway
authorizing a tool call, a workflow service granting one downstream action, or an internal control
plane issuing a single-use operational capability.
Do not use it as a replacement for user authentication, OAuth/OIDC login, workload identity, or a
general policy language. The issuer remains responsible for deciding whether delegation should be
granted.
## Project status
Version 0.2 is a pre-1.0 security primitive with a deliberately small synchronous API. The core
evaluation path is tested and fail-closed, but the crate has not undergone an independent security
audit. Production use requires host-provided durable state, audit persistence, key management, and
careful operation mapping. Treat it as integration-ready for controlled deployments, not a turnkey
authorization platform.
## Install
```toml
[dependencies]
delegated = "0.2"
```
## Security model
- The host configures trusted issuer keys through `IssuerKeyResolver`.
- The issuer signs every capability claim with Ed25519.
- The host supplies `OperationContext` from the actual route/tool/operation being executed.
- Audience, action, resource, and delegation-depth constraints fail closed.
- `TrustStateStore` provides revocation, agent deny, and atomic nonce consumption.
- Trust-store errors deny evaluation; `evaluate_and_audit` also prevents an allow from being
returned when audit persistence fails.
Tokens are bearer credentials. Anyone possessing a valid token can use it until it expires,
is revoked, or its nonce is consumed. Use short lifetimes and transport security.
## Example
```rust
use chrono::{Duration, Utc};
use ed25519_dalek::SigningKey;
use delegated::{
DelegationTokenBuilder, Evaluator, InMemoryTrustState, OperationContext,
PinnedIssuerKeys, envelope,
};
let issuer_key = SigningKey::from_bytes(&[7; 32]);
let trusted_keys = PinnedIssuerKeys::new();
trusted_keys.insert(
"https://issuer.example",
"issuer-2026-01",
issuer_key.verifying_key(),
)?;
let now = Utc::now();
let token = DelegationTokenBuilder::new()
.token_id("token-123")
.issuer("https://issuer.example")
.agent_id("agent:scheduler")
.delegator_id("user:alice")
.audience("calendar-api")
.allowed_action("calendar.create")
.allowed_resource("calendar:alice")
.max_delegation_depth(0)
.issued_at(now)
.expires_at(now + Duration::minutes(10))
.nonce("random-128-bit-value")
.key_id("issuer-2026-01")
.build_and_sign(&issuer_key)?;
let raw = serde_json::to_vec(&envelope(token, Some("request-123".into())))?;
// Construct this from the handler/route and validated target, never from `raw`.
let operation = OperationContext::new("calendar-api", "calendar.create")
.with_resource("calendar:alice")
.with_delegation_depth(0);
let state = InMemoryTrustState::new();
let (decision, audit_event) =
Evaluator::new(&trusted_keys, &state).evaluate(&raw, &operation, now);
if decision.allowed {
// Persist audit_event, then execute exactly `operation`.
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
`InMemoryTrustState` is a reference implementation for tests and single-process services.
Distributed deployments must provide shared storage with atomic `consume_nonce` behavior.
## Documentation
- [Architecture and trust boundaries](docs/ARCHITECTURE.md)
- [Integration guide](docs/INTEGRATION.md)
- [Operations guide](docs/OPERATIONS.md)
- [Threat model](docs/THREAT_MODEL.md)
- [Security review (0.2)](docs/SECURITY_REVIEW.md)
- [Wire specification](SPEC.md)
- [Security reporting](SECURITY.md)
## Required deployment work
1. Load issuer keys from trusted configuration or a verified key service.
2. Implement durable shared `TrustStateStore` storage.
3. Build `OperationContext` from the operation that will actually run.
4. Persist `AuditEvent` to a durable, access-controlled sink before executing an allow.
5. Protect bearer tokens in transit and at rest.
## Checks
```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
cargo package --offline
```
The wire contract is documented in [SPEC.md](SPEC.md). The project is pre-1.0 and the `0.2`
wire format is intentionally incompatible with the earlier experimental format.
## License
MIT OR Apache-2.0