Enforcing Local-Only Inference with OpenHuman Privacy Mode: A Complete Security Guide
OpenHuman implements a dual-layer security architecture that guarantees local-only inference by blocking external network requests at the egress layer and validating model providers at the factory level when PrivacyMode is set to local_only.
The tinyhumansai/openhuman repository provides a comprehensive privacy framework that ensures data never leaves the local device when enforcing local-only inference with OpenHuman privacy mode. This system combines runtime policy enforcement with instantiation-time provider validation to guarantee that sensitive workloads remain completely isolated from cloud services.
Understanding OpenHuman Privacy Modes
OpenHuman defines three distinct privacy levels in src/openhuman/config/schema/privacy.rs that control how user data may leave the device:
standard— Permits fully-featured cloud providers for maximum capabilitysensitive— Allows external calls but subjects them to stricter policy checkslocal_only— Blocks all external network traffic, restricting inference to local-only models
When local_only is active, the system guarantees complete data sovereignty by preventing any outbound inference requests through two complementary enforcement mechanisms.
Dual-Layer Enforcement Architecture
The implementation enforces local-only restrictions at two critical chokepoints to ensure no external request can slip through.
Security Egress Layer
Before any outbound request is transmitted, the egress enforcement code in src/openhuman/security/egress/enforce.rs validates the current privacy mode. If the mode is LocalOnly, the system aborts the request with a specific error message: "Local-only privacy mode is active: this action needs external service…"
This layer also intercepts generic HTTP tools. In src/openhuman/tools/impl/network/curl.rs, the implementation logs blocked attempts via tracing::debug!(target: "[curl]", …) before terminating the connection attempt.
Inference Provider Factory
When a model provider is instantiated, the factory in src/openhuman/inference/provider/factory.rs executes the enforce_local_only_inference helper function. This validation occurs before any provider initialization, ensuring cloud services like OpenAI or Azure are rejected immediately when local-only mode is active.
If an incompatible provider is requested, the factory returns an anyhow::Error containing the provider label and instructions to switch to a local model. This error propagates through the RPC layer in src/openhuman/config/ops/privacy.rs for display in the user interface.
Implementing Local-Only Mode in Practice
Configuring Privacy Mode via RPC
The frontend interacts with the privacy system through RPC calls defined in src/openhuman/config/ops/privacy.rs. The UI component at app/src/components/settings/panels/PrivacyModeSection.tsx uses these endpoints to persist user preferences:
// Switch to the most restrictive mode
await coreRpcClient.call('privacy.mode.save', { mode: 'local_only' });
// Verify the mode was saved
const { mode } = await coreRpcClient.call('privacy.mode.read');
console.log('Current mode:', mode); // → "local_only"
Errors from the backend—such as attempts to use cloud providers in local-only mode—are logged via console.warn('[privacy-mode] …') for debugging purposes.
Enforcing Local-Only in the Provider Factory
The core validation logic resides in src/openhuman/inference/provider/factory.rs. The enforce_local_only_inference function checks the active privacy mode against the requested provider before instantiation:
/// Called by the inference provider factory before creating a model.
fn enforce_local_only_inference(role: &str, provider: &str) -> anyhow::Result<()> {
// Load the current privacy mode from the live policy
let privacy = SecurityLivePolicy::current().privacy_mode();
// Reject any cloud provider when privacy is set to LocalOnly
if privacy == PrivacyMode::LocalOnly && provider != "local" {
anyhow::bail!(
"Local-only privacy mode is active: this action needs external provider {}. \
Switch to a local model (Ollama/LM Studio/etc.) or change privacy mode in Settings.",
provider
);
}
Ok(())
}
This function draws the current policy from SecurityLivePolicy in src/openhuman/security/live_policy.rs, which maintains the privacy mode state across autonomy-only reloads.
Blocking Outbound Network Requests
For general network tools like curl, the egress enforcer provides a generic guard that intercepts all outbound HTTP traffic:
/// Generic entry point for outbound HTTP requests (e.g., curl tool)
pub async fn send_request(req: Request) -> Result<Response, anyhow::Error> {
// Early abort if privacy mode blocks external traffic
if SecurityLivePolicy::current().privacy_mode() == PrivacyMode::LocalOnly {
tracing::debug!(
target: "[curl]",
host = %req.host(),
"blocked: local-only privacy mode"
);
anyhow::bail!("Local-only privacy mode is active: outbound request denied");
}
// Normal request handling…
}
This implementation in src/openhuman/tools/impl/network/curl.rs ensures that even direct network tool invocations respect the local-only restriction.
Configuration Persistence and Testing
Live Policy Propagation
The SecurityLivePolicy struct in src/openhuman/security/live_policy.rs manages the runtime state of the privacy configuration. When users change settings via privacy.mode.save, the policy reloads immediately while preserving the mode state across autonomy-only reloads, ensuring continuous protection without requiring a full application restart.
Test Coverage
The enforcement mechanism is validated through comprehensive test suites in src/openhuman/security/egress/enforce_tests.rs and src/openhuman/inference/provider/factory_tests.rs. These tests assert that error messages consistently contain the phrase "Local-only privacy mode is active", verifying that the guard remains functional across code changes.
Summary
Enforcing local-only inference with OpenHuman privacy mode relies on a robust dual-layer architecture:
- Three privacy levels are defined in
src/openhuman/config/schema/privacy.rs:standard,sensitive, andlocal_only - Egress enforcement in
src/openhuman/security/egress/enforce.rsblocks all outbound network requests when local-only mode is active - Provider validation in
src/openhuman/inference/provider/factory.rsensures only local models (Ollama, LM Studio) can be instantiated - Live policy management in
src/openhuman/security/live_policy.rsmaintains privacy state across reloads - RPC operations in
src/openhuman/config/ops/privacy.rsexpose the functionality to the frontend while surfacing actionable error messages
Frequently Asked Questions
What happens when I try to use a cloud AI provider in local-only mode?
When PrivacyMode::LocalOnly is active and you attempt to initialize a cloud provider like OpenAI or Azure, the factory in src/openhuman/inference/provider/factory.rs rejects the request before any network connection is established. The system returns an anyhow::Error with the message "Local-only privacy mode is active: this action needs external provider [name]" and suggests switching to a local model such as Ollama or LM Studio.
How does OpenHuman prevent network leaks when local-only mode is enabled?
OpenHuman implements two independent safeguards: the Security Egress Layer in src/openhuman/security/egress/enforce.rs intercepts all outbound HTTP requests and aborts them if the privacy mode is LocalOnly, while the Inference Provider Factory validates that only local providers can be instantiated. This dual-layer approach ensures that even if one check were bypassed, the other would still block external data transmission.
Can I switch privacy modes dynamically without restarting the application?
Yes. The SecurityLivePolicy in src/openhuman/security/live_policy.rs supports hot-reloading of privacy settings via the privacy.mode.save RPC call. Changes take effect immediately for subsequent inference requests and network operations, though existing active connections may need to be reestablished to reflect the new policy.
Which UI components control the privacy mode settings?
The privacy mode interface is implemented in app/src/components/settings/panels/PrivacyModeSection.tsx, which communicates with the core through the privacy.mode.read and privacy.mode.save RPC methods defined in src/openhuman/config/ops/privacy.rs. This component displays the current mode and handles backend validation errors by logging them to the browser console with the [privacy-mode] prefix.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →