delegated 0.2.0

Minimal fail-closed capability-token evaluation core
Documentation

delegated

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

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.

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

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.

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

cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test
cargo package --offline

The wire contract is documented in 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