Features
MarkdownAI tool approvals & audit
From externa-core 1.4.0, the in-app assistant and the MCP server share one permission-aware tool registry (#202). Tools that write or delete data are denied by default, need a human approval when enabled, and every tool decision lands in a redacted audit log (#203).
Related
Agent and per-action permissions: AI assistant model. Chat UI: AI chat. Env keys: AI configuration.
Tool kinds
Each registered tool has a kind (AiToolKindEnum):
| Kind | Tools |
|---|---|
read | DescribeFieldTypes, ExtractPdfText, GetImportJobStatus, QueryCollectionItems, ExportCollection, SearchSimilarCollectionItems, QueryActivityLogs |
write | ImportCollectionCsv, ImportRemoteJson |
destructive | ManageCollections, ManageCollectionItems, ManageAiSyncSources, ManageFiles, ManageUsers, ManageRoles, ManageGroups, RollbackLastAiTurn |
write and destructive tools are mutating. The Manage* tools mix read and write actions through their action argument (for example list / get vs create / delete).
Write gate
A mutating tool may run only when both are true:
- Kill switch on:
AI_WRITE_TOOLS=true(also accepted asAI_TOOLS_WRITE_ENABLED=true). Defaultfalse. - Tool on the allowlist: its class name is in
AI_TOOLS_WRITE_ALLOW(CSV) or selected under Settings → AI write tools (/settings/ai-tools, project settingai.write_tools, needscan-manage-project-settings). The two lists are merged. An empty allowlist denies every mutating tool.
AI_WRITE_TOOLS=true
AI_TOOLS_WRITE_ALLOW=ManageCollectionItems,ImportCollectionCsv
Three more keys filter tools for every kind, before the write gate:
| Env | Default | Effect |
|---|---|---|
AI_TOOLS_ENABLED | true | false turns off every AI tool in chat and MCP. |
AI_TOOLS_ALLOW | empty | When set, only these tool names are available at all. |
AI_TOOLS_DENY | empty | These tool names are never available (wins over ALLOW). |
The registry then checks the actor's permissions and each tool re-checks action-level permissions, as before.
Chat: read actions keep working
With the write gate closed (the default), chat still lists the Manage* tools as a read-only copy: list, get, search and other read actions run, while every write or destructive action is refused with an English machine message the model relays (#439). This keeps the 1.3.0 behaviour for reads. Pure write tools (ImportCollectionCsv, ImportRemoteJson) and RollbackLastAiTurn are not offered in chat until they are allowlisted.
When a tool is allowlisted and the kill switch is on, its write and destructive actions pause for the user's approval in the chat (laravel/ai Approvable) before they run. Examples of write actions: ManageCollectionItems create / update / publish / restore / bulk_update; ManageFiles create / rename / move / tag / save_attachment. Destructive: delete, force_delete, bulk_delete.
The assistant's approvals Agent Skill explains pending, rejected, and abandoned approvals to the user.
MCP: pending approvals
On the MCP surface, an allowed mutating call does not run. The executor stores a PendingToolCall and returns pending_approval to the MCP client.
| Item | Detail |
|---|---|
| Inbox | Settings → AI tool approvals (GET /settings/pending-tool-calls, permission can-use-ai) |
| Actions | POST /settings/pending-tool-calls/{id}/approve, …/reject |
| Approver | Must also pass the tool's permission check for those arguments, otherwise the approval is refused |
| Expiry | AI_TOOLS_APPROVAL_TTL minutes (default 15); overdue rows become expired and never run |
| Shown fields | Tool, redacted arguments, requester, expiry |
Approving runs the call once and records executed or failed. MCP write tools are listed only when AI_MCP_WRITE_ENABLED and the key's mcp:write scope allow it — see MCP server — Write tools.
Audit log
Every registry decision for a signed-in actor (allowed, denied, pending, executed) is written to the activity log with log_name=ai and event=ai_tool, including the tool, the decision, the status, and the surface (chat or mcp). Filter it in Activity log by the ai_tool event.
Arguments are redacted before they are logged or shown in the approval inbox. AI_TOOLS_REDACT_KEYS (CSV) sets the redacted keys. Default:
auth_bearer,auth_header,authorization,bearer,password,token,api_key,apikey,secret,client_secret,email,phone,telephone,ssn,social_security_number,credit_card,card_number,cvv
Retention: ai:prune-tool-audit
php artisan ai:prune-tool-audit
Scheduled daily in routes/console.php. It:
- expires overdue pending approvals;
- deletes
ai_toolactivity rows older thanAI_TOOLS_AUDIT_RETENTION_DAYS(default 90); - deletes resolved
pending_tool_calls(rejected, expired, executed, failed) older than the same window.
The scheduler must be running — see Queues & scheduler.
Environment summary
| Env | Default | Config |
|---|---|---|
AI_TOOLS_ENABLED | true | ai.tools.enabled |
AI_WRITE_TOOLS | false | ai.tools.write_enabled |
AI_TOOLS_WRITE_ENABLED | false | Fallback for AI_WRITE_TOOLS |
AI_TOOLS_WRITE_ALLOW | empty | ai.tools.write_allow |
AI_TOOLS_ALLOW | empty | ai.tools.allow |
AI_TOOLS_DENY | empty | ai.tools.deny |
AI_TOOLS_APPROVAL_TTL | 15 | ai.tools.approval_ttl_minutes |
AI_TOOLS_AUDIT_RETENTION_DAYS | 90 | ai.tools.audit_retention_days |
AI_TOOLS_REDACT_KEYS | see above | ai.tools.redact_keys |
Source map
| Concern | Location (externa-core) |
|---|---|
| Registry | app/Services/Ai/AiToolRegistryService.php |
| Executor | app/Services/Ai/AiToolExecutorService.php |
| Write gate | app/Services/Ai/AiToolWriteGateService.php |
| Approvals | app/Services/Ai/AiToolApprovalService.php, app/Models/PendingToolCall.php |
| Chat approval | app/Ai/Concerns/RequiresDestructiveApprovalTrait.php |
| Inbox UI | PendingToolCallController, resources/js/pages/admin/pending-tool-calls/index.tsx |
| Settings UI | AiToolSettingsController, resources/js/pages/settings/ai-tools.tsx |
| Prune command | app/Console/Commands/Ai/PruneAiToolAuditCommand.php |