Aleph
Concepts

Identity Context

Immutable identity snapshot flowing through the execution chain, enabling permission enforcement at the tool level.

Overview

IdentityContext is an immutable identity snapshot that flows through the entire execution chain, enabling identity-based permission enforcement at the tool execution level.

Every request that enters Aleph carries an identity -- whether from the owner, a guest with limited scope, or an unauthenticated caller. This identity is captured once at session creation and becomes read-only for the remainder of the request lifecycle. By making the identity immutable, the system prevents privilege escalation attacks where a caller might attempt to change roles mid-execution.

Identity Context Flow

1. Session Creation
   SessionManager
   ┌────────────────────────────────────────────┐
   │ Owner Session:                              │
   │   metadata = SessionIdentityMeta {          │
   │     role: Owner,                            │
   │     identity_id: "owner",                   │
   │     scope: None                             │
   │   }                                         │
   │                                             │
   │ Guest Session:                              │
   │   metadata = SessionIdentityMeta {          │
   │     role: Guest,                            │
   │     identity_id: "guest-123",               │
   │     scope: Some(GuestScope { ... })         │
   │   }                                         │
   └────────────────────────────────────────────┘


2. Request Processing
   ExecutionEngine
   ┌────────────────────────────────────────────┐
   │ let identity = session_manager              │
   │     .get_identity_context(&session_key)     │
   │     .await?;                                │
   │                                             │
   │ // IdentityContext {                        │
   │ //   request_id, session_key, role,         │
   │ //   identity_id, scope, created_at,        │
   │ //   source_channel                         │
   │ // }                                        │
   └────────────────────────────────────────────┘


3. Harness Execution
   HarnessRunner::run(
       FlowRequest {
           agent_id, input,
           identity,  // IdentityContext passed here
           abort_signal,
       }, ...
   )


4. Tool Execution
   Executor::execute(&action, &identity)
   ┌────────────────────────────────────────────┐
   │ let normalized = normalize_tool_name(...);  │
   │                                             │
   │ let result = PolicyEngine::check_tool_permission(
   │     &identity, &normalized                  │
   │ );                                          │
   │                                             │
   │ match result {                              │
   │     Allowed => execute_tool(...),           │
   │     Denied { reason } => ToolError { ... }  │
   │ }                                           │
   └────────────────────────────────────────────┘


5. Permission Check
   PolicyEngine::check_tool_permission
   ┌────────────────────────────────────────────┐
   │ match identity.role {                       │
   │     Role::Owner => Allowed,                 │
   │     Role::Guest => { check scope },         │
   │     Role::Anonymous => Denied,              │
   │ }                                           │
   └────────────────────────────────────────────┘

Key Components

ComponentLocationResponsibility
IdentityContextshared/protocol/src/auth.rsImmutable identity snapshot
SessionIdentityMetasrc/gateway/session_manager.rsPersistent identity metadata
PolicyEnginesrc/gateway/security/policy_engine.rsStateless permission checker
InvitationManagersrc/gateway/security/invitation_manager.rsGuest invitation lifecycle
SessionManagersrc/gateway/session_manager.rsIdentity construction
Orchestratorsrc/orchestrator/Identity injection into FlowRequest

Role Types

Aleph defines three role tiers that determine what a caller is allowed to do:

RolePermissionsUse Case
OwnerBypass all checksServer owner with full access
GuestScope-limitedTemporary access via invitation
AnonymousDenied unless auth disabledUnauthenticated requests

Owner

The owner role is assigned to the server administrator. Owner sessions skip all permission checks -- the PolicyEngine returns Allowed immediately when role == Owner. This is appropriate because the owner already has full system access through the operating system.

Guest

Guest sessions are created through the invitation system. A guest receives a limited scope defining which tools they can invoke and when their access expires. The PolicyEngine validates all three dimensions: role, scope presence, and tool membership.

Anonymous

Anonymous requests are rejected unless the server is running with authentication disabled (development mode). In production, all requests must carry either owner or guest identity.

GuestScope

When a guest session is created, it carries a GuestScope that bounds what the guest can do:

pub struct GuestScope {
    pub allowed_tools: Vec<String>,  // Tool names the guest may invoke
    pub expires_at: Option<i64>,     // Unix timestamp; None = no expiry
    pub display_name: Option<String>,// Human-readable name for logs
}
FieldTypeDescription
allowed_toolsVec<String>Exact tool names the guest may call
expires_atOption<i64>Unix timestamp after which access is denied
display_nameOption<String>Friendly name shown in audit logs

Guest scope is enforced at the tool execution level, not at the API boundary. A guest can send any request, but the PolicyEngine denies tool calls that are not in allowed_tools.

PolicyEngine

The PolicyEngine is a stateless permission checker. It has no mutable state, no database access, and no side effects. It takes an IdentityContext and a normalized tool name, and returns an Allow or Deny decision.

pub struct PolicyEngine;

impl PolicyEngine {
    pub fn check_tool_permission(
        identity: &IdentityContext,
        tool_name: &str,
    ) -> PermissionResult {
        match identity.role {
            Role::Owner => PermissionResult::Allowed,
            Role::Guest => { /* check scope, expiry, tool list */ }
            Role::Anonymous => PermissionResult::Denied { reason: "auth required" },
        }
    }
}

Because the PolicyEngine is stateless, it can be instantiated anywhere in the call chain without worrying about lifecycle or synchronization. The immutable IdentityContext provides all the data needed to make a decision.

Example: Owner Request

// 1. Owner creates session (default)
let session_key = "owner-session-1";
let metadata = SessionIdentityMeta::owner("gateway");

// 2. Request arrives
let identity = session_manager
    .get_identity_context(session_key, "gateway")
    .await?;
// identity.role = Role::Owner

// 3. Execute tool
let action = Action::ToolCall {
    tool_name: "shell_exec".to_string(),
    arguments: json!({"command": "ls"}),
};

let result = executor.execute(&action, &identity).await;
// Result: ToolSuccess (Owner bypasses all checks)

Example: Guest Request with Invitation

// 1. Owner creates invitation
let invitation = manager.create_invitation(CreateInvitationRequest {
    guest_name: "Alice".to_string(),
    scope: GuestScope {
        allowed_tools: vec!["translate".to_string()],
        expires_at: Some(now + 3600),
        display_name: Some("Alice".to_string()),
    },
})?;

let guest_token = manager.activate_invitation(&invitation.token)?;

// 2. Guest creates session
// SessionManager stores metadata with guest scope

// 3. Request arrives
let identity = session_manager
    .get_identity_context(session_key, "gateway")
    .await?;
// identity.role = Role::Guest
// identity.scope = Some(GuestScope { allowed_tools: ["translate"], ... })

// 4. Execute allowed tool
let action1 = Action::ToolCall {
    tool_name: "translate".to_string(),
    arguments: json!({"text": "Hello"}),
};
let result1 = executor.execute(&action1, &identity).await;
// Result: ToolSuccess (tool in allowed_tools)

// 5. Execute denied tool
let action2 = Action::ToolCall {
    tool_name: "shell_exec".to_string(),
    arguments: json!({"command": "ls"}),
};
let result2 = executor.execute(&action2, &identity).await;
// Result: ToolError { error: "Tool 'shell_exec' not in guest scope" }

See Also

On this page