feat(privacy): S7 — local-only enforcement across integrations + tools (#4441)#4850
Conversation
…fetch/curl/polymarket) (tinyhumansai#4441)
|
Important Review skippedNo new commits to review since the last review. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds 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. ChangesLocalOnly enforcement core
Outbound execution gates
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/openhuman/integrations/client.rs (1)
349-349: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd 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 inclient_tests.rscalls the private helper directly — none of these six call sites (post,get,patch,delete,upload_multipart,get_bytes) are exercised end-to-end underLocalOnly. Composio'sexecute_tool/execute_tool_onceand cloud embedding'sembed()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. theBAD_REQUEST/FORBIDDENmocks) 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 winAvoid logging the full URL on the LocalOnly block path.
local_only_tool_blockalready logsdesc.service(just the host) vialog::warn!inenforce.rs. This newtracing::debug!additionally logs the full rawurl, 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 winFile exceeds the ~500-line guideline; tests could move to a sibling
_tests.rsfile.This file now runs to ~636 lines with the
mod testsblock inline.http_request.rsandpolymarket.rsin the same cohort already split their tests intohttp_request_tests.rs/polymarket_tests.rs. Movingcurl.rs's test module to acurl_tests.rswould 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 winSame 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 constructingEgressDescriptor::network_fetch(host). Root cause:EgressDescriptor::network_fetchtakes a host string, not a URL, so every call site must duplicate the extraction+fallback logic. Adding something likeEgressDescriptor::network_fetch_from_url(url: &str) -> Selfinsecurity::egress::types.rswould 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: havenetwork_egress_descriptorbuild 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 ownnetwork_egress_descriptorhelper).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 insend_with_retrywith 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
📒 Files selected for processing (17)
src/openhuman/composio/client.rssrc/openhuman/composio/client_tests.rssrc/openhuman/embeddings/cloud_adapter.rssrc/openhuman/integrations/client.rssrc/openhuman/integrations/client_tests.rssrc/openhuman/security/egress/enforce.rssrc/openhuman/security/egress/enforce_tests.rssrc/openhuman/security/egress/mod.rssrc/openhuman/security/live_policy.rssrc/openhuman/security/mod.rssrc/openhuman/tools/impl/network/curl.rssrc/openhuman/tools/impl/network/http_request.rssrc/openhuman/tools/impl/network/http_request_tests.rssrc/openhuman/tools/impl/network/mod.rssrc/openhuman/tools/impl/network/polymarket.rssrc/openhuman/tools/impl/network/polymarket_tests.rssrc/openhuman/tools/impl/network/web_fetch.rs
|
Review round addressed — 5 commits pushed (
Tests green across egress/integrations/polymarket/curl/clob_auth. |
🧩 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 || trueLength of output: 1225 Good, the commits mentioned all exist on the PR. I'll trigger a full re-review to verify the fixes now.
ヽ(・∀・)ノ ✅ Action performedReview finished.
|
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
src/openhuman/composio/execute_dispatch.rssrc/openhuman/integrations/client_tests.rssrc/openhuman/security/egress/enforce.rssrc/openhuman/security/egress/enforce_tests.rssrc/openhuman/tools/impl/network/curl.rssrc/openhuman/tools/impl/network/polymarket.rssrc/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
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/openhuman/tools/impl/network/polymarket.rs (1)
577-597: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winStill missing
emit_external_transferfor 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 callsemit_external_transfer, so the/auth/api-key(wallet address + L1 signature) transfer never produces the usualExternalTransferPendingdisclosure 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 winExtract 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 inmod.rs(next tolocal_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 thattracing::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
📒 Files selected for processing (18)
src/openhuman/composio/client.rssrc/openhuman/composio/client_tests.rssrc/openhuman/composio/execute_dispatch.rssrc/openhuman/embeddings/cloud_adapter.rssrc/openhuman/integrations/client.rssrc/openhuman/integrations/client_tests.rssrc/openhuman/security/egress/enforce.rssrc/openhuman/security/egress/enforce_tests.rssrc/openhuman/security/egress/mod.rssrc/openhuman/security/live_policy.rssrc/openhuman/security/mod.rssrc/openhuman/tools/impl/network/curl.rssrc/openhuman/tools/impl/network/http_request.rssrc/openhuman/tools/impl/network/http_request_tests.rssrc/openhuman/tools/impl/network/mod.rssrc/openhuman/tools/impl/network/polymarket.rssrc/openhuman/tools/impl/network/polymarket_tests.rssrc/openhuman/tools/impl/network/web_fetch.rs
|
@coderabbitai all review comments addressed in the latest commits — please re-review and approve if clean. |
|
✅ Action performedReview finished.
|
…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).
|
Maintainer review changes:
Letting PR CI verify (no local build/test matrix run). Re the CodeRabbit nitpick asking for a verb-level |
|
@coderabbitai review The outstanding item — missing |
🧩 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.rsLength of output: 4005
This resolves the outstanding disclosure gap. I'll trigger a fresh review pass to confirm there are no other findings. ✏️ Learnings added
✅ Action performedReview finished.
|
|
@coderabbitai review Maintainer fix-pass check: all outstanding review findings are already resolved in the current HEAD (
Requesting a re-review to clear the now-stale CHANGES_REQUESTED. |
🧩 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.rsLength 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 -60Length of output: 440 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -rn "execute_composio_action_kind_with_connection" -A 30 src/openhuman/composio/ | head -80Length of output: 6201 Confirmed — the actual function is
Everything checks out cleanly against current HEAD. I'll trigger a fresh review pass now. ✅ Action performedReview finished.
|
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.
Maintainer fix passSynced onto fresh Fixed
Already addressed (verified in current HEAD — no action needed)
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. |
|
@coderabbitai review Re-review requested on the current head
No outstanding inline comments remain. Requesting a fresh review so the stale CHANGES_REQUESTED can be cleared. |
|
(ノ´ヮ´)ノ*:・゚✧ ✅ Action performedReview finished.
|
M3gA-Mind
left a comment
There was a problem hiding this comment.
Reviewed — CI green and all CodeRabbit/Codex review threads resolved. Approving.
Summary
http_request/web_fetch/curl/polymarket).security/egress/enforce.rs: purelocal_only_blocks(mode, desc)+is_control_plane(desc)(no globals, unit-testable) and side-effectingenforce_egress()/local_only_tool_block(). Gates before emit; keepsemit_external_transferobserve-only.composio/execute, all non-composio tool namespaces, embeddings, network fetch) is blocked.curl,polymarket, and integrationspatch/delete/upload_multipart/get_bytes.anyhowbail 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
security/egress/enforce.rs, re-exported fromsecurity/egress/mod.rs):local_only_blocks(mode, desc)returns true only whenmode == LocalOnly && desc.is_external && !is_control_plane(desc);enforce_egress(desc)readslive_policy::current_privacy_mode()(returnsStandard= no-op in unmanaged CLI/cron contexts, matching S1) andErrs with a clean human message;local_only_tool_block(desc)returns a[policy-blocked]-marked string for ToolResult sites. Each chokepoint calls the gate beforeemit_external_transfer. TheEgressDescriptortype is unchanged — the exemption is a helper-side path match ondesc.service, not a new field, so the S3 frontend consumer is unaffected.is_control_planeexemptsIntegrationdescriptors whoseserviceis not under/agent-integrations/(session/team/billing/auth — these actually route viaapi::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.execute_tool+execute_tool_once(both, per the dual-write hazard); integrationspost/get(+ newly-instrumentedpatch/delete/upload_multipart/get_bytes) with the exemption; embeddingsembed(); networkhttp_request/web_fetch/curl/polymarketgated at the top ofexecute(before URL validation → no DNS leak), returningToolResult::error.factory.rs(already correct).LocalOnlyglobally blocked unrelated mock tests). Fixed with a test-only thread-scopedlive_policy::test_privacy_scopeoverride that all S7 tests use, plus restoringStandardat the end of the pre-existingreload_privacytest (which had leakedLocalOnly— harmless pre-S7, breaks mocks now).Submission Checklist
local_only_blocks/is_control_planetruth tables + AC7 allowed/blocked per surface (composio, integrations user-data-vs-control-plane, embeddings, all 4 network tools).diff-covergate authoritative.## Related— N/A: no matrix feature row for this enforcement slice.Standardmode; enforcement only activates under user-selected local-only.Closes #NNNin the## RelatedsectionImpact
src/openhuman/**). No frontend.Standardmode behaviour is unchanged (gate is a no-op). No descriptor reshape, no migration.Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
Validation Run
pnpm --filter openhuman-app format:check— N/A: no frontend changed (Rust-core-only slice)pnpm typecheck— N/A: no TypeScript changedcargo test --lib openhuman::security::egress+ composio/integrations/embeddings/network module tests — 257 passcargo fmt --checkclean ·cargo checkcleansrc-taurichangedValidation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Standard-mode behaviour are unchanged.Summary by CodeRabbit