# Enforcing Local-Only Inference with OpenHuman Privacy Mode: A Complete Security Guide

> Secure your data with OpenHuman privacy mode enforcing local-only inference. Learn how to block external network requests and validate models for complete data security.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: security
- Published: 2026-08-29

---

**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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/schema/privacy.rs) that control how user data may leave the device:

- **`standard`** — Permits fully-featured cloud providers for maximum capability
- **`sensitive`** — Allows external calls but subjects them to stricter policy checks  
- **`local_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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/ops/privacy.rs). The UI component at [`app/src/components/settings/panels/PrivacyModeSection.tsx`](https://github.com/tinyhumansai/openhuman/blob/main/app/src/components/settings/panels/PrivacyModeSection.tsx) uses these endpoints to persist user preferences:

```typescript
// 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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/inference/provider/factory.rs). The `enforce_local_only_inference` function checks the active privacy mode against the requested provider before instantiation:

```rust
/// 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`](https://github.com/tinyhumansai/openhuman/blob/main/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:

```rust
/// 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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/egress/enforce_tests.rs) and [`src/openhuman/inference/provider/factory_tests.rs`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/schema/privacy.rs): `standard`, `sensitive`, and `local_only`
- **Egress enforcement** in [`src/openhuman/security/egress/enforce.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/egress/enforce.rs) blocks all outbound network requests when local-only mode is active
- **Provider validation** in [`src/openhuman/inference/provider/factory.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/inference/provider/factory.rs) ensures only local models (Ollama, LM Studio) can be instantiated
- **Live policy management** in [`src/openhuman/security/live_policy.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/security/live_policy.rs) maintains privacy state across reloads
- **RPC operations** in [`src/openhuman/config/ops/privacy.rs`](https://github.com/tinyhumansai/openhuman/blob/main/src/openhuman/config/ops/privacy.rs) expose 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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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.