How TinyBus Admits Loadable Native Modules in OpenHuman: The tinydocs and tinyvoice Admission Pipeline

TinyBus admits loadable native modules through a five-stage admission pipeline that includes registry declaration, SHA-256 checksum verification, dlopen-based attachment, and contract-based interface binding, ensuring only authenticated artifacts from the static registry are loaded into the process.

OpenHuman's Rust core extends its capabilities at runtime by loading native cdylib modules such as tinydocs (document generation) and tinyvoice (speech-to-text) through the TinyBus module system. Unlike traditional linked libraries, these modules are discovered, verified, and attached dynamically via a strictly controlled admission pipeline. The TinyBus module system ensures that only cryptographically verified artifacts matching the host's target triple are admitted into the process address space.

The TinyBus Module Admission Pipeline

The admission of loadable modules follows a rigorous five-stage pipeline defined in src/openhuman/modules/registry.rs and src/openhuman/modules/ops.rs. Each stage implements specific security and binding guarantees.

Stage 1: Registry Declaration

TinyBus maintains a static registry table in src/openhuman/modules/registry.rs (lines 10-30) that explicitly lists every module the system may load. Each registry entry contains three critical fields:

  • Bus name: The unique identifier (e.g., "ai.tinyhumans.tinydocs" or "ai.tinyhumans.tinyvoice")
  • Object path: The logical path used to locate the module artifact
  • SHA-256 digest: The cryptographic hash of the release artifact for integrity verification

This registry acts as a whitelist. If a module is not declared in this static table, TinyBus will refuse to load it regardless of the file's presence on the filesystem.

Stage 2: Artifact Selection and Verification

When the core requests a module (e.g., via the modules.list RPC), TinyBus performs cryptographic verification before loading:

  1. Asset selection: TinyBus fetches the release's checksum.toml and selects a matching asset
  2. Tag validation: The system verifies that the URL points to a tagged artifact; branch URLs are explicitly rejected
  3. Digest validation: The downloaded file's SHA-256 hash must match the digest stored in the registry entry. If the digest differs, TinyBus rejects the download immediately

This verification ensures that only the exact artifact published by Tiny Humans can be loaded, preventing substitution attacks.

Stage 3: Process-Wide Bus Attachment

After passing verification, the module is attached to the process through src/openhuman/modules/ops.rs:

  • Broker creation: TinyBus creates a tinybus::broker::Broker instance that will own the module's bus
  • Dynamic loading: The library is loaded into the process using dlopen
  • Address space sharing: The module inherits the same address space, privileges, and crash domain as the core Rust process

Critically, as noted in ops.rs (lines 7-9), TinyBus never unloads a library. Once loaded, the module remains attached for the process lifetime. Any failure—whether refusal, fault, or missing artifacts—is cached permanently, eliminating the "unload-and-reuse-after-exploit" attack surface.

Stage 4: Interface Binding

Each loadable module ships with a companion contract crate (e.g., tinydocs-bus or tinyvoice-bus) located in the vendor/ directory. These contracts define:

  • The TinyBus object name
  • Method signatures for RPC calls
  • Version compatibility information

After loading, the core uses the contract's tinybus::Connection to obtain a proxy object (tinybus::Proxy) that implements the module's RPC interface. For example, calls to GenerateDocument or TranscribeAudio are routed through this proxy, which marshals arguments across the bus boundary while maintaining type safety.

Stage 5: Error Classification

Errors originating from loadable modules are wrapped in tinybus::Error types. The core maps these to domain-specific error types—such as VoiceCallError for tinyvoice or equivalent document errors for tinydocs—so the rest of OpenHuman can handle failures uniformly. The classify function in src/openhuman/modules/voice.rs (lines 491-503) demonstrates this mapping for the voice module.

How tinydocs and tinyvoice Navigate the Pipeline

Both modules follow the identical admission machinery but bind to different bus names and expose distinct capabilities.

tinydocs Module Flow

  • Bus name: "ai.tinyhumans.tinydocs"
  • Contract crate: tinydocs-bus located in vendor/tinydocs
  • Key method: GenerateDocx
  • Admission path: When the core needs to generate a document, it calls modules::documents::attested_proxy, which coordinates the registry lookup, verification, and dlopen sequence, returning a tinybus::Proxy ready to invoke GenerateDocx

tinyvoice Module Flow

  • Bus name: "ai.tinyhumans.tinyvoice"
  • Contract crate: tinyvoice-bus
  • Key methods: TranscribeAudio and GenerateSpeech
  • Admission path: The core obtains a proxy via modules::voice::attested_proxy. The proxy creation logic in src/openhuman/modules/voice.rs (lines 491-505) handles the broker connection and proxy instantiation

Because TinyBus creates the broker once per process, subsequent calls to either module reuse the same in-process bus, eliminating repeated load overhead while maintaining isolation boundaries.

Security Guarantees of the TinyBus Module System

The admission pipeline implements three critical security controls:

Digest-Based Authenticity

The SHA-256 digest stored in src/openhuman/modules/registry.rs guarantees that only the exact artifact published by Tiny Humans can be loaded. Any modification to the binary—whether malicious or accidental—results in a digest mismatch and immediate rejection.

Target-Triple Validation

Before loading, src/openhuman/modules/platform.rs validates that the binary matches the host's target triple (architecture and platform). This prevents accidental loading of incompatible builds (e.g., macOS binaries on Linux hosts) that could cause undefined behavior.

Never-Unload Policy

Once a module passes verification and attaches via dlopen, it remains mapped for the process lifetime. This design eliminates the classic attack surface where an exploit might trigger an unload, modify the library on disk, and force a reload of compromised code.

Implementing Module Calls in Practice

The following Rust example demonstrates loading the tinydocs module and invoking document generation:

use openhuman::modules::{self, Config};
use tinybus::Proxy;

async fn generate_document(input_markdown: &str, output_path: &str) -> Result<(), modules::Error> {
    // Load configuration containing the modules section
    let config = Config::load().await?;
    
    // Obtain an attested proxy for the documents module
    // This performs registry lookup, checksum verification, and dlopen if needed
    let docx_proxy: Proxy = modules::documents::attested_proxy(&config).await?;
    
    // Invoke the method defined in the tinydocs-bus contract
    let result = docx_proxy
        .call("GenerateDocx", (input_markdown, output_path))
        .await?;
    
    // Convert TinyBus errors into OpenHuman-specific error types
    tinybus::Result::from(result)
        .map_err(modules::documents::classify)
}

To use tinyvoice instead, substitute modules::documents with modules::voice and call methods like TranscribeAudio using the same proxy pattern.

Summary

  • TinyBus uses a static registry in src/openhuman/modules/registry.rs to whitelist allowable modules and their SHA-256 digests
  • Cryptographic verification against checksum.toml and target-triple checks in platform.rs ensure artifact integrity before dlopen
  • Contract crates (tinydocs-bus, tinyvoice-bus) define type-safe RPC interfaces via tinybus::Proxy objects
  • The never-unload policy prevents runtime exploitation by caching failures and prohibiting library unmapping
  • Modules like tinydocs and tinyvoice share identical admission machinery but bind to unique bus names (ai.tinyhumans.tinydocs, ai.tinyhumans.tinyvoice)

Frequently Asked Questions

How does TinyBus verify module authenticity before loading?

TinyBus verifies authenticity through a two-step process defined in src/openhuman/modules/registry.rs. First, it checks that the module exists in the static registry table. Second, it downloads the artifact from the tagged release URL and validates that its SHA-256 digest matches the hash stored in the registry. If either check fails, the module is rejected before dlopen is ever called.

Can loadable native modules be unloaded dynamically in OpenHuman?

No. According to the source code in src/openhuman/modules/ops.rs, TinyBus implements a never-unload policy. Once a module is loaded via dlopen, it remains attached to the process for its entire lifetime. This design decision eliminates security vulnerabilities related to runtime library substitution and ensures consistent behavior after initial load.

What happens if a module's SHA-256 checksum doesn't match the registry entry?

If the downloaded artifact's SHA-256 digest differs from the value stored in src/openhuman/modules/registry.rs, TinyBus immediately rejects the download and refuses to load the module. The error is cached for the process lifetime, preventing retry loops. The system will not fall back to untrusted sources or proceed with an unverified binary.

How do I add a new loadable module to the TinyBus system?

Adding a module requires three steps: First, create the native cdylib library and a corresponding contract crate defining the TinyBus interface (object name and methods). Second, add an entry to the static registry in src/openhuman/modules/registry.rs containing the bus name, object path, and expected SHA-256 digest. Third, implement an attested_proxy function in a new file under src/openhuman/modules/ (following the pattern in voice.rs or documents.rs) that handles the proxy creation and error classification for your specific domain.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →