Capability System

NØNOS Capability System

Version 0.8.0 | March 2026

NØNOS uses a capability-based security model instead of traditional user/group permissions. Every privileged operation requires holding the appropriate capability. This document explains how capabilities work, what they control, and how they're managed.

What Is a Capability?

A capability is a cryptographic token that grants permission to perform specific actions. Think of it like a signed permission slip that says "this process is allowed to do X."

Unlike traditional Unix permissions (which check "who are you?"), capabilities check "what are you holding?" A process either has the token or it doesn't—there's no ambient authority based on user ID.

Why Capabilities?

Traditional permission systems have problems:

TraditionalCapability-Based
Root can do anythingMust hold specific capability
Ambient authority (UID 0)Explicit token required
Confused deputy attacksProcess knows exactly what's allowed
All-or-nothing escalationFine-grained permission

With capabilities:

  • A process that handles network traffic can be denied filesystem access
  • A cryptographic service can be denied network access
  • Compromise of one capability doesn't grant all privileges

Capability Types

NØNOS defines 17 capability types, each controlling a specific domain of operations.

File System Capabilities

CapabilityBitDescription
FILE_READ0Read file contents
FILE_WRITE1Write to files
FILE_EXECUTE2Execute files as programs
FILE_CREATE3Create new files
FILE_DELETE4Delete files

Usage Examples:

  • A text editor needs FILE_READ, FILE_WRITE, FILE_CREATE
  • A log viewer needs only FILE_READ
  • A cleanup utility needs FILE_READ, FILE_DELETE

Network Capabilities

CapabilityBitDescription
NET_ACCESS5Basic network access
NET_BIND6Bind to a port
NET_LISTEN7Listen for incoming connections
NET_RAW8Raw socket access

Usage Examples:

  • A web browser needs NET_ACCESS
  • A web server needs NET_ACCESS, NET_BIND, NET_LISTEN
  • A packet sniffer needs NET_RAW

Process Capabilities

CapabilityBitDescription
PROC_SPAWN9Create child processes
PROC_KILL10Terminate other processes
PROC_DEBUG11Debug/ptrace other processes

Usage Examples:

  • A shell needs PROC_SPAWN to run commands
  • A task manager needs PROC_KILL
  • A debugger needs PROC_DEBUG

System Capabilities

CapabilityBitDescription
SYS_SHUTDOWN12Shut down the system
SYS_REBOOT13Reboot the system
SYS_CONFIG14Modify system configuration

Usage Examples:

  • The Settings app needs SYS_CONFIG
  • Power management needs SYS_SHUTDOWN, SYS_REBOOT

Cryptographic Capabilities

CapabilityBitDescription
CRYPTO_SIGN15Access signing keys
CRYPTO_ENCRYPT16Perform encryption operations

Usage Examples:

  • A code signing tool needs CRYPTO_SIGN
  • An encrypted file handler needs CRYPTO_ENCRYPT

Capability Tokens

Capabilities are represented as cryptographically signed tokens.

Token Structure

FieldSizeDescription
Module ID8 bytesWho created this token
Capability Bits8 bytesWhich capabilities granted
Expiration8 bytesWhen token expires (milliseconds)
Nonce32 bytesUnique random value
Signature64 bytesEd25519 signature

Total Size: 120 bytes per token

Token Properties

Non-Forgeable: The Ed25519 signature prevents token creation without the signing key. Only the kernel can mint valid tokens.

Time-Limited: Tokens expire after a set duration (default: 24 hours). Expired tokens are rejected.

Non-Replayable: The random nonce ensures each token is unique. The kernel tracks used nonces to prevent replay.

Delegatable: Token holders can create child tokens with a subset of their capabilities (not more).

Token Operations

Token Creation

When a process is created, it receives a capability token from its parent:

// Kernel-side token creation
let token = CapabilityToken {
    module_id: parent.module_id,
    capabilities: requested & parent.capabilities,  // Cannot exceed parent's
    expiration: now + DEFAULT_EXPIRATION,
    nonce: random_bytes(32),
    signature: sign(token_data),
};

Token Verification

Every system call checks capabilities before proceeding:

fn syscall_open(path: &str, flags: u32) -> Result<Fd> {
    let caps = current_process().capability_bits;

    if flags.contains(O_RDONLY) && !caps.contains(FILE_READ) {
        return Err(EPERM);
    }
    if flags.contains(O_WRONLY) && !caps.contains(FILE_WRITE) {
        return Err(EPERM);
    }
    if flags.contains(O_CREAT) && !caps.contains(FILE_CREATE) {
        return Err(EPERM);
    }

    // Proceed with open...
}

Token Delegation

A process can delegate capabilities to another process:

// Create a child token with reduced capabilities
let child_token = parent_token.delegate([FILE_READ, NET_ACCESS]);

Rules:

  • Can only delegate capabilities you hold
  • Cannot add capabilities
  • Cannot extend expiration
  • Child tokens are signed by the kernel, not the delegator

Token Revocation

Tokens can be revoked before expiration:

// Kernel revokes all tokens for a module
kernel.revoke_tokens(module_id);

// Or revoke specific token by nonce
kernel.revoke_token(token_nonce);

Revocation takes effect immediately. The kernel maintains a revocation list checked on every operation.

Process Capabilities

At Process Creation

New processes receive capabilities through several mechanisms:

MethodCapability Source
InheritanceSubset of parent's capabilities
Module ManifestCapabilities declared in signed module
Explicit GrantParent grants specific capabilities
System DefaultMinimal capabilities for basic operation

Default Capabilities

By default, new processes receive:

CapabilityGranted
FILE_READNo
FILE_WRITENo
NET_ACCESSNo
PROC_SPAWNNo
...No

Everything is denied by default. Capabilities must be explicitly granted.

Capability Checking

The kernel checks capabilities atomically—either all required capabilities are present, or the operation fails entirely.

# Check capabilities from shell
capabilities

System Call Requirements

Each system call has specific capability requirements:

File Operations

System CallRequired Capabilities
read()FILE_READ
write()FILE_WRITE
open(O_RDONLY)FILE_READ
open(O_WRONLY)FILE_WRITE
open(O_CREAT)FILE_CREATE
unlink()FILE_DELETE
chmod()FILE_WRITE
execve()FILE_EXECUTE

Network Operations

System CallRequired Capabilities
socket()NET_ACCESS
connect()NET_ACCESS
bind()NET_BIND
listen()NET_LISTEN
accept()NET_LISTEN
send()NET_ACCESS
recv()NET_ACCESS
raw socketNET_RAW

Process Operations

System CallRequired Capabilities
fork()PROC_SPAWN
clone()PROC_SPAWN
kill()PROC_KILL
ptrace()PROC_DEBUG
execve()PROC_SPAWN

System Operations

System CallRequired Capabilities
shutdown()SYS_SHUTDOWN
reboot()SYS_REBOOT
mount()SYS_CONFIG
module_load()SYS_CONFIG

Cryptographic Operations

System CallRequired Capabilities
crypto_sign()CRYPTO_SIGN
crypto_verify()None (verification is public)
crypto_encrypt()CRYPTO_ENCRYPT
crypto_decrypt()CRYPTO_ENCRYPT

Capability in Practice

Example: Web Browser

A web browser needs:

CapabilityWhy
FILE_READRead cached files, bookmarks
FILE_WRITEWrite downloads, cache
FILE_CREATECreate download files
NET_ACCESSConnect to websites

It does not need:

  • NET_BIND (it's a client, not a server)
  • NET_LISTEN (it's not accepting connections)
  • PROC_KILL (it shouldn't kill other processes)
  • SYS_SHUTDOWN (it shouldn't shut down the system)

If the browser is compromised, the attacker cannot:

  • Listen for incoming connections (no backdoor server)
  • Kill other processes
  • Modify system configuration
  • Access signing keys

Example: Password Manager

A password manager needs:

CapabilityWhy
FILE_READRead encrypted vault
FILE_WRITEUpdate vault
CRYPTO_ENCRYPTEncrypt/decrypt vault

It does not need:

  • NET_ACCESS (if offline-only)
  • PROC_SPAWN (no need to run other programs)
  • FILE_EXECUTE (just data, no executables)

Example: Shell

The shell needs broad capabilities:

CapabilityWhy
FILE_READRead scripts, display files
FILE_WRITEOutput redirection
FILE_CREATECreate files
FILE_EXECUTERun programs
PROC_SPAWNExecute commands
NET_ACCESSNetwork commands

This is why the shell is a high-privilege component. Users should be careful what they run.

Capability Audit Trail

All capability operations are logged:

[AUDIT] 2026-03-04 14:23:15 GRANT module=shell caps=FILE_READ|FILE_WRITE
[AUDIT] 2026-03-04 14:23:16 CHECK module=editor caps=FILE_READ result=ALLOW
[AUDIT] 2026-03-04 14:23:17 CHECK module=editor caps=NET_ACCESS result=DENY
[AUDIT] 2026-03-04 14:23:18 REVOKE module=editor reason=EXPIRED

The audit trail provides:

  • Who requested what capability
  • Whether it was granted or denied
  • When capabilities expired or were revoked
  • Chain of delegation

Capability API

Checking Capabilities

// Check if current process has capability
if kernel.has_capability(Capability::NetAccess) {
    // Proceed with network operation
}

// Check multiple capabilities
if kernel.has_all_capabilities([Capability::FileRead, Capability::FileWrite]) {
    // Proceed
}

Dropping Capabilities

A process can voluntarily drop capabilities:

// Drop network access (cannot be regained)
kernel.drop_capability(Capability::NetAccess);

Once dropped, a capability cannot be regained without creating a new process.

Querying Capabilities

// Get current capability set
let caps = kernel.get_capabilities();

// Check specific capability
let can_sign = caps.contains(Capability::CryptoSign);

Module Capabilities

Loaded modules declare capabilities in their manifest:

ModuleManifest {
    name: "network_monitor",
    requested_capabilities: [
        Capability::NetAccess,
        Capability::NetRaw,
    ],
    ...
}

The kernel validates:

  1. Requested capabilities are appropriate for module type
  2. Module signature is valid
  3. No capability escalation beyond policy

Module Types and Allowed Capabilities

Module TypeMaximum Capabilities
UserFILE_*, limited NET
ServiceMost capabilities
DriverFull hardware access
SystemAll capabilities

User modules cannot request SYS_CONFIG, PROC_DEBUG, or other dangerous capabilities.

Security Properties

Principle of Least Privilege

Each process receives only the capabilities it needs:

  • Reduces attack surface
  • Limits blast radius of compromise
  • Makes privilege escalation harder

No Ambient Authority

Unlike traditional Unix:

  • Being "root" doesn't grant all permissions
  • Must hold specific capability token
  • Capabilities are explicit, not implicit

Unforgeable Tokens

Ed25519 signatures prevent:

  • Token creation by non-kernel code
  • Token modification after creation
  • Token transfer (except through delegation)

Timeboxed Permissions

Token expiration means:

  • Stolen tokens become useless after expiration
  • Long-running processes must refresh tokens
  • Abandoned tokens automatically clean up

Common Patterns

Temporary Elevation

Sometimes a process needs temporary elevated capabilities:

// Request elevated token for specific operation
let elevated = kernel.request_elevation([Capability::SysConfig]);
// Use elevated capabilities
kernel.modify_config(...);
// Token automatically expires

Capability Brokering

A trusted broker can grant capabilities:

// Broker verifies request and grants capability
if broker.verify_request(requestor) {
    broker.delegate_capability(requestor, [Capability::NetAccess]);
}

Sandbox Patterns

Create highly restricted processes:

// Spawn with minimal capabilities
let sandbox = spawn_with_capabilities([], []);  // Empty capability set

// Sandbox can compute but not access files or network

Troubleshooting

Permission Denied Errors

If you see EPERM:

  1. Check current capabilities: capabilities
  2. Identify required capability for operation
  3. Verify capability in process chain
  4. Check token expiration

Capability Not Working

If a capability seems ignored:

  1. Verify token signature (shouldn't fail silently)
  2. Check expiration time
  3. Look for revocation in audit log
  4. Verify capability bit is actually set

Debugging Capability Issues

# Show current capabilities
capabilities

# Check audit log
audit | grep DENY

# Verbose capability info
capabilities -v

AGPL-3.0 | Copyright 2026 NØNOS Contributors