fix(perms): propagate sender/role through MCP bridge & delegate (#915)#1105
Open
mozaa-solana wants to merge 1 commit intonextlevelbuilder:devfrom
Open
fix(perms): propagate sender/role through MCP bridge & delegate (#915)#1105mozaa-solana wants to merge 1 commit intonextlevelbuilder:devfrom
mozaa-solana wants to merge 1 commit intonextlevelbuilder:devfrom
Conversation
…levelbuilder#915) Group-scoped permission checks (CheckCronPermission / CheckFileWriterPermission) fail with senderID="" when tools are invoked through: 1. MCP bridge (external CLI providers like claude-cli) — middleware never injected SenderID/Role into ctx, so even though the agent loop set them on RunRequest, the bridge layer dropped attribution. 2. delegate tool (gateway_managed.go delegateRunFn) — built RunRequest without copying SenderID/Role/Channel/ChatID/PeerKind from DelegateRequest, so the target agent's loop_context.injectContext skipped WithSenderID and downstream group cron/file_writer mutations denied with senderID="". Fix: - claude_cli: new OptSenderID, OptRole; BridgeContext carries SenderID + Role; writeMCPConfig emits X-Sender-ID / X-Role headers and signs them in the HMAC payload (extras: localKey | sessionKey | senderID | role). VerifyBridgeContext gains a backward-compat fallback that drops the last 2 extras for sessions written pre-this-change. - gateway/server.bridgeContextMiddleware: read X-Sender-ID + X-Role headers (HMAC-protected), inject WithSenderID / WithRole into ctx. - agent loop_pipeline_callbacks: pass req.SenderID and req.Role into chatReq.Options so external CLI providers receive them. - gateway_managed.delegateRunFn: propagate SenderID/Role/Channel/ChatID/ PeerKind from DelegateRequest into RunRequest. - team_tool_dispatch / gateway_consumer_normal: warn when sender is empty in a group-target dispatch so admins can find the upstream caller losing attribution. - config_permission_store: surface diagnostic-rich error messages so LLMs/users know exactly which user/scope/configType denied, instead of a generic "permission denied". - cron tool: forward CheckCronPermission's diagnostic message to the LLM result so the agent can self-correct.
This was referenced May 6, 2026
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.
Summary
Group-scoped permission checks (
CheckCronPermission/CheckFileWriterPermission) deny withsenderID=""when tools are invoked through:claude-cli,opencode-go) — the bridge middleware never injectedSenderID/Roleinto ctx, so even though the agent loop sets them onRunRequest, the bridge layer dropped attribution. Result: any group cron / file_writer mutation called via the bridge denies as "system context".delegatetool (gateway_managed.godelegateRunFn) — builtRunRequestwithout copyingSenderID/Role/Channel/ChatID/PeerKindfromDelegateRequest, so the target agent'sloop_context.injectContextskippedWithSenderIDand downstream group cron/file_writer mutations denied withsenderID="".Changes
internal/providers/claude_cli.go— newOptSenderID,OptRole.internal/providers/claude_cli_mcp.go—BridgeContextcarriesSenderID+Role;writeMCPConfigemitsX-Sender-ID/X-Roleheaders and signs them in the HMAC payload (extras:localKey | sessionKey | senderID | role).VerifyBridgeContextgains a backward-compat fallback that drops the last 2 extras for sessions written pre-this-change.internal/providers/claude_cli_session.go—bridgeContextFromOptsreads new opts.internal/gateway/server.go(bridgeContextMiddleware) — readX-Sender-ID+X-Roleheaders (HMAC-protected), injectWithSenderID/WithRoleinto ctx.internal/agent/loop_pipeline_callbacks.go— passreq.SenderID/req.RoleintochatReq.Options.cmd/gateway_managed.go(delegateRunFn) — propagateSenderID/Role/Channel/ChatID/PeerKindfromDelegateRequestintoRunRequest.internal/tools/team_tool_dispatch.go+cmd/gateway_consumer_normal.go—slog.Warnwhen sender is empty in a group-target dispatch so admins can find upstream callers losing attribution.internal/store/config_permission_store.go— surface diagnostic-rich error messages (which user/scope/configType denied) instead of generic "permission denied".internal/tools/cron.go— forward the diagnostic message to LLM result so the agent can self-correct.Test plan
go build ./...cleango test ./internal/providers/... ./internal/store/... ./internal/gateway/... ./internal/agent/...passes (incl.TestVerifyBridgeContextHMAC fallbacks)VerifyBridgeContext.Related
Follow-up to #915. Diagnostic logs added in this PR are the ones I used to localize the root cause; happy to drop them if reviewers prefer.