5.1 KiB
5.1 KiB
Chatbot V1 — User Allow-List, 8 Capabilities, Audit Log
Built on top of the MVP. V1 ships:
- 8 capabilities (MVP 3 +
data_read+ 4 high-risk). - Per-user override via the new
TenantChatbotAccesstable. - Audit-event-per-high-risk-call through
@toolctrl/db/audit-events.server. - Per-capability sliding-window rate-limits via
@toolctrl/shared-store.
Capability matrix
| Capability | Risk | Tool name | Notes |
|---|---|---|---|
qa_help |
low | search_help |
Bundled docs ranker. No DB. |
kb_search |
low | search_knowledge_base |
Tenant-scoped KB. Wraps results in ---KB ARTICLE--- markers. |
account_info |
low | get_account_info |
Whitelist projection. Never returns billing tokens / customer ids. |
data_read |
medium | read_entities |
Strict entity enum, tenant-scoped read. |
data_write |
high | write_entity |
Audit + rate-limit (30/min default). |
workflow_trigger |
high | start_workflow |
Audit + rate-limit (10/min default). |
admin_actions |
high | admin_action |
Audit + rate-limit (5/min) + OWNER/ADMIN role gate. |
external_tools |
high | invoke_external_tool |
MCP-tool registry. Audit + rate-limit (60/min default). |
Allow-list semantics
TenantChatbotAccess is a per-(tenantId, userId) override row.
- Master-flag
chatbotOFF → no capabilities. - Row exists with
enabled = false→ explicit deny (wins over any tenant default). - Row exists with
enabled = trueand non-emptycapabilityScopes→ those scopes. - Row exists with
enabled = trueand empty scopes → fall through to tenant default. - No row,
chatbotAccessMode = 'all_members'→Tenant.chatbotDefaultCapabilities. - No row,
chatbotAccessMode = 'allow_list'→ no capabilities.
Audit pipeline
auditChatbotToolCall writes through createAuditEvent. Action format:
chatbot.tool_call.<capability>.<toolName>. Payload uses an allow-list
projection (ALLOWED_ARG_KEYS); anything not on the list is dropped so
prompt-injection cannot exfiltrate secrets into the audit table.
Outcomes:
success— inner tool ran cleanly.failure— inner tool threw (rethrown to the SDK).blocked— rate-limit denied or role-check returnedforbidden.
Rate-limit defaults (sliding window, 60 s)
| Capability | Max / window |
|---|---|
qa_help |
1000 |
kb_search |
1000 |
account_info |
1000 |
data_read |
200 |
data_write |
30 |
workflow_trigger |
10 |
admin_actions |
5 |
external_tools |
60 |
Bucket key: chatbot:<capability>:<tenantId>:<userId|anon>. Backed by
the shared-store (Redis in prod, MemoryAdapter in tests).
Operator commands
# Toggle the master flag
toolctrl chatbot enable | disable | status
# Grant / revoke per-user access
toolctrl chatbot grant --tenant acme --email alice@example.com --capabilities=qa_help,kb_search,data_read
toolctrl chatbot revoke --tenant acme --email bob@example.com # disables override
toolctrl chatbot revoke --tenant acme --email bob@example.com --hard # removes the row
# Update tenant default
toolctrl chatbot set-capabilities --tenant acme --mode allow_list \
--capabilities qa_help,kb_search,account_info
Files
apps/web/app/modules/chatbot/lib/capabilities/{data-read,data-write,workflow-trigger,admin-actions,external-tools}.server.tsapps/web/app/modules/chatbot/lib/capabilities/{audit,rate-limit,high-risk-wrapper}.server.tsapps/web/app/modules/chatbot/lib/capabilities/resolver.server.ts— V1 lookup matrixapps/web/app/modules/chatbot/db/chatbot-access.db.server.tsapps/web/app/routes/app.$tenant/settings/chatbot.tsx— V1 settings UIpackages/cli/src/commands/chatbot/access.tsprisma/schema/accounts.prisma(TenantChatbotAccess,Tenant.chatbotAccessMode,Tenant.chatbotDefaultCapabilities)
Troubleshooting
- A user sees no chatbot tools even with override. Check the master
flag (
toolctrl chatbot status) and the row'senabledfield. admin_actionreturnsforbidden. Role gate. Only OWNER / ADMIN may run admin actions even with the capability in scope.rate_limitedreturned from a high-risk tool. Inspect thechatbot:<capability>:<tenantId>:<userId>bucket in Redis or wait the 60 s window.
Known limitations
- No confirmation UI step yet — the wrapper is ready to return a
{ ok: false, reason: 'awaiting_confirmation' }shape but the chat widget doesn't yet render it. external_toolsprovider is a stub. Phase 07 V2 will wire the MCP client into it.