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.
- A strict
0.2JSON capability format and Rust data model. -
DelegationTokenBuilderfor issuing Ed25519-signed capabilities. -
Evaluatorfor binding a capability to the audience, action, resource, and delegation depth supplied by the host. -
IssuerKeyResolverfor connecting evaluation to explicitly trusted issuer keys. -
TrustStateStorefor connecting evaluation to revocation, agent-deny, and atomic replay state. -
AuditEventandAuditSinkfor 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.
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.
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.
[dependencies]
delegated = "0.2"- The host configures trusted issuer keys through
IssuerKeyResolver. - The issuer signs every capability claim with Ed25519.
- The host supplies
OperationContextfrom the actual route/tool/operation being executed. - Audience, action, resource, and delegation-depth constraints fail closed.
-
TrustStateStoreprovides revocation, agent deny, and atomic nonce consumption. - Trust-store errors deny evaluation;
evaluate_and_auditalso 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.
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.
- Architecture and trust boundaries
- Integration guide
- Operations guide
- Redis trust state (
delegated-redis) - Threat model
- Security review (0.2)
- Wire specification
- Security reporting
Related crates:
-
delegated— capability evaluation core -
delegated-redis— Redis-backedTrustStateStore
- Load issuer keys from trusted configuration or a verified key service.
- Provide durable shared
TrustStateStorestorage (delegated-redisfor Redis). - Build
OperationContextfrom the operation that will actually run. - Persist
AuditEventto a durable, access-controlled sink before executing an allow. - Protect bearer tokens in transit and at rest.
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo test
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
cargo package --offlineThe 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.
MIT OR Apache-2.0
