Role-Based Access Control (RBAC)

Overview

Users are assigned to roles, and roles define permissions. A user's effective permissions are the union of all permissions from all their roles (OR logic). Permissions are loaded from the database on every request — changes take effect immediately.

Default Roles

Role

Description

Admins

Full access to everything (sinas.*:all)

Users

Create and manage own resources, chat with any agent, execute own functions

GuestUsers

Read and update own profile only

Permission Format

<service>.<resource>[/<path>].<action>:<scope>
<service>.<resource>[/<path>].<action>:<scope>
<service>.<resource>[/<path>].<action>:<scope>

Components:

Part

Description

Examples

Service

Top-level namespace

sinas, or a custom prefix like titan, acme

Resource

Resource type

agents, functions, states, users

Path

Optional namespace/name path

/marketing/send_email, /*

Action

What operation is allowed

create, read, update, delete, execute, chat

Scope

Ownership scope

:own (user's resources), :all (all resources)

Permission Matching Rules

Scope hierarchy: :all automatically grants :own. A user with sinas.agents.read:all passes any check for sinas.agents.read:own.

Wildcards can be used at any level:

Pattern

Matches

sinas.*:all

Everything in Sinas (admin access)

sinas.agents/*/*.chat:all

Chat with any agent in any namespace

sinas.functions/marketing/*.execute:own

Execute any function in the marketing namespace

sinas.states/*.read:own

Read own states in any namespace

sinas.chats.*:own

All chat actions (read, update, delete) on own chats

Namespaced resource permissions use slashes in the resource path:

sinas.agents/support/ticket-bot.chat:own        # Chat with specific agent
sinas.functions/*/send_email.execute:own         # Execute send_email in any namespace
sinas.states/api_keys.read:all                   # Read all shared states in api_keys namespace
sinas.agents/support/ticket-bot.chat:own        # Chat with specific agent
sinas.functions/*/send_email.execute:own         # Execute send_email in any namespace
sinas.states/api_keys.read:all                   # Read all shared states in api_keys namespace
sinas.agents/support/ticket-bot.chat:own        # Chat with specific agent
sinas.functions/*/send_email.execute:own         # Execute send_email in any namespace
sinas.states/api_keys.read:all                   # Read all shared states in api_keys namespace

Non-namespaced resource permissions use simple dot notation:

sinas.webhooks.create:own                        # Create webhooks
sinas.schedules.read:own                         # Read own schedules
sinas.users.update:own                           # Update own profile
sinas.webhooks.create:own                        # Create webhooks
sinas.schedules.read:own                         # Read own schedules
sinas.users.update:own                           # Update own profile
sinas.webhooks.create:own                        # Create webhooks
sinas.schedules.read:own                         # Read own schedules
sinas.users.update:own                           # Update own profile

Custom Permissions

The permission system is not limited to sinas.*. You can define permissions with any service prefix for your own applications:

titan.student_profile.read:own
titan.courses/math/*.enroll:own
acme.billing.invoices.read:all
myapp.*:all
titan.student_profile.read:own
titan.courses/math/*.enroll:own
acme.billing.invoices.read:all
myapp.*:all
titan.student_profile.read:own
titan.courses/math/*.enroll:own
acme.billing.invoices.read:all
myapp.*:all

These work identically to built-in permissions — same wildcard matching, same scope hierarchy. This lets you use Sinas as the authorization backend for external applications.

Checking Permissions from External Services

Use the POST /auth/check-permissions endpoint to verify whether the current user (identified by their Bearer token or API key) has specific permissions:

curl -X POST https://yourdomain.com/auth/check-permissions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "permissions": ["titan.student_profile.read:own", "titan.courses.enroll:own"],
    "logic": "AND"
  }'
curl -X POST https://yourdomain.com/auth/check-permissions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "permissions": ["titan.student_profile.read:own", "titan.courses.enroll:own"],
    "logic": "AND"
  }'
curl -X POST https://yourdomain.com/auth/check-permissions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "permissions": ["titan.student_profile.read:own", "titan.courses.enroll:own"],
    "logic": "AND"
  }'

Response:

{
  "result": true,
  "logic": "AND",
  "checks": [
    {"permission": "titan.student_profile.read:own", "has_permission": true},
    {"permission": "titan.courses.enroll:own", "has_permission": true}
  ]
}
{
  "result": true,
  "logic": "AND",
  "checks": [
    {"permission": "titan.student_profile.read:own", "has_permission": true},
    {"permission": "titan.courses.enroll:own", "has_permission": true}
  ]
}
{
  "result": true,
  "logic": "AND",
  "checks": [
    {"permission": "titan.student_profile.read:own", "has_permission": true},
    {"permission": "titan.courses.enroll:own", "has_permission": true}
  ]
}
  • logic: "AND" — User must have ALL listed permissions (default)

  • logic: "OR" — User must have AT LEAST ONE of the listed permissions

This makes Sinas usable as a centralized authorization service for any number of external applications.

Managing Roles

POST   /api/v1/roles                        # Create role
GET    /api/v1/roles                        # List roles
GET    /api/v1/roles/{name}                 # Get role details
PATCH  /api/v1/roles/{name}                 # Update role
DELETE /api/v1/roles/{name}                 # Delete role
POST   /api/v1/roles/{name}/members         # Add user to role
DELETE /api/v1/roles/{name}/members/{id}    # Remove user from role
POST   /api/v1/roles/{name}/permissions     # Set permission
DELETE /api/v1/roles/{name}/permissions     # Remove permission
GET    /api/v1/permissions/reference        # List all known permissions
POST   /api/v1/roles                        # Create role
GET    /api/v1/roles                        # List roles
GET    /api/v1/roles/{name}                 # Get role details
PATCH  /api/v1/roles/{name}                 # Update role
DELETE /api/v1/roles/{name}                 # Delete role
POST   /api/v1/roles/{name}/members         # Add user to role
DELETE /api/v1/roles/{name}/members/{id}    # Remove user from role
POST   /api/v1/roles/{name}/permissions     # Set permission
DELETE /api/v1/roles/{name}/permissions     # Remove permission
GET    /api/v1/permissions/reference        # List all known permissions
POST   /api/v1/roles                        # Create role
GET    /api/v1/roles                        # List roles
GET    /api/v1/roles/{name}                 # Get role details
PATCH  /api/v1/roles/{name}                 # Update role
DELETE /api/v1/roles/{name}                 # Delete role
POST   /api/v1/roles/{name}/members         # Add user to role
DELETE /api/v1/roles/{name}/members/{id}    # Remove user from role
POST   /api/v1/roles/{name}/permissions     # Set permission
DELETE /api/v1/roles/{name}/permissions     # Remove permission
GET    /api/v1/permissions/reference        # List all known permissions

Agent Execution & Permissions

Agents define which tools are available (via enabled_* fields), but the user's role permissions are checked at execution time. Both conditions must be met:

  1. Agent declares the tool — the resource is in the agent's enabledFunctions, enabledQueries, enabledStores, enabledCollections, or enabledAgents

  2. User has the permission — checked when the tool is actually called

If the user lacks permission, the tool call returns an error that the LLM can explain to the user. Tools are still visible to the LLM regardless — the check happens at execution, not discovery.

Resource

Agent config

Permission checked at execution

Functions

enabledFunctions

sinas.functions/{ns}/{name}.execute:own

Queries

enabledQueries

sinas.queries/{ns}/{name}.execute:own

Collections

enabledCollections

sinas.collections/{ns}/{name}.download:own (read) / .upload:own (write)

Stores

enabledStores

sinas.stores/{ns}/{name}.read_state:own / .write_state:own

Sub-agents

enabledAgents

sinas.agents/{ns}/{name}.chat:all

Connectors

enabledConnectors

Agent-gated only (no standalone execution path)

Connectors are the exception — they have no independent API and can only be accessed through an agent, so the agent's enabledConnectors (with per-operation filtering) is the sole access control.

Namespace wildcards make this manageable. Grant sinas.functions/sales/*.execute:own once on a role, and all functions in the sales/ namespace are accessible.

Was this helpful?

role-based-access-control-rbac