feat: propagate RBAC role through dispatch + bypass file-writer grant for admins

Dashboard users (and other tenant-authenticated admin/operator/owner
actors) dispatching team tasks to agents in Telegram/Discord groups
previously hit the empty-sender DENY rule when the assigned agent
tried to write files — because dashboard users have tenant identities
(e.g. "viettx") that don't match the numeric file-writer grants stored
per channel.

Two-part fix:

1. CheckFileWriterPermission / CheckCronPermission add an RBAC bypass
   at the store layer: if ctx carries RoleFromContext in {admin,
   operator, owner}, skip the per-user grant check. Tenant RBAC already
   pre-authenticated these callers at the gateway edge; requiring a
   redundant per-channel grant blocks legitimate admin work.

2. Role propagation through the full dispatch chain:
     ctx (RoleFromContext)
       → RunRequest.Role
       → SubagentTask.OriginRole / AnnounceMetadata.OriginRole
         / DelegateRequest.Role
       → InboundMessage.Metadata[MetaOriginRole]
       → subagentAnnounceRouting.Role / teammateRole
       → RunRequest.Role (re-ingress)
       → store.WithRole(ctx, ...) in loop_context
   Covers subagent announce, delegate announce, teammate dispatch
   (WS dashboard, tool), session_send, and processNormalMessage's
   synthetic-sender override path. team_tasks_create persists
   origin_role for deferred dispatches.

Viewer and empty roles fall through to the existing sender-based path
(no bypass).

Regression tests:
- A.10 AdminRoleBypass: admin/operator/owner in ctx allowed even with
  empty sender (normally DENY).
- A.11 ViewerRoleDoesNotBypass: viewer and empty roles still hit DENY.

Builds + integration + unit tests green.
This commit is contained in:
viettranx committed 2026-04-16 14:17:48 +07:00
1 parent 51cf103714
commit 6a9b747b65
15 files changed
+87 -6

No files matched your search

+6
View File
@@ -67,6 +67,12 @@ func (l *Loop) injectContext(ctx context.Context, req *RunRequest) (contextSetup
if req.SenderName != "" {
ctx = store.WithSenderName(ctx, req.SenderName)
}
// Inject caller role so RBAC-aware permission checks (CheckFileWriterPermission,
// CheckCronPermission) can bypass per-user grants for authenticated admins
// dispatched from dashboard or other trusted sources (#915).
if req.Role != "" {
ctx = store.WithRole(ctx, req.Role)
}
// Inject global + per-agent builtin tool settings (tier 1+3).
// Media/provider-chain tools read the merged view via BuiltinToolSettingsFromCtx.
if l.builtinToolSettings != nil {