Living Document Notice
Published 2026-09-18. The evolving architecture, revisions, and connected notes for this dispatch live in the Stax Digital Garden.

Sandboxing Client Extensions with Outrigger

Sandboxing Client Extensions with Outrigger: Stark monochrome P4 paper white vector CRT macro showing isolated hexagonal core protected by concentric defensive perimeter barrier rings

Summary

Community extensions and third-party plugins drive user customization in desktop personal knowledge management tools. However, conventional plugin architectures execute third-party JavaScript directly within the main application execution context. This gives unvetted community scripts unrestricted access to the DOM, local storage databases, filesystem handles, and outbound network sockets, introducing catastrophic attack vectors for data theft and silent file modification.

FreeNext isolates third-party extensions using the Outrigger Protocol execution runtime. By running plugins inside capability-bounded WebAssembly instances, extensions can only interact with local vault data through explicitly declared, user-approved system interfaces.

Capability-Based Sandboxing Architecture

Under Outrigger, a plugin binary possesses zero intrinsic permissions. It cannot execute disk I/O, allocate unbounded host memory, or establish network connections unless the host environment explicitly provides imported capability handles.

+-------------------------------------------------------------+
|                     FreeNext Host Runtime                   |
|         - Master SQLite Database Handle                     |
|         - Full Vault Filesystem Access                      |
+-------------------------------------------------------------+
                               |
                               | Mediated Host Boundaries
                               v
+-------------------------------------------------------------+
|                 Outrigger Capability Broker                 |
|       - Validates Extension Manifest Permissions            |
|       - Injects Scoped Read-Only SQLite Virtual Tables      |
|       - Blocks Outbound Sockets by Default                  |
+-------------------------------------------------------------+
                               |
                               | WASI / Wasmtime Memory Sandbox
                               v
+-------------------------------------------------------------+
|                 Sandboxed WASM Extension                    |
|             (e.g., Graph Renderer, Word Counter)            |
+-------------------------------------------------------------+

If an extension attempts to read paths outside its assigned manifest scope, the WebAssembly runtime traps the execution thread and logs a security exception without endangering host data.

Capability Permission Matrix

Outrigger enforces granular capability boundaries rather than all-or-nothing installation prompts.

Capability IdentifierGranted ScopeDefault StateEnforcement Mechanism
vault:read:metadataNote titles, frontmatter tags, word countsPermittedRead-only SQLite view restricting body text
vault:read:contentFull CommonMark document contentsPrompt RequiredPath-scoped file descriptor handles
vault:write:patchProposing unified diffs to active notePrompt RequiredTransaction staging buffer prior to user review
network:outboundHTTP fetch to external domainsBlockedComplete absence of network syscall host imports
host:clipboardReading system clipboard bufferBlockedExplicit user confirmation per write operation
compute:fuelCPU execution limits per cycle50M Fuel UnitsWebAssembly instruction counting trap

By default, an analytical extension like an alternative graph renderer receives only vault:read:metadata. It cannot exfiltrate private note prose or contact remote telemetry collectors.

Outrigger Host Import Interface

The host runtime exposes a constrained Foreign Function Interface (FFI) to the guest WebAssembly module using standard WebAssembly System Interface (WASI) conventions:

// outrigger_host.rs: Host interface providing capability-gated database access
use wasmtime::*;
 
pub struct OutriggerPluginContext {
    pub extension_id: String,
    pub allowed_paths: Vec<String>,
    pub execution_fuel_remaining: u64,
}
 
pub fn bind_outrigger_host_imports(
    linker: &mut Linker<OutriggerPluginContext>,
) -> Result<(), wasmtime::Error> {
    linker.func_wrap(
        "outrigger_v1",
        "query_note_tags",
        |mut caller: Caller<'_, OutriggerPluginContext>, path_ptr: i32, path_len: i32, out_ptr: i32| -> i32 {
            let memory = caller.get_export("memory").unwrap().into_memory().unwrap();
            let ctx = caller.data();
 
            let mut path_buf = vec![0u8; path_len as usize];
            memory.read(&caller, path_ptr as usize, &mut path_buf).unwrap();
            let requested_path = String::from_utf8_lossy(&path_buf);
 
            if !ctx.allowed_paths.iter().any(|p| requested_path.starts_with(p)) {
                return -1; // Permission Denied code
            }
 
            let tags_json = r#"["dispatch", "architecture"]"#;
            let bytes = tags_json.as_bytes();
            memory.write(&mut caller, out_ptr as usize, bytes).unwrap();
 
            bytes.len() as i32
        },
    )?;
 
    Ok(())
}

Isolating community plugins within capability-governed WebAssembly sandboxes enables modular extensibility while protecting user notes against exfiltration and unauthorized modification.


  • Directus Target: freenext
  • Garden Source Reference: OUT-1001 - Zero Dependency Sandboxing, NXT-1001 - The Application Is Just a Lens, MOC - Data Liberation Workbenches, MOC - The Plain-Text Longevity Standard, MOC - Local-First Systems and Synchronization, MOC - Bosun PKM Tools, MOC - Outrigger Protocol