Authorization
Mesa combines relationship-based access control (ReBAC) and attribute-based access control (ABAC). Both run inside the application, against its own database. There is no separate authorization service to deploy, operate or keep in sync.
Every decision goes through one entry point, and failure is closed: a check that cannot be completed denies rather than allows.
The two models
Relationships
ReBAC answers whether a path exists between a subject and a resource:
user → member of → team → part of → tenant → owns → bot
It answers questions like "can this user reach this bot?" and "which bots does this tenant own?". Paths are resolved in the database with recursive queries, so a deep relationship costs one round trip rather than a walk from the application.
Attributes
ABAC applies typed conditions once a relationship permits something at all — the request's properties, not just its shape. "May this agent be invoked from this kind of chat?" is an ABAC question; "may this user reach this agent?" is a ReBAC one.
What is actually permissioned
Permissions are relations on a resource type, not job titles. There is no fixed ladder of "tenant admin / bot admin / user" roles:
| Resource | Actions |
|---|---|
| Bot | access, configure, own |
| Agent | invoke, configure |
| Tool | use, configure |
| Entity | view, edit |
| Platform channel | send, moderate, admin |
A person is not "a bot admin" globally; they hold the configure relation on particular bots. That
is what makes it possible for one user to administer one team's bots and merely use another's.
Channel moderation is separate from these relations — see Moderation.
Channel authorization, and the part worth reading twice
Which conversations a bot may participate in is controlled per tenant, and the model differs by platform in a way that matters for risk:
| Platform | Mode | What authorizing grants |
|---|---|---|
| Telegram (and Slack, Discord, Teams when built) | Membership | Authorize the channel, and every member of it can use the bot — including people who join later. Individuals can be banned. |
| Email (and SMS, WhatsApp when built) | Sender | Default closed. Each sender is whitelisted individually. |
The asymmetry is deliberate — anyone in the world can email an address, whereas being in a Telegram group is itself a membership decision someone already made — but it means authorizing a large Telegram group is a broader grant than it looks. Membership of that group becomes the access control, and it is administered in Telegram, not in Mesa.
Bans are the per-user escape hatch on the membership side.
Private 1:1 chats are the sender model on every platform, including the membership ones — a DM is never covered by a group's grant, and a group member cannot message a bot privately until granted individually. How those grants work has its own page.
Authorization groups
Groups let several channels be administered together, with the same bots assigned to all of them:
group "Customer support"
├── Telegram chat -1001234567890
├── Telegram chat -1009876543210
└── bots: [Support Bot, FAQ Bot]
Assignment is what makes a bot answer: an authorized channel with no bot assigned to it produces silence, and so does an unauthorized one. The two are distinguishable in the logs, and nowhere else — see Troubleshooting.
Moderation
Moderation is per channel and per tenant, and it is enforcement, not access design.
Bans are absolute: a ban overrides every grant, so a banned user is refused even where group membership or an individual private-chat grant would otherwise allow them. A user can be banned from a single channel or — in one action — from every channel the tenant has authorized. Each ban records when it was made, by whom and why, and unbanning restores exactly what the grants say.
Channel roles name each channel's own moderators and admins, per tenant. They are designations on the channel, not global titles — consistent with the rest of the model, being a moderator of one channel says nothing about any other.
Tenant scope is not negotiable
Two invariants hold across the whole API and are worth stating in an evaluation context:
- Tenant scope comes from the authenticated principal, never from client input. A caller cannot widen their own scope by naming another tenant.
- Requests reaching the API without a valid principal are refused. There is no pass-through when the identity provider is unconfigured; the guard fails closed rather than open.