How OpenHuman Enforces Privacy Mode at the Rust Core Level
OpenHuman enforces privacy mode through a global, thread-safe RwLock<PrivacyMode> inside LivePolicy that is consulted by every egress component before allowing network traffic, with support for hot-reloading via RPC without restarting the core.
The OpenHuman project (tinyhumansai/openhuman) implements a layered security architecture where privacy preferences are enforced at the Rust core level rather than at the application boundary. This design ensures that sensitive data never leaves the local machine when privacy mode is activated, regardless of which high-level tool or UI component initiates the request.
Configuration to SecurityPolicy Mapping
Privacy enforcement begins at startup when the core parses the user-editable config.toml. The [privacy] section contains a mode field that determines the initial security posture.
In src/openhuman/security/policy/types.rs, the SecurityPolicy struct stores this value as privacy_mode: PrivacyMode. The SecurityPolicy::from_config constructor reads config.privacy.mode and maps it to the appropriate enum variant (typically PrivacyMode::Standard or PrivacyMode::LocalOnly). This static representation is then promoted to a live, mutable policy.
Live Policy Wrapper and Hot-Reload Mechanism
Once constructed, the static SecurityPolicy is wrapped by live_policy::LivePolicy, which holds a RwLock<PrivacyMode>. During initialization, live_policy::install_policy copies the configured privacy mode into this global lock, making it accessible to all threads.
The system supports runtime modification through the RPC endpoint openhuman.config_set_privacy_mode. When invoked, the handler updates the persisted configuration and calls live_policy::reload_privacy(new_mode). This function clones the current SecurityPolicy, replaces only the privacy_mode field, and writes the updated policy back into the LivePolicy lock. Changes take effect immediately for all subsequent egress checks.
Components read the active mode via live_policy::current_privacy_mode(), which acquires the lock and returns the current variant, falling back to PrivacyMode::Standard if no policy is installed.
Runtime Enforcement Points
Egress Descriptor Validation
Every outbound request first constructs an EgressDescriptor defined in src/openhuman/security/egress/types.rs. During this construction phase, the builder calls current_privacy_mode(). If the mode is LocalOnly, the descriptor either masks the destination or aborts the request before any network layer interaction occurs.
Network Tool Guards
Concrete network implementations contain explicit privacy gates. In src/openhuman/tools/impl/network/http_request.rs, the execution path contains:
// Local‑only enforcement (privacy epic S7, #4441)
if live_policy::current_privacy_mode() == PrivacyMode::LocalOnly {
tracing::debug!(target: "[http]", "blocked: local‑only privacy mode");
return Err(Error::PrivacyModeBlocked);
}
The same check appears in src/openhuman/tools/impl/network/curl.rs and other network tools, ensuring consistent enforcement regardless of which HTTP client backs the operation.
Thread-Local Overrides for Testing
The test suite uses live_policy::test_privacy_scope(mode) to temporarily force a specific mode for a single thread. This function returns a TestPrivacyGuard that restores the previous mode when dropped, allowing tests to verify that tools correctly respect privacy settings without altering global state.
Implementation Examples
Setting privacy mode via RPC from a Rust client:
use openhuman_core::client::CoreRpcClient;
use serde_json::json;
// Switch to “LocalOnly” – all outbound network traffic will be blocked
let rpc = CoreRpcClient::new("http://127.0.0.1:8000/rpc")?;
rpc.invoke(
"openhuman.config_set_privacy_mode",
json!({ "mode": "LocalOnly" })
).await?;
Reloading privacy mode after a manual config edit:
use openhuman::security::live_policy::{reload_privacy, PrivacyMode};
fn apply_new_mode() -> Result<(), String> {
let new_mode = PrivacyMode::Standard;
reload_privacy(new_mode)?;
Ok(())
}
Checking the current mode in a custom tool:
use openhuman::security::live_policy::current_privacy_mode;
use openhuman::security::policy::PrivacyMode;
fn can_send() -> bool {
matches!(current_privacy_mode(), PrivacyMode::Standard)
}
Temporarily forcing a mode in a unit test:
use openhuman::security::live_policy::{test_privacy_scope, PrivacyMode};
#[tokio::test]
async fn blocks_when_local_only() {
let _guard = test_privacy_scope(PrivacyMode::LocalOnly);
// any network request made here will be rejected
assert!(http_request::fetch("https://example.com").await.is_err());
}
Summary
- Global State: Privacy mode is stored in a
RwLock<PrivacyMode>insideLivePolicy, accessible viacurrent_privacy_mode(). - Hot-Reload: The
reload_privacyfunction andopenhuman.config_set_privacy_modeRPC allow runtime changes without restarting the core. - Configuration: The initial value originates from
config.toml, parsed bySecurityPolicy::from_configinsrc/openhuman/security/policy/types.rs. - Enforcement: Every egress path—including
http_request.rsandcurl.rs—checkscurrent_privacy_mode()and returnsError::PrivacyModeBlockedwhenLocalOnlyis active. - Testability:
test_privacy_scopeprovides thread-local overrides for isolated testing.
Frequently Asked Questions
Where is the privacy mode stored in memory?
The active privacy mode is stored in a global RwLock<PrivacyMode> inside the LivePolicy singleton defined in src/openhuman/security/live_policy.rs. This lock is initialized during install_policy and read by current_privacy_mode() whenever a component needs to check the current security posture.
How can I change privacy mode without restarting OpenHuman?
Call the openhuman.config_set_privacy_mode RPC endpoint with the desired mode (Standard or LocalOnly). The handler triggers live_policy::reload_privacy(), which updates the global lock immediately. Since all components query current_privacy_mode() at execution time, no restart is required for the change to take effect.
What happens when privacy mode is set to LocalOnly?
When current_privacy_mode() returns PrivacyMode::LocalOnly, egress descriptors mask or block destinations, and network tools like http_request.rs return Error::PrivacyModeBlocked before any socket connection is established. This prevents all outbound network traffic while allowing local filesystem and in-process operations to continue.
How does the testing framework verify privacy enforcement?
Tests use live_policy::test_privacy_scope(PrivacyMode::LocalOnly), which returns a TestPrivacyGuard. The guard sets a thread-local override for the calling thread only, restoring the previous mode when it drops. This allows concurrent tests to run with different privacy settings without interfering with each other or the global policy state.
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 →