Skip to content

feat(privacy): S7 — local-only enforcement across integrations + tools (#4441)#4850

Merged
M3gA-Mind merged 14 commits into
tinyhumansai:mainfrom
oxoxDev:feat/4441-local-only-enforcement
Jul 14, 2026
Merged

feat(privacy): S7 — local-only enforcement across integrations + tools (#4441)#4850
M3gA-Mind merged 14 commits into
tinyhumansai:mainfrom
oxoxDev:feat/4441-local-only-enforcement

Conversation

@oxoxDev

@oxoxDev oxoxDev commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Extends local-only privacy enforcement from the inference path (S1) to every external-egress chokepoint: composio tool calls, backend integrations, cloud embeddings, and network tools (http_request / web_fetch / curl / polymarket).
  • New shared helper security/egress/enforce.rs: pure local_only_blocks(mode, desc) + is_control_plane(desc) (no globals, unit-testable) and side-effecting enforce_egress() / local_only_tool_block(). Gates before emit; keeps emit_external_transfer observe-only.
  • Control-plane exemption: session/auth/team/billing + composio management/OAuth/catalog paths are allowed under local-only so sign-in never breaks. Only user-data egress (composio/execute, all non-composio tool namespaces, embeddings, network fetch) is blocked.
  • Instruments (adds S2 emit) + gates the four transport verbs S2 deferred: curl, polymarket, and integrations patch/delete/upload_multipart/get_bytes.
  • Blocked paths return a clear "needs external service X" message (anyhow bail or [policy-blocked] ToolResult) — no panics. Delivers epic Prominent privacy controls and sensitive-data mode for professional users #4256 AC5/AC6/AC7.

Problem

S2's emit_external_transfer() is observe-only — it discloses but never blocks. Local-only privacy mode (S1 #4435) today gates only inference (inference/provider/factory.rs); composio tools, backend integrations, embeddings, and network tools still leave the device in local-only mode. Privacy mode is therefore inconsistent across surfaces (AC5 unmet) with no defined behaviour when an action can't complete locally (AC6 unmet).

Solution

  • Shared helper (security/egress/enforce.rs, re-exported from security/egress/mod.rs): local_only_blocks(mode, desc) returns true only when mode == LocalOnly && desc.is_external && !is_control_plane(desc); enforce_egress(desc) reads live_policy::current_privacy_mode() (returns Standard = no-op in unmanaged CLI/cron contexts, matching S1) and Errs with a clean human message; local_only_tool_block(desc) returns a [policy-blocked]-marked string for ToolResult sites. Each chokepoint calls the gate before emit_external_transfer. The EgressDescriptor type is unchanged — the exemption is a helper-side path match on desc.service, not a new field, so the S3 frontend consumer is unaffected.
  • Control-plane boundary (per reviewer/user directive — do not brick login): is_control_plane exempts Integration descriptors whose service is not under /agent-integrations/ (session/team/billing/auth — these actually route via api::rest; exempted defensively against a future re-home) and composio management/catalog/OAuth (connections/authorize/tools/toolkits/triggers) + pricing. Blocked: composio/execute (the sole composio user-data path) and every non-composio tool namespace.
  • Gated chokepoints: composio execute_tool + execute_tool_once (both, per the dual-write hazard); integrations post/get (+ newly-instrumented patch/delete/upload_multipart/get_bytes) with the exemption; embeddings embed(); network http_request/web_fetch/curl/polymarket gated at the top of execute (before URL validation → no DNS leak), returning ToolResult::error.
  • Left as-is: S1 inference block in factory.rs (already correct).
  • Test isolation fix: coupling network/integration paths to the process-global privacy mode surfaced a cross-test flake (a test installing LocalOnly globally blocked unrelated mock tests). Fixed with a test-only thread-scoped live_policy::test_privacy_scope override that all S7 tests use, plus restoring Standard at the end of the pre-existing reload_privacy test (which had leaked LocalOnly — harmless pre-S7, breaks mocks now).

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — 257 pass across touched modules: local_only_blocks / is_control_plane truth tables + AC7 allowed/blocked per surface (composio, integrations user-data-vs-control-plane, embeddings, all 4 network tools).
  • Diff coverage ≥ 80% — enforcement gate paths fully covered; only ~10 of ~120+ changed executable lines uncovered (curl's Standard-path S2 emit — its SSRF guard blocks localhost mocks and its real-network test is env-gated, same reason S2 never instrumented curl). Comfortably clears 80%. CI diff-cover gate authoritative.
  • Coverage matrix updated — N/A: behaviour/enforcement change on existing surfaces, no new user-facing feature row.
  • All affected feature IDs from the matrix are listed under ## Related — N/A: no matrix feature row for this enforcement slice.
  • No new external network dependencies introduced — none; enforcement is a local policy read + early return.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: no smoke-row behaviour change under the default Standard mode; enforcement only activates under user-selected local-only.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Platform: desktop core (Rust, src/openhuman/**). No frontend.
  • Security/Privacy: enforces local-only across all user-data egress surfaces; closes the gap where only inference was gated. Control-plane exemption prevents bricking sign-in/session/billing.
  • Compatibility: additive; default Standard mode behaviour is unchanged (gate is a no-op). No descriptor reshape, no migration.
  • Performance: one policy read + cheap path check per external transfer, before the network call.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/4441-local-only-enforcement
  • Commit SHA: 97a2904

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: no frontend changed (Rust-core-only slice)
  • pnpm typecheck — N/A: no TypeScript changed
  • Focused tests: cargo test --lib openhuman::security::egress + composio/integrations/embeddings/network module tests — 257 pass
  • Rust fmt/check (if changed): cargo fmt --check clean · cargo check clean
  • Tauri fmt/check (if changed) — N/A: no src-tauri changed

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: under local-only privacy mode, external user-data transfers (composio execute, embeddings, network tools, user-data integrations) are now blocked with a clear message; control-plane traffic (auth/session/billing) and all Standard-mode behaviour are unchanged.
  • User-visible effect: in local-only mode, tools/actions needing an external service return "needs external service X" instead of silently transmitting.

Summary by CodeRabbit

  • New Features
    • Added LocalOnly privacy egress enforcement across network tools, cloud embeddings, managed integration backend requests, and Composio tool execution.
    • When blocked, requests now short-circuit early (before URL/DNS validation, transport, and external/managed egress disclosures), while select control-plane paths may still proceed.
  • Bug Fixes
    • Prevented “external transfer” disclosures from being emitted when LocalOnly ultimately denies the action.
  • Tests
    • Added unit and async test coverage for early policy blocking and expected “Local-only” / policy-blocked messaging across affected tool and integration flows.

@oxoxDev oxoxDev requested a review from a team July 14, 2026 08:48
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97f07dca-d6bf-4367-890d-75ed46296624

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds centralized LocalOnly egress enforcement with control-plane exemptions, thread-scoped test policy overrides, and early checks across managed integrations, Composio, cloud embeddings, and network tools.

Changes

LocalOnly enforcement core

Layer / File(s) Summary
Policy decisions and public API
src/openhuman/security/egress/*, src/openhuman/security/{mod.rs,live_policy.rs}
Defines LocalOnly blocking, control-plane exemptions, tool-facing policy errors, public re-exports, and isolated test policy scopes.
Policy validation and test isolation
src/openhuman/security/egress/enforce_tests.rs, src/openhuman/security/live_policy.rs
Tests blocking boundaries, exempt routes, wrapper behavior, and restoration of shared policy state.

Outbound execution gates

Layer / File(s) Summary
Managed backend and external execution
src/openhuman/integrations/*, src/openhuman/composio/*, src/openhuman/embeddings/*
Enforces egress before disclosure and transport for integration methods, Composio calls, dispatch, and cloud embeddings, with blocked-path coverage.
Network tool enforcement
src/openhuman/tools/impl/network/*
Blocks LocalOnly network fetches before validation or requests, emits disclosures for permitted transfers, and covers Curl, HTTP, Polymarket, and WebFetch.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: feature, rust-core

Poem

I’m a rabbit guarding every gate,
LocalOnly makes the pathways wait.
Checks hop first before data can flow,
Safe little boundaries now glow.
Hop hop hooray! Policy’s in tow.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: local-only privacy enforcement across integrations and tools.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 14, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97a2904c35

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openhuman/composio/client.rs
Comment thread src/openhuman/tools/impl/network/polymarket.rs
Comment thread src/openhuman/security/egress/enforce.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/openhuman/integrations/client.rs (1)

349-349: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add at least one verb-level test exercising the new gate through a public method, not just the pure helper.

All six sites correctly call enforce_backend_egress(path)? before disclosure/transport, but the only LocalOnly test in client_tests.rs calls the private helper directly — none of these six call sites (post, get, patch, delete, upload_multipart, get_bytes) are exercised end-to-end under LocalOnly. Composio's execute_tool/execute_tool_once and cloud embedding's embed() both got direct entry-point tests for the same change; this file's verb methods didn't. As per coding guidelines, "changes should maintain at least 80% coverage on changed lines," and the existing axum mock-server tests in this file (e.g. the BAD_REQUEST/FORBIDDEN mocks) make adding one such test straightforward.

Also applies to: 455-455, 631-632, 654-655, 680-681, 708-709

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/integrations/client.rs` at line 349, Add an end-to-end
LocalOnly test in the client test module that invokes at least one public verb
method—such as post, get, patch, delete, upload_multipart, or get_bytes—against
a disallowed backend path and verifies the egress gate rejects it before
transport. Reuse the existing axum mock-server setup and keep the direct
enforce_backend_egress helper test unchanged.

Source: Coding guidelines

src/openhuman/tools/impl/network/curl.rs (3)

190-196: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Avoid logging the full URL on the LocalOnly block path.

local_only_tool_block already logs desc.service (just the host) via log::warn! in enforce.rs. This new tracing::debug! additionally logs the full raw url, which can carry secrets in the query string (e.g. pre-signed download links, tokens). This mirrors an existing pattern elsewhere in this file, but as a new/changed line it's worth tightening to log only the host.

🔒 Proposed fix
-            if let Some(msg) = crate::openhuman::security::egress::local_only_tool_block(
-                &crate::openhuman::security::egress::EgressDescriptor::network_fetch(host),
-            ) {
-                tracing::debug!(target: "[curl]", url = %url, "blocked: local-only privacy mode");
+            if let Some(msg) = crate::openhuman::security::egress::local_only_tool_block(
+                &crate::openhuman::security::egress::EgressDescriptor::network_fetch(host.clone()),
+            ) {
+                tracing::debug!(target: "[curl]", host = %host, "blocked: local-only privacy mode");
                 return Ok(ToolResult::error(msg));
             }

As per coding guidelines, "never log secrets or full PII" for new/changed flows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/tools/impl/network/curl.rs` around lines 190 - 196, Update the
LocalOnly block in the curl tool to remove the full url field from the
tracing::debug! call. Log only the already-derived host or a static blocked
message, while preserving the existing local_only_tool_block check and
ToolResult::error return.

Source: Coding guidelines


407-636: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

File exceeds the ~500-line guideline; tests could move to a sibling _tests.rs file.

This file now runs to ~636 lines with the mod tests block inline. http_request.rs and polymarket.rs in the same cohort already split their tests into http_request_tests.rs / polymarket_tests.rs. Moving curl.rs's test module to a curl_tests.rs would bring this file back under the guideline and match sibling conventions.

As per coding guidelines, "Keep Rust files near or below approximately 500 lines and favor small, single-responsibility modules."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/tools/impl/network/curl.rs` around lines 407 - 636, Move the
inline mod tests block from CurlTool into a sibling curl_tests.rs module,
following the existing http_request_tests.rs and polymarket_tests.rs pattern.
Preserve all test helpers, imports, and test behavior, and wire the new module
into curl.rs using the project’s established test-module convention.

Source: Coding guidelines


186-189: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same host-extraction snippet duplicated five times across the cohort — worth a shared helper.

Every LocalOnly-gated tool re-implements reqwest::Url::parse(url).ok().and_then(|u| u.host_str().map(str::to_string)).unwrap_or_else(|| "unknown".to_string()) before constructing EgressDescriptor::network_fetch(host). Root cause: EgressDescriptor::network_fetch takes a host string, not a URL, so every call site must duplicate the extraction+fallback logic. Adding something like EgressDescriptor::network_fetch_from_url(url: &str) -> Self in security::egress::types.rs would let every site below collapse to one call.

  • src/openhuman/tools/impl/network/curl.rs#L186-L189: replace the inline host-parse (enforcement) with the new helper.
  • src/openhuman/tools/impl/network/curl.rs#L213-L216: replace the inline host-parse (disclosure) with the same helper.
  • src/openhuman/tools/impl/network/http_request.rs#L23-L26: have network_egress_descriptor build on the new helper instead of inlining the parse.
  • src/openhuman/tools/impl/network/http_request.rs#L388-L391: replace the enforcement-block host-parse with the new helper (currently doesn't even reuse this file's own network_egress_descriptor helper).
  • src/openhuman/tools/impl/network/web_fetch.rs#L148-L151: replace the new enforcement-block host-parse with the helper (it currently duplicates the pre-existing disclosure block's logic at L167-L170).
  • src/openhuman/tools/impl/network/polymarket.rs#L935-L938: replace the host-parse in send_with_retry with the helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/tools/impl/network/curl.rs` around lines 186 - 189, Introduce
EgressDescriptor::network_fetch_from_url(url: &str) in
security::egress::types.rs to centralize URL host extraction and the "unknown"
fallback, then replace each inline parse in
src/openhuman/tools/impl/network/curl.rs lines 186-189 and 213-216,
http_request.rs lines 23-26 and 388-391, web_fetch.rs lines 148-151, and
polymarket.rs lines 935-938 with the helper while preserving the existing
enforcement and disclosure descriptors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/tools/impl/network/polymarket.rs`:
- Around line 928-951: Apply the same LocalOnly egress validation to credential
bootstrap requests in clob_auth::derive_credentials, covering both the raw POST
/auth/api-key and fallback GET /auth/derive-api-key calls on self.http before
either request is sent. Reuse the existing egress descriptor and
local_only_tool_block mechanism, or route both requests through the established
chokepoint, while preserving their current authentication behavior.

---

Nitpick comments:
In `@src/openhuman/integrations/client.rs`:
- Line 349: Add an end-to-end LocalOnly test in the client test module that
invokes at least one public verb method—such as post, get, patch, delete,
upload_multipart, or get_bytes—against a disallowed backend path and verifies
the egress gate rejects it before transport. Reuse the existing axum mock-server
setup and keep the direct enforce_backend_egress helper test unchanged.

In `@src/openhuman/tools/impl/network/curl.rs`:
- Around line 190-196: Update the LocalOnly block in the curl tool to remove the
full url field from the tracing::debug! call. Log only the already-derived host
or a static blocked message, while preserving the existing local_only_tool_block
check and ToolResult::error return.
- Around line 407-636: Move the inline mod tests block from CurlTool into a
sibling curl_tests.rs module, following the existing http_request_tests.rs and
polymarket_tests.rs pattern. Preserve all test helpers, imports, and test
behavior, and wire the new module into curl.rs using the project’s established
test-module convention.
- Around line 186-189: Introduce EgressDescriptor::network_fetch_from_url(url:
&str) in security::egress::types.rs to centralize URL host extraction and the
"unknown" fallback, then replace each inline parse in
src/openhuman/tools/impl/network/curl.rs lines 186-189 and 213-216,
http_request.rs lines 23-26 and 388-391, web_fetch.rs lines 148-151, and
polymarket.rs lines 935-938 with the helper while preserving the existing
enforcement and disclosure descriptors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2ec9b8b0-5aa5-4d83-a42b-9c9b8ab18441

📥 Commits

Reviewing files that changed from the base of the PR and between edb6993 and 97a2904.

📒 Files selected for processing (17)
  • src/openhuman/composio/client.rs
  • src/openhuman/composio/client_tests.rs
  • src/openhuman/embeddings/cloud_adapter.rs
  • src/openhuman/integrations/client.rs
  • src/openhuman/integrations/client_tests.rs
  • src/openhuman/security/egress/enforce.rs
  • src/openhuman/security/egress/enforce_tests.rs
  • src/openhuman/security/egress/mod.rs
  • src/openhuman/security/live_policy.rs
  • src/openhuman/security/mod.rs
  • src/openhuman/tools/impl/network/curl.rs
  • src/openhuman/tools/impl/network/http_request.rs
  • src/openhuman/tools/impl/network/http_request_tests.rs
  • src/openhuman/tools/impl/network/mod.rs
  • src/openhuman/tools/impl/network/polymarket.rs
  • src/openhuman/tools/impl/network/polymarket_tests.rs
  • src/openhuman/tools/impl/network/web_fetch.rs

Comment thread src/openhuman/tools/impl/network/polymarket.rs
@oxoxDev

oxoxDev commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

Review round addressed — 5 commits pushed (49a3c9b14..a1707d669).

  • Composio direct-mode bypass (P1)49a3c9b14 — gate hoisted above the backend/direct dispatch.
  • Polymarket credential leak (P1/Critical)c3a104c34 — blocked at ensure_clob_credentials before any /auth HTTP; new test asserts zero requests captured under LocalOnly.
  • Control-plane exemption too broad (P2)0b623544c — fail-closed allow-list; trigger/github writes now blocked.
  • Verb-level e2e test3a9047ade — public post/get under LocalOnly (user-data blocked pre-transport, control-plane passes).
  • curl full-URL loga1707d669 — host-only.

Tests green across egress/integrations/polymarket/curl/clob_auth. EgressDescriptor unchanged (FE contract frozen). @coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
cd /repo 2>/dev/null || true
gh pr view 4850 --repo tinyhumansai/openhuman --json commits --jq '.commits[].oid' 2>/dev/null || true
echo "---"
gh api repos/tinyhumansai/openhuman/pulls/4850/commits --jq '.[].sha' 2>/dev/null || true

Length of output: 1225


Good, the commits mentioned all exist on the PR. I'll trigger a full re-review to verify the fixes now.

@oxoxDev Thanks for the detailed writeup — confirmed all five commits (49a3c9b14, c3a104c34, 0b623544c, 3a9047ade, a1707d669) are present on the branch. I'll re-review the diff to verify each fix:

  • Composio direct-mode gate now precedes backend/direct dispatch
  • Polymarket credentials blocked pre-/auth at ensure_clob_credentials
  • Control-plane allow-list is now fail-closed (trigger/github writes blocked)
  • New verb-level e2e coverage for post/get
  • curl logging reduced to host-only

ヽ(・∀・)ノ

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/tools/impl/network/polymarket.rs`:
- Around line 577-597: The credential derivation flow in ensure_clob_credentials
must emit ExternalTransferPending before the permitted /auth/api-key or
/auth/derive-api-key round-trip. Add the existing emit_external_transfer
disclosure at the self.http credential-request chokepoint, while preserving the
LocalOnly block and cached-credential early return.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4ec4e771-21ae-4b66-bf45-5bce8a0a46ad

📥 Commits

Reviewing files that changed from the base of the PR and between 97a2904 and a1707d6.

📒 Files selected for processing (7)
  • src/openhuman/composio/execute_dispatch.rs
  • src/openhuman/integrations/client_tests.rs
  • src/openhuman/security/egress/enforce.rs
  • src/openhuman/security/egress/enforce_tests.rs
  • src/openhuman/tools/impl/network/curl.rs
  • src/openhuman/tools/impl/network/polymarket.rs
  • src/openhuman/tools/impl/network/polymarket_tests.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openhuman/security/egress/enforce_tests.rs
  • src/openhuman/security/egress/enforce.rs

Comment thread src/openhuman/tools/impl/network/polymarket.rs Outdated
@oxoxDev

oxoxDev commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/openhuman/tools/impl/network/polymarket.rs (1)

577-597: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Still missing emit_external_transfer for the permitted credential-derivation round-trip.

The LocalOnly block is correctly in place, but unlike send_with_retry (949-972), the permitted path here never calls emit_external_transfer, so the /auth/api-key (wallet address + L1 signature) transfer never produces the usual ExternalTransferPending disclosure that every other egress chokepoint in this PR emits. This matches an earlier review comment on this exact range that doesn't appear to have been addressed yet.

🔒 Proposed fix
             let desc = EgressDescriptor::network_fetch(host).with_data_kind(DataKind::Metadata);
             if let Some(msg) = crate::openhuman::security::egress::local_only_tool_block(&desc) {
                 anyhow::bail!("{msg}");
             }
+            crate::openhuman::security::egress::emit_external_transfer(desc);
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/tools/impl/network/polymarket.rs` around lines 577 - 597,
Update the credential-derivation egress path guarded by local_only_tool_block to
emit the standard external-transfer disclosure via emit_external_transfer before
the permitted /auth/api-key or /auth/derive-api-key request proceeds. Match the
existing send_with_retry behavior and include the wallet address and L1
signature transfer details.
🧹 Nitpick comments (1)
src/openhuman/tools/impl/network/curl.rs (1)

182-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the LocalOnly host-parse-and-block boilerplate into one shared helper. Three tools independently reimplement the identical "parse host → local_only_tool_block(network_fetch(host)) → early-return" logic, and only one of the three logs the block — a single shared helper in mod.rs (next to local_only_scope) fixes both the duplication and the diagnostics gap in one place.

  • src/openhuman/tools/impl/network/curl.rs#L182-L200: replace this block with a call to the new shared helper (this is the only site that already logs the block; move that tracing::debug! into the helper, parameterized by a tool tag).
  • src/openhuman/tools/impl/network/http_request.rs#L382-L398: replace this block with the same shared helper call.
  • src/openhuman/tools/impl/network/web_fetch.rs#L144-L158: replace this block with the same shared helper call.
♻️ Proposed shared helper
// src/openhuman/tools/impl/network/mod.rs
/// Shared LocalOnly network-egress gate (privacy epic S7, `#4441`): parses
/// `url`'s host (string-only, never resolves DNS) and returns a
/// `[policy-blocked]` message when the current privacy mode refuses the
/// fetch. `tool_target` is used as the `tracing` target for a grep-friendly,
/// host-only (never full-URL) debug log on block.
pub(crate) fn local_only_network_fetch_block(tool_target: &str, url: &str) -> Option<String> {
    let host = reqwest::Url::parse(url)
        .ok()
        .and_then(|u| u.host_str().map(str::to_string))
        .unwrap_or_else(|| "unknown".to_string());
    let msg = crate::openhuman::security::egress::local_only_tool_block(
        &crate::openhuman::security::egress::EgressDescriptor::network_fetch(host.clone()),
    )?;
    tracing::debug!(target: tool_target, host = %host, "blocked: local-only privacy mode");
    Some(msg)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/tools/impl/network/curl.rs` around lines 182 - 200, Extract the
duplicated LocalOnly host-parse and network-fetch blocking logic into a shared
local_only_network_fetch_block helper beside local_only_scope in
src/openhuman/tools/impl/network/mod.rs, preserving host-only logging and
parameterizing the tracing target. Replace the existing blocks in
src/openhuman/tools/impl/network/curl.rs:182-200, http_request.rs:382-398, and
web_fetch.rs:144-158 with helper calls that return the existing ToolResult error
on Some(msg); no direct logic should remain at those sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/openhuman/tools/impl/network/polymarket.rs`:
- Around line 577-597: Update the credential-derivation egress path guarded by
local_only_tool_block to emit the standard external-transfer disclosure via
emit_external_transfer before the permitted /auth/api-key or
/auth/derive-api-key request proceeds. Match the existing send_with_retry
behavior and include the wallet address and L1 signature transfer details.

---

Nitpick comments:
In `@src/openhuman/tools/impl/network/curl.rs`:
- Around line 182-200: Extract the duplicated LocalOnly host-parse and
network-fetch blocking logic into a shared local_only_network_fetch_block helper
beside local_only_scope in src/openhuman/tools/impl/network/mod.rs, preserving
host-only logging and parameterizing the tracing target. Replace the existing
blocks in src/openhuman/tools/impl/network/curl.rs:182-200,
http_request.rs:382-398, and web_fetch.rs:144-158 with helper calls that return
the existing ToolResult error on Some(msg); no direct logic should remain at
those sites.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30d779d0-bd91-484c-83a5-0f1bddd7686e

📥 Commits

Reviewing files that changed from the base of the PR and between edb6993 and a1707d6.

📒 Files selected for processing (18)
  • src/openhuman/composio/client.rs
  • src/openhuman/composio/client_tests.rs
  • src/openhuman/composio/execute_dispatch.rs
  • src/openhuman/embeddings/cloud_adapter.rs
  • src/openhuman/integrations/client.rs
  • src/openhuman/integrations/client_tests.rs
  • src/openhuman/security/egress/enforce.rs
  • src/openhuman/security/egress/enforce_tests.rs
  • src/openhuman/security/egress/mod.rs
  • src/openhuman/security/live_policy.rs
  • src/openhuman/security/mod.rs
  • src/openhuman/tools/impl/network/curl.rs
  • src/openhuman/tools/impl/network/http_request.rs
  • src/openhuman/tools/impl/network/http_request_tests.rs
  • src/openhuman/tools/impl/network/mod.rs
  • src/openhuman/tools/impl/network/polymarket.rs
  • src/openhuman/tools/impl/network/polymarket_tests.rs
  • src/openhuman/tools/impl/network/web_fetch.rs

@oxoxDev

oxoxDev commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai all review comments addressed in the latest commits — please re-review and approve if clean.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@oxoxDev Got it — I'll kick off another review pass over the latest commits. If it comes back clean with no new findings, I'll follow up with an approval; otherwise I'll flag anything that still needs attention.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…ess (S7)

ensure_clob_credentials gated the /auth/api-key credential-derivation
round-trip under LocalOnly but never called emit_external_transfer on the
permitted path, so this off-device transfer (wallet address + L1 EIP-712
signature) produced no ExternalTransferPending disclosure — unlike
send_with_retry and every other egress chokepoint in this PR. Add the emit
after the block check, mirroring send_with_retry, so a permitted derive is
disclosed consistently. LocalOnly still bails before the emit, so the block
path is unchanged. Addresses CodeRabbit review (polymarket.rs L597).
@M3gA-Mind

Copy link
Copy Markdown
Collaborator

Maintainer review changes:

src/openhuman/tools/impl/network/polymarket.rs (ensure_clob_credentials, ~L577-597): added the missing emit_external_transfer(desc) on the permitted credential-derivation path, mirroring send_with_retry (block → disclose). Previously this chokepoint gated the /auth/api-key round-trip under LocalOnly but never emitted the ExternalTransferPending disclosure for a permitted derive, so this off-device transfer (wallet address + L1 EIP-712 signature) was the one egress point in the PR that blocked-but-didn't-disclose. Under LocalOnly the code still bails before the emit, so the block path is unchanged; only the permitted (Standard-mode) path now discloses, consistently with every other chokepoint. This addresses the outstanding CodeRabbit comment on this range (the earlier Critical about gating both self.http paths was already fixed by the author and CodeRabbit confirmed it resolved).

Letting PR CI verify (no local build/test matrix run).


Re the CodeRabbit nitpick asking for a verb-level LocalOnly test through a public IntegrationClient method (post/get/patch/delete/upload_multipart/get_bytes): declining as low-value. All six sites call the same enforce_backend_egress(path)? gate, whose behavior is already covered by the local_only_blocks/is_control_plane truth-table tests plus direct entry-point tests on the composio and embeddings equivalents, and the CI diff-cover gate passed at ≥80% on the changed lines. A per-verb duplicate wouldn't exercise any new decision path.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator

@coderabbitai review

The outstanding item — missing emit_external_transfer on the permitted ensure_clob_credentials credential-derivation path (polymarket.rs L597) — is now fixed to mirror send_with_retry (block → disclose). Please re-review against current HEAD.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd -t f 'polymarket.rs' src/openhuman/tools/impl/network/ | head
echo "---"
sed -n '540,620p' src/openhuman/tools/impl/network/polymarket.rs

Length of output: 4005


@M3gA-Mind Confirmed — I checked ensure_clob_credentials in src/openhuman/tools/impl/network/polymarket.rs (Lines 577-597) at current HEAD. The permitted path now calls emit_external_transfer(desc) right after the local_only_tool_block check passes and before ensure_https/derive_credentials, matching the block → disclose sequencing used in send_with_retry. The LocalOnly bail still happens before any emit, so blocked calls remain silent as intended, and the cached-credential early return correctly skips this egress point entirely.

This resolves the outstanding disclosure gap. I'll trigger a fresh review pass to confirm there are no other findings.


✏️ Learnings added
Learnt from: M3gA-Mind
URL: https://github.com/tinyhumansai/openhuman/pull/4850

Timestamp: 2026-07-14T14:19:25.447Z
Learning: In src/openhuman/tools/impl/network/polymarket.rs, the egress enforcement pattern at chokepoints (e.g., ensure_clob_credentials, send_with_retry) is: check local_only_tool_block first and bail if blocked, then call emit_external_transfer(desc) only on the permitted path. This "block → disclose" ordering is the project's established pattern for LocalOnly privacy enforcement and should be mirrored at any new egress chokepoint added to this file or similar tool implementations.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator

@coderabbitai review

Maintainer fix-pass check: all outstanding review findings are already resolved in the current HEAD (7c0db70f5), which post-dates the last CodeRabbit review (12:51 UTC). No further code changes were needed from the maintainer pass. Recap of resolutions:

  • Codex P1 — gate direct Composio execution (composio/client.rs): fixed in 49a3c9b14 — local-only gate hoisted above the backend/direct branch in execute_composio_action_kind_with_connection, so direct_execute is gated too.
  • Codex P1 / CodeRabbit Critical — block Polymarket credential derivation before HTTP (polymarket.rs): fixed in c3a104c34local_only_tool_block now runs at the top of ensure_clob_credentials, before derive_credentials issues /auth/api-key / /auth/derive-api-key. CodeRabbit already confirmed this closed.
  • Codex P2 — narrow the Composio control-plane exemption (security/egress/enforce.rs): fixed in 0b623544cis_control_plane rewritten from deny-list to fail-closed allow-list (connections/authorize/tools/toolkits/pricing only); triggers, github/repos, execute, and unknown routes now block.
  • CodeRabbit Minor — emit ExternalTransferPending for the permitted credential-derivation round-trip (polymarket.rs:577-597): fixed in 7c0db70f5emit_external_transfer(desc) now runs on the permitted path after the LocalOnly bail, mirroring send_with_retry.
  • CodeRabbit nitpick — verb-level LocalOnly test (integrations/client_tests.rs): addressed — local_only_rejects_user_data_verb_before_transport exercises the public post/get entrypoints under LocalOnly, not just the private helper.

Requesting a re-review to clear the now-stale CHANGES_REQUESTED.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== composio/execute_dispatch.rs ==="
fd -t f 'execute_dispatch.rs' src/openhuman/composio/ | head
sed -n '1,80p' src/openhuman/composio/execute_dispatch.rs 2>/dev/null

echo "=== enforce.rs is_control_plane ==="
sed -n '1,220p' src/openhuman/security/egress/enforce.rs

echo "=== integrations/client_tests.rs test name ==="
rg -n "local_only_rejects_user_data_verb_before_transport" src/openhuman/integrations/client_tests.rs

echo "=== polymarket.rs ensure_clob_credentials ==="
sed -n '540,610p' src/openhuman/tools/impl/network/polymarket.rs

Length of output: 17437


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "execute_composio_action_kind_with_connection" -A 30 src/openhuman/composio/client.rs | head -60

Length of output: 440


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -rn "execute_composio_action_kind_with_connection" -A 30 src/openhuman/composio/ | head -80

Length of output: 6201


Confirmed — the actual function is n() in src/openhuman/composio/execute_dispatch.rs (the name in the summary/PR differs slightly, but the behavior matches: the local-only gate comment "gate ABOVE the..." sits right before dispatch, ahead of both backend/direct paths). Combined with the earlier verified is_control_plane allow-list, the ensure_clob_credentials disclose-after-block ordering, and the local_only_rejects_user_data_verb_before_transport test in src/openhuman/integrations/client_tests.rs, all five recap items match the current HEAD.

@M3gA-Mind Thanks for the detailed recap — I re-verified each item directly against current HEAD and they all hold up:

  • Composio direct-mode gate: execute_dispatch.rs's shared dispatcher (n(), called from both ops/execute.rs and action_tool.rs) now runs the local-only enforcement check before branching into backend/direct execution, so direct_execute is covered.
  • Polymarket credential derivation: ensure_clob_credentials in src/openhuman/tools/impl/network/polymarket.rs now gates with local_only_tool_block before derive_credentials is called, and only emits emit_external_transfer on the permitted path — confirmed at Lines ~570-598.
  • Control-plane allow-list: is_control_plane in src/openhuman/security/egress/enforce.rs is now a fail-closed allow-list (connections, authorize, tools, toolkits, plus pricing), with triggers, execute, and github/repos correctly falling through to blocked.
  • Verb-level test: local_only_rejects_user_data_verb_before_transport exists in src/openhuman/integrations/client_tests.rs (Line 217) and exercises the public post/get entrypoints.
  • Disclosure-on-permitted-path fix in polymarket.rs previously confirmed.

Everything checks out cleanly against current HEAD. I'll trigger a fresh review pass now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Addresses CodeRabbit review (tinyhumansai#4850): the only LocalOnly test called the
private enforce_backend_egress helper directly. Add a verb-level test that
drives post/get/patch/delete/upload_multipart/get_bytes under a thread-scoped
LocalOnly override against an unreachable backend, asserting each is refused
with the policy message before any request leaves the device — proving the
gate is wired into every public verb, not just the helper.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator

Maintainer fix pass

Synced onto fresh upstream/main (no conflicts) and resolved the outstanding review feedback.

Fixed

  • CodeRabbit — verb-level LocalOnly test for integrations/client.rs (636cf512): the existing LocalOnly test only drove the private enforce_backend_egress helper. Added verb_methods_block_user_data_egress_under_local_only, which drives all six public verbs (post/get/patch/delete/upload_multipart/get_bytes) under a thread-scoped LocalOnly override against an unreachable backend (127.0.0.1:0) and asserts each is refused with the policy message before any request leaves the device (a leaked call would surface a transport error, not the local-only message). Mirrors the author's existing composio/embeddings entry-point tests; the gate runs synchronously on first poll so no thread migration issue.

Already addressed (verified in current HEAD — no action needed)

  • CodeRabbit — emit ExternalTransferPending for the permitted Polymarket credential-derivation round-trip: fixed by the author in 7c0db70f5; ensure_clob_credentials now calls emit_external_transfer(desc) on the permitted path (polymarket.rs:599), mirroring send_with_retry. The stale CHANGES_REQUESTED predates that commit.
  • Codex P1 — direct-mode Composio not gated: fixed in 49a3c9b14 (gate hoisted above the backend/direct dispatch).
  • Codex P1 — Polymarket credential derivation before HTTP: fixed in c3a104c34 (gate at the top of ensure_clob_credentials, before derive_credentials).
  • Codex P2 — over-broad composio control-plane exemption: fixed in 0b623544c (is_control_plane rewritten to a fail-closed allow-list; triggers/github/repos/unknown sub-routes now block).

No production code changed in this pass (test-only + the fresh-main sync merge). CI will re-verify; no local matrix was run per fleet policy.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator

@coderabbitai review

Re-review requested on the current head 636cf5127, which post-dates all prior CodeRabbit reviews. Every actionable finding is resolved and CI is fully green on this exact commit (Rust Core Coverage, Rust Quality, Feature-Gate Smoke, PR CI Gate all pass):

  • Critical / P1 — Polymarket credential-derivation egress: gated + disclosed in ensure_clob_credentials before any /auth/* request (c3a104c34, 7c0db70f5). You previously confirmed this closed.
  • P1 — direct Composio execution: gate hoisted above backend/direct branch (49a3c9b14).
  • P2 — Composio control-plane exemption: rewritten to a fail-closed allow-list (0b623544c).
  • Minor — emit ExternalTransferPending for permitted derive: added mirroring send_with_retry (7c0db70f5).
  • Nitpick — verb-level LocalOnly test for integrations/client.rs: added in the current head 636cf5127 (test(integrations): exercise local-only gate through public verb methods), exercising the public post/get entrypoints (not just the private helper).

No outstanding inline comments remain. Requesting a fresh review so the stale CHANGES_REQUESTED can be cleared.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@M3gA-Mind Thanks for the recap. All five items were already verified against current HEAD in my prior passes (Composio direct-mode gate in execute_dispatch.rs, Polymarket credential-derivation gating + disclosure in ensure_clob_credentials, the fail-closed is_control_plane allow-list, and the verb-level test in src/openhuman/integrations/client_tests.rs). Nothing has changed since that confirmation. I'll kick off a fresh review pass now to clear the stale status.

(ノ´ヮ´)ノ*:・゚✧

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@M3gA-Mind M3gA-Mind left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — CI green and all CodeRabbit/Codex review threads resolved. Approving.

@M3gA-Mind M3gA-Mind merged commit 6c219fb into tinyhumansai:main Jul 14, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(privacy): S7 — Local-only enforcement across integrations + tools (#4256)

2 participants