Security
Role-based access control, authorization, and multi-layer security in Botovis
Botovis implements a multi-layer security model to protect your data. Every request passes through authentication, role resolution, permission checking, and write confirmation before any database operation is executed.
Security Layers
Request → Authentication → Role Resolution → Permission Check → Tool Execution → Write Confirmation- Authentication — User must be authenticated (configurable guard)
- Role Resolution — User's role is determined from the configured resolver
- Permission Check — The role's CRUD permissions are checked against the target table
- Schema Filtering — Only permitted tables/columns are visible in the AI's context
- Write Confirmation — Destructive operations require explicit user approval
Authentication Guard
'security' => [
'guard' => 'web',
'require_auth' => true,
],The guard setting specifies which Laravel authentication guard to use (default: 'web'). Set require_auth to false if you want to allow unauthenticated access.
If require_auth is true and no authenticated user is found, all requests return 401 Unauthorized.
Role Resolvers
Botovis needs to determine a user's role to apply permissions. The role_resolver config is a string that selects the resolver type, with separate config keys for each:
// Reads the role from a database column on the User model
'role_resolver' => 'attribute',
'role_attribute' => 'role', // $user->role// Calls a method on the User model
'role_resolver' => 'method',
'role_method' => 'getRole', // $user->getRole()// Uses spatie/laravel-permission package
'role_resolver' => 'spatie',
// Uses $user->getRoleNames()->first()// Uses a custom callback function
'role_resolver' => 'callback',
'role_callback' => function ($user) {
if ($user->is_super_admin) return 'admin';
if ($user->department === 'analytics') return 'analyst';
return 'viewer';
},Role-Based Permissions
Each role maps tables to their allowed CRUD actions:
'roles' => [
// Wildcard role — applies to all users by default
'*' => [
'*' => ['create', 'read', 'update', 'delete'], // All actions on all tables
],
// Admin — full access to everything
'admin' => [
'*' => ['create', 'read', 'update', 'delete'],
],
// Analyst — read-only access to all tables
'analyst' => [
'*' => ['read'],
],
// Sales — mixed permissions per table
'sales' => [
'orders' => ['create', 'read', 'update'],
'customers' => ['read'],
'products' => ['read'],
],
// Viewer — read-only on specific tables
'viewer' => [
'products' => ['read'],
'categories' => ['read'],
],
],Permission Rules
- Roles are matched by the user's resolved role name
- Use
'*'as a table key to grant access to all whitelisted models - CRUD actions:
create,read,update,delete - The wildcard role
'*'is the fallback when no specific role matches - Table names must match the actual database table names (not model names)
- A table not mentioned in a role's config is completely invisible to that user
Write Confirmation
'require_confirmation' => ['create', 'update', 'delete'],This setting lists which action types require explicit user confirmation before execution. The default requires confirmation for all write operations.
When a confirmed action is triggered:
- The agent detects a write tool call
- The agent pauses and sends a confirmation event to the widget
- The widget shows the user what will be modified, with Confirm and Reject buttons
- Only after the user confirms does the tool actually execute
- If rejected, the agent receives the rejection and responds accordingly
Set to an empty array [] to disable confirmation entirely.
Strongly Recommended
Keep at least ['create', 'update', 'delete'] in production. Removing action types means the AI can perform those operations without human approval.
Laravel Gates Integration
Enable gate-based authorization:
'use_gates' => true, // default: falseWhen enabled, Botovis checks Laravel Gates for each table/action combination using the botovis.{table}.{action} naming convention:
// In AuthServiceProvider or a Gate file
Gate::define('botovis.orders.read', function ($user) {
return $user->hasVerifiedEmail() && $user->is_active;
});
Gate::define('botovis.orders.create', function ($user) {
return $user->hasRole('admin');
});You can also add a global gate check to your Botovis route middleware:
// config/botovis.php
'route' => [
'middleware' => ['web', 'auth', 'can:use-botovis'],
],Schema Filtering
Botovis automatically filters the database schema based on the current user's permissions:
- A viewer with access to
['products', 'categories']will only see those two tables in the AI's context - The AI literally cannot reference tables the user doesn't have access to — they don't exist in its schema
- Relationships pointing to inaccessible tables are excluded
- This filtering happens at the
SecurityContextlevel before any LLM interaction
SecurityContext
The SecurityContext DTO is built for every request and carries:
SecurityContext {
?string $userId // Authenticated user's ID
?string $userRole // Resolved role name
array $allowedTables // Tables visible to this user
array $permissions // CRUD permissions per table
array $metadata // Additional context (extensible)
}This object is passed through the entire request lifecycle — from schema filtering to tool execution to the agent loop. The BotovisAuthorizer builds this context using the buildContext() method.
Custom Authorizer
For complete control over authorization, implement the AuthorizerInterface:
use Botovis\Core\Contracts\AuthorizerInterface;
use Botovis\Core\DTO\SecurityContext;
class CustomAuthorizer implements AuthorizerInterface
{
public function buildContext(): SecurityContext
{
$user = auth()->user();
return new SecurityContext(
userId: (string) $user->id,
userRole: $this->determineRole($user),
allowedTables: $this->getAllowedTables($user),
permissions: $this->getPermissions($user),
metadata: ['source' => 'custom'],
);
}
public function authorize(SecurityContext $context, string $action, string $table): bool
{
// Your custom authorization logic
return in_array($action, $context->permissions[$table] ?? []);
}
public function filterSchema(SecurityContext $context, array $schema): array
{
// Filter schema based on permissions
return array_filter($schema, fn($table) =>
in_array($table, $context->allowedTables)
);
}
}Register it in a service provider:
$this->app->singleton(AuthorizerInterface::class, CustomAuthorizer::class);Best Practices
- Start restrictive — Use the wildcard
*role with minimal permissions, then add specific roles as needed - Always require confirmation — Keep
['create', 'update', 'delete']in therequire_confirmationarray in production - Use the model whitelist — Only register models that should be accessible via the AI
- Audit access — Monitor which users are making which queries through your application logs
- Guard your routes — Add appropriate middleware (authentication, rate limiting, etc.)
- Test role resolution — Use
botovis:discoverto see what each role can access - Separate environments — Consider different configurations for development vs. production