Skip to content

fix(perms): propagate sender/role through MCP bridge & delegate (#915)#1105

Open
mozaa-solana wants to merge 1 commit intonextlevelbuilder:devfrom
mozaa-solana:fix/perms-bridge-delegate-915
Open

fix(perms): propagate sender/role through MCP bridge & delegate (#915)#1105
mozaa-solana wants to merge 1 commit intonextlevelbuilder:devfrom
mozaa-solana:fix/perms-bridge-delegate-915

Conversation

@mozaa-solana
Copy link
Copy Markdown

Summary

Group-scoped permission checks (CheckCronPermission / CheckFileWriterPermission) deny with senderID="" when tools are invoked through:

  1. MCP bridge (external CLI providers like claude-cli, opencode-go) — the bridge middleware never injected SenderID/Role into ctx, so even though the agent loop sets them on RunRequest, the bridge layer dropped attribution. Result: any group cron / file_writer mutation called via the bridge denies as "system context".
  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="".

Changes

  • internal/providers/claude_cli.go — new OptSenderID, OptRole.
  • internal/providers/claude_cli_mcp.goBridgeContext 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.
  • internal/providers/claude_cli_session.gobridgeContextFromOpts reads new opts.
  • internal/gateway/server.go (bridgeContextMiddleware) — read X-Sender-ID+X-Role headers (HMAC-protected), inject WithSenderID/WithRole into ctx.
  • internal/agent/loop_pipeline_callbacks.go — pass req.SenderID/req.Role into chatReq.Options.
  • cmd/gateway_managed.go (delegateRunFn) — propagate SenderID/Role/Channel/ChatID/PeerKind from DelegateRequest into RunRequest.
  • internal/tools/team_tool_dispatch.go + cmd/gateway_consumer_normal.goslog.Warn when 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 ./... clean
  • go test ./internal/providers/... ./internal/store/... ./internal/gateway/... ./internal/agent/... passes (incl. TestVerifyBridgeContext HMAC fallbacks)
  • Reproduced the cron-in-Telegram-group denial, applied this fix, retested, cron now works for whitelisted users
  • Backward compatibility verified: pre-existing MCP config sessions (without senderID/role headers) still match the older HMAC fallback in 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.

…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant