Aleph
Concepts

Capability System

Plugin capability declarations and tool permission middleware for controlling what extensions can do.

The capability and permission modules control what plugins and agents are allowed to do. The capability system declares what a plugin contributes (tools, channels, skills), while the permission system enforces access control at runtime.

Design Philosophy

  1. Declarative capabilities — Plugins declare what they provide via CapabilityDeclaration
  2. Layered permissions — Global defaults + per-agent overrides, most-restrictive-wins
  3. Middleware enforcement — Permission checks happen in the tool middleware layer before execution

Capability Declarations

Plugins declare their contributions via a unified enum:

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CapabilityDeclaration {
    Tool(ToolDeclaration),
    Hook(HookDeclaration),
    Channel(ChannelDeclaration),
    Provider(ProviderDeclaration),
    GatewayMethod(GatewayMethodDeclaration),
    HttpRoute(HttpRouteDeclaration),
    Cli(CliDeclaration),
    Service(ServiceDeclaration),
    Command(CommandDeclaration),
    Skill(SkillDeclaration),
    Agent(AgentDeclaration),
    McpServer(McpServerDeclaration),
}

Each variant maps 1:1 to a registration type in the PluginRegistry. The #[serde(tag = "type")] attribute serializes as { "type": "tool", "name": "my_tool", ... }.


Permission System

Classification

Tool invocations are classified into three outcomes:

pub enum Classification {
    Allow,                       // Execute without prompting
    Confirm { reason: String },  // Ask user for approval
    Deny { reason: String },     // Reject unconditionally
}

SmartFilter

pub trait SmartFilter: Send + Sync {
    fn classify(&self,
        name: &str,
        input: &Value,
    ) -> Classification;
}

LayeredPermissionResolver

Merges global + per-agent permissions using most-restrictive-wins semantics:

pub struct LayeredPermissionResolver {
    merged: Arc<ArcSwap<ToolPermissionsConfig>>,
}

Merge rules: Deny > Ask > Allow

Exec-class tools (code_exec, bash) have special handling — their approval is owned by WorkspaceSandbox, not the general permission layer.

Live reload: Uses ArcSwap so permission updates from config RPC handlers take effect without restarting.

ApprovalGate

For Confirm classifications, the ApprovalGate prompts the user:

#[async_trait]
pub trait Approver: Send + Sync {
    async fn ask(
        &self,
        tool_name: &str,
        reason: &str,
    ) -> ApprovalOutcome;
}

The real ApprovalGate implements this; a mock impl enables unit testing without full approval UI plumbing.


Permission Config

Permissions are configured in providers.toml or via the config RPC:

[permissions.tools]
code_exec = "ask"
bash = "deny"
file_ops = "allow"

Actions:

  • allow — Execute without confirmation
  • ask — Prompt user before executing
  • deny — Block execution

Safety Properties

  • Deny-first evaluation — All patterns checked for Deny before any Ask prompts
  • Deterministic ordering — Config rules are sorted by key for reproducible evaluation
  • No recursion overflow — Pattern matching uses bounded recursion (patterns come from config, not user input)

Code Location

  • src/extension/capability.rsCapabilityDeclaration enum
  • src/tools/middleware/permission/mod.rs — Permission layer
  • src/tools/middleware/permission/resolver.rsLayeredPermissionResolver
  • src/tools/middleware/permission/agent_filter.rs — Per-agent filtering
  • src/config/types/policies/tool_permissions.rs — Config types

See Also

On this page