# delegated

`delegated` is a small Rust library for evaluating issuer-signed bearer capability tokens.

[![crates.io](https://img.shields.io/crates/v/delegated.svg)](https://crates.io/crates/delegated)
[![docs.rs](https://img.shields.io/docsrs/delegated)](https://docs.rs/delegated)
[![CI](https://github.com/abendrothj/delegated/actions/workflows/ci.yml/badge.svg)](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)
- [Redis trust state (`delegated-redis`)](crates/delegated-redis/OPERATIONS.md)
- [Threat model](docs/THREAT_MODEL.md)
- [Security review (0.2)](docs/SECURITY_REVIEW.md)
- [Wire specification](SPEC.md)
- [Security reporting](SECURITY.md)

Related crates:

- [`delegated`](https://docs.rs/delegated) — capability evaluation core
- [`delegated-redis`](https://docs.rs/delegated-redis) — Redis-backed `TrustStateStore`

## Required deployment work

1. Load issuer keys from trusted configuration or a verified key service.
2. Provide durable shared `TrustStateStore` storage ([`delegated-redis`](crates/delegated-redis/OPERATIONS.md) for Redis).
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
