fix(security/channels): enforce cross-account channel_send guard through the /mcp bridge (#6443)#6455
Merged
Merged
Conversation
…ugh the /mcp bridge (#6443) #6449 fixed the cross-account (cross-tenant) channel_send guard on the in-process agent-loop path and flagged the /mcp bridge (subprocess claude-code driver) as an unchanged parallel surface pending a maintainer call. This lands that parity, mirroring how #6125 threaded the #6117 peer scope through the same chain: - CompletionRequest gains sender_account_id — the bot account / tenant the inbound turn arrived on. Out-of-band callers (cron, triggers, compaction, routing probes) leave it None via the struct's existing Default. - Both agent loops (run_agent_loop and the streaming variant) populate it from the manifest metadata stamp the kernel already writes (SENDER_ACCOUNT_ID_METADATA_KEY, #6449). - The claude-code driver's write_mcp_config forwards it on the bridge connection as X-LibreFang-Current-Account-Id, next to the existing X-LibreFang-Current-Peer-Jid / -Channel / -Chat-Id headers; empty or absent values emit no header. - mcp_http reads the header back and routes tool execution through execute_tool_with_sender_account, so tool_channel_send's existing #6443 guard rejects an explicit account_id that differs from the turn's account on the same channel. Clients that omit the header run unguarded exactly as before. Tests: - claude_code::tests — the peer-scope header tests now cover the account header (emitted when present, omitted when absent). - api_integration_test::test_mcp_http_channel_send_cross_account_guard_uses_account_header — end-to-end /mcp tools/call: mismatched account blocked, matching account passes, missing header no-ops. Verified: cargo check --workspace --lib, cargo clippy --workspace --all-targets -D warnings (clean), cargo fmt, scoped cargo test (llm-drivers mcp_config: 5 passed; api channel_send guards: 2 passed; runtime channel_send: 23 passed).
houko
added a commit
that referenced
this pull request
Jul 15, 2026
…issue (#6466) * fix(ci): treat retired pnpm audit endpoint as skip, not a dependency issue npm retired the legacy pnpm audit endpoints (/-/npm/v1/security/audits and .../audits/quick); they now return HTTP 410 with a body directing callers to the bulk advisory endpoint. pnpm v10 still calls the legacy path, so `pnpm audit` exits non-zero even when no advisory exists. `xtask deps --web` only inspected the exit code, so it counted each of the three frontend dirs (web / dashboard / docs) as a dependency issue and failed the Security job with "3 dependency issue(s) found". Because the job runs on any Rust/CI change, main went red on every push regardless of the diff under test (#6455, #6456, #6457 all inherited it). run_pnpm_audit now captures the combined stdout+stderr and classifies the outcome: a genuine advisory still fails the build, but the retired-endpoint signature (ERR_PNPM_AUDIT_BAD_RESPONSE / "endpoint is being retired" / an audit-context 410) is warned and skipped rather than counted. An audit-infrastructure outage no longer reads as a vulnerability. Restoring real frontend advisory coverage requires a pnpm version that speaks the bulk advisory endpoint and is out of scope here. Closes #6462 * docs(changelog): reference the PR number (#6466) for the pnpm audit fix CHANGELOG entries follow the PR-number convention; the initial entry used the tracking issue number (#6462) written before the PR existed. The `Closes #6462` link lives in the PR body / commit. --------- Co-authored-by: Evan <[email protected]>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #6443
Summary
#6449 fixed the cross-account (cross-tenant)
channel_sendguard on the in-process agent-loop path and explicitly flagged the/mcpbridge (subprocessclaude-codedriver) as an unchanged parallel surface pending a maintainer call.Without this parity, an agent running through the subprocess driver could still pass another tenant's
account_idand dispatch content into that tenant's chat — the exact vulnerability #6443 reported, on the one remaining path.This PR lands the parity, mirroring how #6125 threaded the #6117 peer scope through the same chain.
Changes
librefang-llm-driver:CompletionRequestgainssender_account_id— the bot account / tenant the inbound turn arrived on. Out-of-band callers (cron, triggers, compaction, routing probes) leave itNonevia the struct's existingDefault, so the ~35..Default::default()construction sites are untouched.librefang-runtime: both agent loops (run_agent_loopand the streaming variant) populate the new field from the manifest metadata stamp the kernel already writes (SENDER_ACCOUNT_ID_METADATA_KEY, fix: resolve four reported bugs (#6423, #6442, #6443, #6444) #6449).librefang-llm-drivers:claude_code.rs::write_mcp_configforwards it on the bridge connection asX-LibreFang-Current-Account-Id, next to the existingX-LibreFang-Current-Peer-Jid/-Channel/-Chat-Idheaders. Empty or absent values emit no header, so single-tenant and out-of-band spawns produce byte-identical configs to before.librefang-api:mcp_httpreads the header back and routes tool execution throughexecute_tool_with_sender_account, sotool_channel_send's existing bug(security/channels): channel_send account_id accepted unvalidated — cross-tenant dispatch across bot accounts #6443 guard fires on the bridge path. Clients that omit the header (external MCP clients, pre-parity CLIs) run unguarded exactly as before — the guard no-ops onNone.CHANGELOG.md: Security entry under[Unreleased].No guard logic changed — the bridge now feeds the same guard #6449 landed, with the same explicit-value-only, same-channel scoping.
Verification
cargo check --workspace --lib— clean.cargo clippy --workspace --all-targets -- -D warnings— zero warnings.cargo test -p librefang-llm-drivers --lib mcp_config— 5 passed (account header emitted when present, omitted when absent, headers object still omitted when empty).cargo test -p librefang-api --test api_integration_test channel_send— 2 passed, including the newtest_mcp_http_channel_send_cross_account_guard_uses_account_header: end-to-end/mcptools/callwith mismatched account blocked, matching account passing, and missing header no-oping.cargo test -p librefang-runtime --lib channel_send— 23 passed (existing bug(security/channels): channel_send can dispatch to a different recipient on the same channel (cross-chat message leak) #6117/bug(security/channels): channel_send account_id accepted unvalidated — cross-tenant dispatch across bot accounts #6443 guard unit tests unaffected).