delegated 0.2.2

Minimal fail-closed capability-token evaluation core
Documentation

delegated

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

crates.io docs.rs CI

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

[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

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

Related crates:

Required deployment work

  1. Load issuer keys from trusted configuration or a verified key service.
  2. Provide durable shared TrustStateStore storage (delegated-redis 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

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. 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