Skip to main content

Capability-Based Security

OpenFang uses a capability-based security model where every agent operation is subject to explicit permission checks. Capabilities are declared in the agent manifest and enforced at runtime by the CapabilityManager.

Why Capability-Based Security?

Traditional agent frameworks use coarse-grained permissions (“can this agent use tools?”). OpenFang uses fine-grained capabilities where each tool, memory scope, network target, and operation is individually gated.

Benefits

  • Least Privilege: Agents only access resources explicitly granted
  • Privilege Escalation Prevention: Child agents cannot exceed parent capabilities
  • Auditability: Every capability grant is logged to the Merkle audit trail
  • Defense-in-Depth: Works alongside other security layers

Capability Types

Capabilities are defined as an enum in openfang-types/src/capability.rs:
pub enum Capability {
    // Tool access
    ToolInvoke(String),       // e.g., "file_read"
    ToolAll,                  // Access to all tools

    // Memory access
    MemoryRead(String),       // e.g., "*", "self.*"
    MemoryWrite(String),      // e.g., "self.*", "shared.*"

    // Network access
    NetConnect(String),       // e.g., "api.example.com", "*"

    // Agent operations
    AgentSpawn,               // Can spawn new agents
    AgentMessage(String),     // e.g., "coder", "researcher", "*"
    AgentKill(String),        // e.g., "child_agent_name"

    // Shell access
    ShellExec(String),        // e.g., "ls", "git *", "*"

    // OFP networking
    OfpDiscover,              // Can discover remote peers
    OfpConnect(String),       // e.g., "peer_address"
    OfpAdvertise,             // Can advertise to peers
}

Wildcard Patterns

Capabilities support glob-style patterns:
  • "*" — Matches everything
  • "self.*" — Matches anything starting with “self.”
  • "file_*" — Matches file_read, file_write, file_list, etc.

Manifest Declaration

Capabilities are declared in the agent’s manifest.toml:
[capabilities]
tools = ["file_read", "file_list", "web_fetch", "web_search"]
memory_read = ["*"]
memory_write = ["self.*"]
network = ["api.anthropic.com", "api.openai.com"]
shell = []
agent_spawn = false
agent_message = ["coder", "researcher"]
agent_kill = []
ofp_discover = false
ofp_connect = []
ofp_advertise = false

Example: Minimal Agent

An agent that only reads files and stores results in its own memory:
[manifest]
name = "file-reader"
version = "1.0.0"

[capabilities]
tools = ["file_read", "file_list"]
memory_read = ["self.*"]
memory_write = ["self.*"]
network = []  # No network access
shell = []    # No shell access
agent_spawn = false

Example: Research Agent

An agent that searches the web, fetches content, and stores results:
[manifest]
name = "researcher"
version = "1.0.0"

[capabilities]
tools = ["web_search", "web_fetch", "memory_store"]
memory_read = ["*"]
memory_write = ["self.*", "shared.research"]
network = ["*"]  # Needs broad network access for web research
shell = []
agent_spawn = false

Example: Orchestrator Agent

An agent that spawns and coordinates other agents:
[manifest]
name = "orchestrator"
version = "1.0.0"

[capabilities]
tools = ["agent_spawn", "agent_message", "workflow_run"]
memory_read = ["*"]
memory_write = ["self.*"]
network = []  # Doesn't need network, delegates to child agents
shell = []
agent_spawn = true
agent_message = ["*"]
agent_kill = ["*"]

Grant and Revoke Flow

Capabilities are managed by the CapabilityManager in openfang-kernel.

Grant Flow (At Spawn Time)

1. Agent manifest parsed

2. Capabilities extracted from [capabilities] section

3. validate_capability_inheritance() checks parent capabilities

4. If validation passes, grant() called for each capability

5. Capabilities stored in DashMap<AgentId, Vec<Capability>>

6. Grant logged to Merkle audit trail

Revoke Flow (At Kill Time)

1. Agent kill requested

2. CapabilityManager.revoke_all(agent_id)

3. Entry removed from DashMap

4. Revoke logged to audit trail

Runtime Enforcement

Tool invocation request
    |
    v
CapabilityManager.check(agent_id, ToolInvoke("file_read"))
    |
    +-- Granted --> Validate path (traversal check) --> Execute tool
    |
    +-- Denied --> Return "Permission denied" error to LLM

Inheritance Validation

One of the most critical security features: child agents cannot exceed parent capabilities.

How It Works

validate_capability_inheritance() is called at spawn time before any capabilities are granted:
pub fn validate_capability_inheritance(
    parent_caps: &[Capability],
    child_caps: &[Capability],
) -> Result<(), OpenFangError> {
    for child_cap in child_caps {
        if !is_covered_by_parent(child_cap, parent_caps) {
            return Err(OpenFangError::PrivilegeEscalation {
                requested: format!("{:?}", child_cap),
                reason: "Child capability exceeds parent grant",
            });
        }
    }
    Ok(())
}

Example: Valid Inheritance

Parent capabilities:
tools = ["file_*"]  # Wildcard covers all file operations
memory_write = ["shared.*"]
Child capabilities (VALID):
tools = ["file_read", "file_list"]  # Subset of "file_*"
memory_write = ["shared.reports"]   # Subset of "shared.*"

Example: Invalid Inheritance (Blocked)

Parent capabilities:
tools = ["file_read"]
network = []
Child capabilities (INVALID):
tools = ["file_read", "web_fetch"]  # "web_fetch" NOT in parent grant
network = ["*"]                     # Parent has NO network access
Result: OpenFangError::PrivilegeEscalation at spawn time.

Enforcement Points

Capabilities are enforced at multiple points in the execution flow:

1. Tool Invocation

Location: openfang-runtime/src/tool_runner.rs
// Before executing ANY tool
let required_cap = Capability::ToolInvoke(tool_name.clone());
if !capability_manager.check(&agent_id, &required_cap) {
    return Err(OpenFangError::PermissionDenied {
        operation: format!("tool:{}", tool_name),
        required_capability: format!("{:?}", required_cap),
    });
}

2. Tool List Filter

Location: openfang-runtime/src/agent_loop.rs Before the LLM sees the tool list, it’s filtered by capabilities:
let available_tools: Vec<ToolDefinition> = all_tools
    .into_iter()
    .filter(|tool| {
        let cap = Capability::ToolInvoke(tool.name.clone());
        capability_manager.check(&agent_id, &cap)
    })
    .collect();
This prevents the LLM from even attempting to call unauthorized tools.

3. Memory Operations

Location: openfang-memory/src/kv_store.rs
// Before memory_store
let required_cap = Capability::MemoryWrite(key.clone());
if !capability_manager.check(&agent_id, &required_cap) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

// Before memory_recall
let required_cap = Capability::MemoryRead(key.clone());
if !capability_manager.check(&agent_id, &required_cap) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

4. Network Access

Location: openfang-runtime/src/web_fetch.rs
// Before web_fetch
let host = url.host_str().ok_or(/* ... */)?;
let required_cap = Capability::NetConnect(host.to_string());
if !capability_manager.check(&agent_id, &required_cap) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

5. Agent Operations

Location: openfang-kernel/src/agent_registry.rs
// Before agent spawn
if !capability_manager.check(&parent_id, &Capability::AgentSpawn) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

// Before agent message
let required_cap = Capability::AgentMessage(target_name.clone());
if !capability_manager.check(&sender_id, &required_cap) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

// Before agent kill
let required_cap = Capability::AgentKill(target_name.clone());
if !capability_manager.check(&caller_id, &required_cap) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

6. Shell Execution

Location: openfang-runtime/src/tools/bash.rs
// Before bash tool execution
let required_cap = Capability::ShellExec(command.clone());
if !capability_manager.check(&agent_id, &required_cap) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

7. OFP Operations

Location: openfang-wire/src/peer_node.rs
// Before OFP discover
if !capability_manager.check(&agent_id, &Capability::OfpDiscover) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

// Before OFP connect
let required_cap = Capability::OfpConnect(peer_addr.to_string());
if !capability_manager.check(&agent_id, &required_cap) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

// Before OFP advertise
if !capability_manager.check(&agent_id, &Capability::OfpAdvertise) {
    return Err(OpenFangError::PermissionDenied { /* ... */ });
}

Dynamic Capability Updates (Future)

Currently, capabilities are static — they’re set at spawn time and cannot change. A future release will support dynamic capability updates:
// Future API (not yet implemented)
kernel.capability_manager.grant(
    &agent_id,
    Capability::ToolInvoke("new_tool".to_string())
)?;

kernel.capability_manager.revoke(
    &agent_id,
    Capability::NetConnect("untrusted.example.com".to_string())
)?;
This will enable time-based capability grants (“access for 1 hour”) and conditional grants (“only if condition X is met”).

Debugging Capability Issues

Check Granted Capabilities

Query the API to see what capabilities an agent has:
curl http://localhost:4200/api/agents/{agent_id}/capabilities
Response:
{
  "agent_id": "uuid-here",
  "capabilities": [
    {"ToolInvoke": "file_read"},
    {"ToolInvoke": "file_list"},
    {"MemoryRead": "*"},
    {"MemoryWrite": "self.*"},
    {"NetConnect": "api.anthropic.com"}
  ]
}

Check Audit Trail

All capability grants and revokes are logged:
curl http://localhost:4200/api/audit?agent_id={agent_id}&event_type=capability
Response:
[
  {
    "timestamp": "2026-03-07T10:15:30Z",
    "agent_id": "uuid-here",
    "event": "CapabilityGranted",
    "capability": {"ToolInvoke": "file_read"},
    "hash": "sha256_hash_here",
    "prev_hash": "previous_hash_here"
  }
]

Common Errors

Error: “Permission denied: tool:web_fetch”

Cause: Agent does not have ToolInvoke("web_fetch") capability. Fix: Add to manifest:
[capabilities]
tools = ["web_fetch"]
network = ["*"]  # Also required for network access

Error: “Privilege escalation: Child capability exceeds parent grant”

Cause: Child agent requested a capability not held by parent. Fix: Either:
  1. Grant the capability to the parent first
  2. Remove the capability from the child manifest

Error: “Permission denied: memory:shared.secrets”

Cause: Agent does not have MemoryWrite("shared.secrets") capability. Fix: Add to manifest:
[capabilities]
memory_write = ["shared.secrets"]

Best Practices

1. Start with Minimal Capabilities

Grant only what’s required. You can always expand later.
# Start here
[capabilities]
tools = ["file_read"]  # NOT "file_*" or "*"
memory_write = ["self.*"]  # NOT "*"

2. Use Wildcards Carefully

Wildcards are convenient but can grant more than intended:
# Dangerous
tools = ["*"]  # Grants ALL tools

# Better
tools = ["file_*"]  # Grants only file operations

# Best
tools = ["file_read", "file_list"]  # Explicit list

3. Separate Network and Tool Access

A tool capability doesn’t imply network access. Both must be granted:
[capabilities]
tools = ["web_fetch"]  # Allows calling the tool
network = ["api.example.com"]  # Allows connecting to this host

4. Audit Regularly

Review the capability audit trail monthly:
curl http://localhost:4200/api/audit?event_type=capability > capability_audit.json

5. Test Inheritance Before Production

Verify that child agents can spawn successfully in a dev environment before deploying.

Overview

All 16 security systems

Sandbox

WASM and subprocess isolation

Audit Trail

Merkle hash-chain logging

Architecture

How capabilities integrate across subsystems

Build docs developers (and LLMs) love