astrid-capabilities 0.3.0

Capability token system for Astrid secure agent runtime
Documentation

astrid-capabilities

License: MIT OR Apache-2.0 MSRV: 1.94

The agent cannot forge the token. It cannot replay an old one. It cannot talk its way past the check.

In the OS model, capability tokens are the kernel's access control mechanism. An agent that wants to call mcp://filesystem:read_file needs a signed token granting Permission::Invoke on a matching resource pattern. No token, no access. The token is Ed25519-signed by the runtime key, linked to the approval audit entry that created it, optionally time-bounded, and scoped to session or persistent storage. The agent has no way to mint, modify, or extend them.

How tokens work

When a human approves an action with "Allow Always", the security interceptor (astrid-approval) calls into this crate to mint a CapabilityToken. The token is signed, stored, and returned as proof. On subsequent calls, the interceptor finds the token and skips the approval prompt.

ResourcePattern uses compiled glob matching via globset. Patterns like mcp://filesystem:* match any tool on that server. file:///home/user/** matches any file under that directory. Exact URIs skip glob compilation for speed. Path traversal (..) is rejected at pattern creation and again at match time. Defense in depth.

Dual-tier storage. Session tokens live in memory via RwLock<HashMap>. Persistent tokens survive restarts via SurrealKV. Both tiers share revocation and single-use tracking. Revocation persists to KV before updating in-memory state, so a crash between the two cannot resurrect a revoked token.

Replay protection. Single-use tokens are marked consumed atomically under a write lock. mark_used checks, persists, and inserts in one critical section to prevent TOCTOU races. State survives process restart via KV.

Tamper detection on read. Persistent tokens are re-validated (expiry + signature) on every get(), find_capability(), and has_capability() call. A token tampered on disk fails signature verification and is silently skipped. Session tokens skip this check because they were validated at add() time and live in trusted memory.

Clock-skew tolerance. Configurable window (default 30 seconds) via validate_with_skew. A token that expired 10 seconds ago still passes with the default tolerance.

Resource pattern examples

Pattern Matches
mcp://filesystem:read_file Exactly that one tool
mcp://filesystem:* Any tool on the filesystem server
mcp://*:read_* Any read_ tool on any server
file:///home/user/** Any file under /home/user

Usage

[dependencies]
astrid-capabilities = { workspace = true }
use astrid_capabilities::{
    CapabilityToken, CapabilityStore, ResourcePattern, TokenScope, AuditEntryId,
};
use astrid_core::Permission;
use astrid_crypto::KeyPair;

let runtime_key = KeyPair::generate();
let pattern = ResourcePattern::new("mcp://filesystem:*").unwrap();

let token = CapabilityToken::create(
    pattern,
    vec![Permission::Invoke],
    TokenScope::Session,
    runtime_key.key_id(),
    AuditEntryId::new(),
    &runtime_key,
    None, // no TTL
);

let store = CapabilityStore::in_memory();
store.add(token).unwrap();
assert!(store.has_capability("mcp://filesystem:read_file", Permission::Invoke));

This crate also defines DirHandle and FileHandle, the opaque UUID-based handles used by astrid-vfs. You cannot construct a path to a directory you have not been granted.

Development

cargo test -p astrid-capabilities

License

Dual MIT/Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.