Skip to content

MM-65671: Agents CRUD, legacy bot migration, and bot account sync#589

Merged
nickmisasi merged 66 commits into
masterfrom
MM-65671
Apr 21, 2026
Merged

MM-65671: Agents CRUD, legacy bot migration, and bot account sync#589
nickmisasi merged 66 commits into
masterfrom
MM-65671

Conversation

@nickmisasi
Copy link
Copy Markdown
Collaborator

@nickmisasi nickmisasi commented Apr 2, 2026

Summary

Implements user agent CRUD improvements, migration of legacy config-defined bots into user agents, and synchronization of Mattermost bot accounts when agents are updated or deleted (display name patch, bot deactivation). Updates the agents UI, system console wiring, i18n strings, and adds e2e coverage plus a small permissions helper.

image image image image image image image image image

Tools list on RHS is based on allowed tools for selected agent
image
Vs an agent with no tools enabled
image

Ticket Link

Jira https://mattermost.atlassian.net/browse/MM-65671

Release Note

NONE

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Full Agents product: REST APIs and UI to create/edit/delete agents (avatar upload), access controls, service & model selection, per-agent MCP tool controls, delegated admins, license gating, Agents product page and nav integration, and RHS/tool-picker respect per-agent allowlists.
  • Migrations

    • One-time migration of legacy bots into Agents and new Agents DB table + persistence APIs.
  • Tests

    • Expanded unit, integration, and Playwright E2E coverage for agents, access control, MCP tools, provider/config flows, and migrations.

nickmisasi and others added 29 commits March 31, 2026 13:02
Detailed implementation plan for Agents_UserAgents table, Morph migration,
CRUD store methods, and integration tests. Includes exact file paths,
line numbers, code snippets, and verification commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 of self-service agent creation: Agents_UserAgents table with
Morph migration 000004, full CRUD store methods with JSON slice field
marshaling, AgentStore interface in api package, and comprehensive
integration tests (12 test functions covering round-trips, soft delete,
edge cases, and concurrency).

Also fixes setupTestStore to set search_path via connection string so
concurrent test goroutines use the correct schema, and updates migration
count assertion from 3 to 4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements Phase 2 of self-service agent creation:
- Agent CRUD handlers (create, list, get, update, delete) with license gating
- Bot account lifecycle (create on agent create, deactivate on delete)
- Avatar upload endpoint for agent profile images
- Services listing endpoint (secrets stripped)
- Permission-based access control (create_agent permission, creator/admin checks)
- Partial update support via pointer fields in UpdateAgentRequest
- Comprehensive test coverage (9 test functions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements Phase 3 of self-service agent creation:
- AgentStore interface in bots package for loading user agents from DB
- userAgentToBotConfig converter maps UserAgent to llm.BotConfig
- EnsureBots merges DB-backed agents into the live bot registry
- forceRefresh flag bypasses optimistic config-equality cache for agent CRUD
- clusterEventAgentUpdate event propagates agent changes to HA nodes
- ClusterAgentNotifier interface + refreshBotsAndNotify helper in API
- Agent create/update/delete handlers trigger registry refresh + cluster notify
- Runtime tests: converter, registry lookup, force-refresh flag
- Nil config guard in EnsureBots for test safety

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add TypeScript types for UserAgent, CreateAgentRequest, UpdateAgentRequest, ServiceInfo
- Add agent CRUD API client functions (getAgents, createAgent, updateAgent, deleteAgent, uploadAgentAvatar, getServices)
- Add Redux agents reducer for state caching
- Create AgentsPage root component with URL-based visibility at /plug/mattermost-ai/agents
- Create AgentRow component with avatar, name, tool badge, and actions menu
- Create AgentsList component with All/Your agents tabs, loading/error/empty states, and delete flow
- Create DeleteAgentDialog confirmation modal
- Create AgentsLicenseGate for E20+ license enforcement
- Register root component and main menu navigation entry point

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. getServices() was calling /agents/services but the backend route is
   /services on the base router — the gin router would match /agents/services
   against /:agentid with agentid="services" → 404.

2. EnabledTool TS type had mismatched fields (type/id/name/serverName) vs
   Go struct (server_origin/tool_name). Aligned to match backend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 5 implementation: three-tab modal for creating/editing agents.
- Config tab: display name, username, avatar, service selection, custom instructions
- Access tab: channel access, user access, agent admin controls
- MCPs tab: per-server and per-tool toggle switches with search
- Wire modal into agents_list.tsx for create and edit flows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Go UserAgent struct uses snake_case JSON tags (bot_user_id,
creator_id, display_name, etc.) but the TS type used camelCase,
causing JSON deserialization to silently drop all fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire UserAgent.EnabledTools into the tool discovery pipeline so
DB-backed agents only expose their selected MCP tools. Adds
EnabledMCPTool type to BotConfig, convertEnabledTools mapping,
RetainOnlyMCPTools filter on ToolStore, and the wiring call in
getToolsStoreForUser. Nil = all tools allowed (backward compat),
empty = no MCP tools, populated = allowlist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and MCP tools

Phase 7: Creates agent container factory, API helper, page object,
and three test spec files covering create/edit/delete flows, user
access level enforcement, and MCP tool selection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Search input on listing page filters by display name and username
- Field-level validation errors in config modal with clear-on-edit
- Server-side 409 (username taken) mapped to inline field error
- Service deleted edge case: warning badge in agent row, fallback
  option in service dropdown
- MCP server removed edge case: orphaned tools warning banner with
  auto-cleanup on tab load

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ServiceID stores UUIDs (36 chars) but was defined as VARCHAR(26),
causing POST /agents to fail with a Postgres truncation error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
navigateToAgentsPage() was pushing /plug/mattermost-ai/agents without
the team prefix, causing Mattermost's router to treat it as an unknown
team and redirect to "Team Not Found". Now extracts the current team
name from the URL path and builds the full team-scoped route.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use registerProduct (internal MM API) instead of registerMainMenuAction
so "Agents" appears in the product switcher grid icon rather than the
team dropdown menu. Simplify AgentsPage to a normal component — routing
is handled by registerProduct, so no URL-matching overlay is needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reverts the registerProduct change (internal API not reliable) and
restores the original rootComponent overlay + registerMainMenuAction
approach. Fixes two issues in the original code:

1. navigateToAgentsPage() now extracts the current team name from the
   URL and navigates to /${teamName}/plug/mattermost-ai/agents
2. AgentsPage visibility check uses includes() instead of endsWith()
   to match team-prefixed URLs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation errors

Add pre-validation for username format (must match ^[a-z][a-z0-9._-]*$)
returning 400 for invalid usernames. Detect duplicate username errors from
bot creation and return 409 Conflict instead of 500.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add app__body and channel-view CSS classes on mount in AgentsPage,
matching what Playbooks and Boards do in their product components.
ChannelController normally sets these classes but isn't loaded in
product views, causing the header to render with a white background.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SetEnabledToolsFromJSON was treating "[]" the same as "" (empty string),
mapping both to nil. This destroyed the nil-vs-empty distinction:
- nil EnabledTools = all tools allowed (config-defined bots)
- [] EnabledTools = no tools allowed (user disabled all toggles)

When an agent was saved with no MCP tools enabled, the DB stored "[]"
but on read it was converted to nil, bypassing the RetainOnlyMCPTools
filter in llm_context.go and allowing all tools at runtime.

Fix: only map "" (empty DB column from pre-feature agents) to nil.
Let json.Unmarshal handle "[]" → empty slice and "null" → nil correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Tools popover in the RHS chat header was showing ALL available MCP
tool providers regardless of the selected agent's configured tools.

Changes:
- Backend: Add EnabledMCPTools to AIBotInfo so /ai_bots exposes each
  bot's allowed MCP tools to the frontend
- Frontend: Add enabledMCPTools to LLMBot interface, pass it to
  ToolProviderPopover, and filter the server list so only servers with
  at least one enabled tool are shown
- Config-defined bots (null enabledMCPTools) continue to show all tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rness

- Register tests/agents/*.spec.ts in e2e-shard-3; validate ci-test-groups
- crud: duplicate username text, search no-results, regular user listing, unprivileged 403 UI
- access-control: allowlist negative, UserAccessLevel None listing, delegated admin edit
- mcp-tools: RHS tool provider cases; longer container beforeAll timeouts
- api: RHS MCP filtering + tests; ai-plugin helpers for Tools menu

Made-with: Cursor
…rules)

- MattermostPage: assert DM outcomes via bot posts (Client4) instead of thread reply UI
- access-control: use expectNoBotDmReplyFromApi / expectBotDmReplyFromApi
- openai-mock: buildChatCompletionMockRule for ordered body matchers
- mcp-tools: layered Smocker rules to tie responses to read_post in completion payload

Made-with: Cursor
- expectNoBotDmReplyFromApi: poll until observeDurationMs elapses (default 45s, matches positive reply timeout)
- Remove early return after minObserve; extend block/allowlist-negative tests to 120s

Made-with: Cursor
Ensure the positive enabled-tools flow only passes after a mocked tool-call round trip, so the suite proves runtime MCP execution instead of just mocked response text.

Made-with: Cursor
…migration

- Persist model, vision, tools, native tools, reasoning, structured output on user agents (DB, API, runtime bot mapping).
- Expose ServiceInfo fields and POST /agents/models/fetch for server-side model listing.
- Agents modal Configuration tab: full parity with legacy bot form; MCPs tab disabled when tools off.
- Structured output vs extended thinking: mutual exclusion, restore reasoning when structured off, inline note.
- Modal scroll fixes (min-height) for long config forms; service selection hint for advanced options.
- System Console: replace AI Bots editor with moved notice + link; default bot from getAIBots().
- Idempotent startup migration from config.bots to DB agents; sysadmin can manage migrated agents (empty creator).
- Tests: API/store, E2E crud and system console; skip legacy bot UI specs pending agents coverage.
- Export NativeToolsItem from bot.tsx for reuse.

Made-with: Cursor
Add agent-builder coverage for the provider-specific settings that moved out of the system console, and rerun legacy config-bot migration after later config updates so seeded bots still appear in the UI.

Made-with: Cursor
Apply fixes from CodeRabbit review: add username validation on agent
update, guard against nil config in service validation, bound avatar
upload size (10MB / 413), align SetEnabledNativeToolsFromJSON empty-slice
semantics, remove legacy bot-reasoning-config spec (coverage moved to
provider-config), add optional chaining in e2e afterAll teardown, and
improve webapp accessibility (avatar alt text, dialog ARIA attributes).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extend user agent model, API, store, and bots integration
- Migrate legacy config bots to user agents on plugin enable
- Sync Mattermost bot display name and deactivate on agent update/delete
- Update agents UI, system console config, and i18n strings
- Add permission helper and e2e agent CRUD coverage
- Fix golangci errcheck and shadow issues in agent handlers and migration

Made-with: Cursor
Remove .planning/phase-1/PLAN.md from version control (keep local copy).
.gitignore: use .planning pattern for the planning directory.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds DB-backed self-service AI agents with REST endpoints, migrations, bots registry integration and cluster events, per-agent MCP tool allowlists, frontend Agents UI and client APIs, E2E test suites and helpers, and assorted plumbing and dependency updates.

Changes

Cohort / File(s) Summary
API & Handlers
api/api.go, api/api_agents.go, api/api_admin_test.go, api/api_agents_test.go
Introduces AgentStore and ClusterAgentNotifier interfaces; extends API constructor; registers authenticated, license-gated /agents and /services endpoints (CRUD, avatar, fetch models); adds extensive handler logic and tests.
Store & Migrations
store/agents.go, store/agents_test.go, store/migrations/000004_create_user_agents_table.*, store/migrations/000005_user_agent_bot_fields.*, store/store_test.go
Adds Agents_UserAgents table and migrations; implements Store CRUD with JSON-backed slice fields, soft-delete semantics, mappers, and comprehensive tests; test harness updated for schema.
Domain Models & Conversion
useragents/model.go, llm/configuration.go
Adds UserAgent and EnabledTool models with DB JSON helpers; adds llm.EnabledMCPTool and BotConfig.EnabledMCPTools (nil vs empty semantics) for per-agent MCP allowlists.
Bots Registry & Sync
bots/bots.go, bots/bots_test.go, bots/agents_test.go, bots/permissions_test.go
MMBots accepts an AgentStore; adds ForceRefreshOnNextEnsure(); EnsureBots() loads DB-backed agents, converts to bot configs (including MCP tools) and resets force-refresh; tests include failing-agent-store case and agent-backed usage restrictions.
Cluster Events & Migration
server/cluster_events.go, server/legacy_bot_migration.go, server/main.go
Adds agent_update cluster event and generic publisher; implements distributed migration of legacy config bots into UserAgents with mutex/flagging; activation runs migration → refresh → publish agent update.
LLM / MCP Tooling
llm/tools.go, llm/tools_test.go, llmcontext/llm_context.go, llmcontext/llm_context_test.go
Adds ToolStore.RetainOnlyMCPTools and integrates allowlist filtering into LLM context builder; normalizes server origins and adds tests for filtering/normalization.
Server Config API
config/config.go, server/cluster_events.go
Adds StorePersistedConfigWithoutNotify to update in-memory persisted config without notifying listeners; refactors cluster event publishing to support agent events.
Frontend: Agents UI & Client
webapp/src/components/agents/*, webapp/src/client.tsx, webapp/src/types/agents.ts, webapp/src/index.tsx, webapp/src/utils/permissions.ts, webapp/src/bots.tsx, webapp/src/i18n/en.json
Adds Agents product route/page and components (list, modal, tabs, MCPs UI, row, delete dialog, license gate), client APIs for agents/services/models, TS types, permission helper, i18n strings, and integrates enabled-MCP-tools into RHS/tool-provider UI.
System Console & Notices
webapp/src/components/system_console/*, e2e/helpers/system-console.ts
Replaces legacy inline bot editor with BotsMovedNotice; fetches runtime bots for default-bot dropdown; exports native-tools item; updates console flows to point to Agents page.
E2E Helpers & Tests
e2e/helpers/*, e2e/tests/agents/*, e2e/tests/system-console/*, e2e/scripts/ci-test-groups.mjs
Adds E2E helpers (agent API, container, page object, OpenAI mock, mm client assertions), many agent-focused Playwright suites (access control, CRUD, MCP tools, provider config), retires/skips legacy System Console suites, and updates CI shard assignments.
OpenAI Mock & Test Utilities
e2e/helpers/openai-mock.ts, e2e/helpers/agent-api.ts, e2e/helpers/agent-container.ts, e2e/helpers/agent-page.ts, e2e/helpers/ai-plugin.ts, e2e/helpers/mm.ts
New helpers for chat-completion mock rules, agent CRUD helpers, test container provisioning, Agents page object, RHS tool interactions, and API-based DM assertion utilities.
Test Harness & Call Sites
api/api_test.go, api/api_admin_test.go, conversations/*, many tests
Updated test harness and numerous tests to match changed constructor arity and added agentStore wiring (many calls adjusted with extra nil parameters).
Misc & Build
go.mod, .gitignore
Bumps Go dependencies and adds replace directives; .gitignore rule changed from excluding .planning/ to .planning.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant API
  participant Store
  participant BotRegistry
  participant Cluster

  Client->>API: POST /agents (CreateAgentRequest)
  API->>Store: CreateAgent(agent)
  Store-->>API: created agent (ID)
  API->>BotRegistry: create/update Mattermost bot account
  BotRegistry-->>API: bot_user_id
  API->>Cluster: PublishAgentUpdate()
  Cluster->>BotRegistry: ForceRefreshOnNextEnsure() & EnsureBots()
  BotRegistry-->>Cluster: EnsureBots result
  API-->>Client: 201 Created (agent)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

Setup Cloud Test Server

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch MM-65671

…RY Responses API

- Skip deactivating plugin-owned Mattermost bots that still map to active DB
  agents when EnsureBots cannot reconcile them (missing/invalid service).
- Centralize ServiceUsesResponsesAPI on llm.ServiceConfig; bifrost and API use it.
- refreshBotsAndNotify returns EnsureBots error; update/delete handlers only
  apply explicit Bot.Patch / UpdateActive when EnsureBots did not run, failed,
  or no bot registry is configured.

GitHub: replied on PR #589 to Cursor auth threads (handlers use canConfigureAgentServices).
Made-with: Cursor
@mm-cloud-bot
Copy link
Copy Markdown

Test server creation failed. Review the error details here.

Comment thread api/api_agents.go
Agent list/get routes call canUserAccessAgent without aiBotRequired; dereferencing
a.bots for usage checks could panic when the bot registry is nil. Evaluate
restrictions with the plugin client instead; MMBots delegates to the same helper.

Made-with: Cursor
@mm-cloud-bot
Copy link
Copy Markdown

Test server creation failed. Review the error details here.

@mm-cloud-bot
Copy link
Copy Markdown

Test server creation failed. Review the error details here.

Copy link
Copy Markdown

@edgarbellot edgarbellot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you Nick!

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a92efcb. Configure here.

Comment thread api/api_config.go Outdated
Comment thread api/api_agents.go
…ls/fetch

- Avoid mutating cached Services when normalizing OpenAI UseResponsesAPI for the response.
- Use ShouldBindJSON in handleFetchModelsForService for consistent error handling.
- Add regression test for GET not mutating stored service config.

Made-with: Cursor
@mm-cloud-bot
Copy link
Copy Markdown

Test server creation failed. Review the error details here.

plugin-tests failed on TestAgentUpdate when CreateAgent and UpdateAgent
shared the same millisecond: assert.Greater(UpdateAt) failed. Bump UpdateAt
to at least previous+1 when the clock has not advanced.

Made-with: Cursor
@mm-cloud-bot
Copy link
Copy Markdown

Test server creation failed. Review the error details here.

@nickmisasi nickmisasi removed 2: Dev Review Requires review by a developer 3: Security Review Review requested from Security Team labels Apr 21, 2026
Update the live service flow to reset agent tool defaults after creation so the DM verification exercises plain replies instead of hanging on pending tool calls.

Made-with: Cursor
@mm-cloud-bot
Copy link
Copy Markdown

Test server creation failed. Review the error details here.

Normalize formatting in api_agents.go so the Go lint/style checks pass cleanly.

Made-with: Cursor
@mm-cloud-bot
Copy link
Copy Markdown

Test server creation failed. Review the error details here.

@mattermost-build
Copy link
Copy Markdown
Collaborator

Test check 0baf37d — action needed

Core agent CRUD, store, bots, permissions, bifrost, and config are well-tested across 18 unit and 4 new E2E spec files, but the 137-line legacy bot migration (server/legacy_bot_migration.go) has no unit tests despite complex idempotency, deferred-migration, and all-or-nothing logic.
Add unit tests for migrateLegacyConfigBotsToUserAgents covering: already-migrated idempotency, empty config bots no-op, deferred migration when MM bot rows are absent, happy-path agent creation + config cleanup, and username de-dup skipping.

Note: Max files limit reached: analyzed 50 of 51 source files and 42 of 42 test files.

More details (truncated)
Test Files Detected
Category Count Files
Unit 18 api/api_admin_test.go, api/api_agents_test.go (NEW), api/api_config_test.go, api/api_test.go, bifrost/bifrost_test.go, bifrost/config_test.go, bots/agents_test.go (NEW), bots/bots_test.go, bots/permissions_test.go, conversations/conversations_test.go, conversations/direct_message_eval_test.go, conversations/handle_messages_test.go, conversations/tool_handling_test.go, llm/configuration_responses_api_test.go, llm/tools_test.go, llmcontext/llm_context_test.go, store/agents_test.go (NEW), store/store_test.go
Integration 0 (none distinct from unit)
E2E 11 e2e/tests/agents/access-control.spec.ts (NEW), e2e/tests/agents/crud.spec.ts (NEW), e2e/tests/agents/mcp-tools.spec.ts (NEW), e2e/tests/agents/provider-config.spec.ts (NEW), e2e/tests/action-item-extraction/follow-ups.spec.ts, e2e/tests/smart-reactions/basic-reactions.spec.ts, e2e/tests/system-console/bot-management-ui.spec.ts, e2e/tests/system-console/bot-native-tools.spec.ts, e2e/tests/system-console/bot-reasoning-config.spec.ts, e2e/tests/system-console/bot-validation.spec.ts, e2e/tests/system-console/initial-state-navigation.spec.ts, e2e/tests/system-console/live-service-full-flow.spec.ts
Analysis

This is a large feature PR adding full agent CRUD with DB persistence, legacy bot migration, access controls, MCP tool allowlisting, and a rich UI. Test coverage is substantial across most layers:

Well-covered areas:

  • API layer (api/api_agents_test.go): 20+ test functions covering create/list/get/update/delete handlers, all permission variants (ManageOwnAgent, ManageOthersAgent, ManageSystem, creator, agent admin, migrated-bot ManageSystem path), free-tier quota enforcement, MCP tool allowlist/auto-enable combinations, username-change prevention, service ID validation, service listing secret-stripping, models-fetch auth and error cases, JSON serialization round-trips, and canUserAccessAgent admin bypass.

  • Store layer (store/agents_test.go): Full CRUD test suite using real SQLite DB — create-and-get with all field round-trips (JSON slices, booleans, timestamps), non-existent-get returns nil, list returns only active (soft-delete excluded), list-by-creator filtering, update bumps UpdateAt, soft-delete idempotency, CountActiveAgents.

  • Bots layer: bots/agents_test.go verifies DB-backed bot lookup by all me

...truncated. View full analysis details

Suggestions
The primary gap is server/legacy_bot_migration.go. Add unit tests covering at minimum:

  1. Already-migrated (idempotency): Set legacyConfigBotsMigratedKey = "true" — function should return (false, nil) without touching the store.
  2. No config bots: dbCfg.Bots is empty — function should return (false, nil) and NOT set the migration flag.
  3. MM bot not yet created (deferred): Config has a bot whose username doesn't appear in the MM bot list — function should return (false, nil) without partial writes.
  4. Happy path: Config has bots with matching MM bot rows — agents are created, config.Bots is cleared, migration flag is set, returns (true, nil).
  5. Already-migrated username (de-dup): Bot already exists in ListAgents result — should skip create for that bot and proceed with others.
    Additionally, consider a test for StorePersistedConfigWithoutNotify (nil config case vs valid config) and a unit test for the handleUploadAgentAvatar 10 MB cap.

To override, comment /test-analysis-override <reason> after verifying tests are adequate or not required.

@nickmisasi
Copy link
Copy Markdown
Collaborator Author

/test-analysis-override chimed in at the 11th hour after passing all day. Need to merge this for RC, so will address gaps in follow up PR.

@nickmisasi nickmisasi merged commit 0796cf7 into master Apr 21, 2026
35 checks passed
@nickmisasi nickmisasi deleted the MM-65671 branch April 21, 2026 18:21
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale comment

Security Review — No Findings

Reviewed all new and modified server-side code (agent CRUD API, store layer, bot registry, permissions, legacy migration, LLM context/tool filtering, configuration, cluster events) and frontend TypeScript for this PR at commit 0baf37d.

Changes since last review (ae6bc1b0baf37d): License gating replaced with per-creation quota (checkAgentCreateQuota), request body size limits added (MaxAgentRequestBodyBytes), BotConfig.Validate() with MaxCustomInstructionsRunes cap, sanitizeAgentForUser strips CustomInstructions from non-admin responses, config clone on GET to prevent shared state mutation, UsageRestrictionsForUserConfig refactored to standalone function (nil-safe), EnsureBots protects active DB agents from deactivation when service is invalid.

Key security properties verified:

  • Authentication: All agent CRUD endpoints sit behind MattermostAuthorizationRequired middleware, which reads the server-injected Mattermost-User-Id header. No identity from request body or query params.
  • Authorization: Create requires PermissionManageOwnAgent or PermissionManageSystem plus quota check. Update/delete requires canManageAgent() (creator, admin list, PermissionManageOthersAgent, or PermissionManageSystem for ownerless migrated bots). List/get filter by canUserAccessAgent() with safe default (default: return false). Service/model endpoints require canConfigureAgentServices().
  • Data sanitization: sanitizeAgentForUser() strips CustomInstructions from responses for non-manager users, preventing disclosure of sensitive organizational procedures.
  • Secrets protection: ServiceInfo struct explicitly excludes API keys, AWS secrets, URLs, and credentials. handleFetchModelsForService uses admin-configured credentials server-side via serviceID lookup — no user-controlled URLs (no SSRF).
  • SQL injection: All store queries use parameterized placeholders ($1$26).
  • Input validation: Username regex, BotConfig.Validate() (field presence, enum range, custom instructions length cap at 16384 runes), service ID existence check against config, username immutability on update, request body size cap (512 KiB), avatar size cap (10 MB via io.LimitReader).
  • Immutable fields: UpdateAgent SQL excludes ID, CreatorID, BotUserID, CreateAt, DeleteAt.
  • MCP tool filtering: RetainOnlyMCPTools preserves built-in tools (empty ServerOrigin) and filters MCP tools by per-agent allowlist. Auth errors filtered consistently. Nil allowlist = no filtering (backward compat).
  • Legacy migration: Cluster mutex, idempotent flag, defers when prerequisites are missing.
  • Error handling: gin.AbortWithError stores errors for ginlogger but does not write details to HTTP response bodies.

No medium, high, or critical vulnerabilities identified.

Open in Web View Automation 

Sent by Cursor Automation: Find vulnerabilities

@cursor cursor Bot restored the MM-65671 branch April 21, 2026 18:28
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review — PR #589

Scope: Agents CRUD endpoints, permissions model, legacy bot migration, store layer, config exposure, and webapp permissions.

Summary

No medium, high, or critical vulnerabilities found. The PR implements a well-structured authorization model with proper security controls throughout.

Areas Reviewed

Authentication & Identity

  • All new agent endpoints sit behind MattermostAuthorizationRequired middleware, which validates the server-injected Mattermost-User-Id header. User identity is never taken from request body or query params. ✅

Authorization

  • Create: Gated by PermissionManageOwnAgent or PermissionManageSystem via canCreateAgent().
  • Read (list/get): Filtered by canUserAccessAgent() which delegates to UsageRestrictionsForUserConfig for user-scope access checks.
  • Update/Delete: Gated by canManageAgent() requiring agent admin status (creator or delegated), PermissionManageOthersAgent, or PermissionManageSystem for legacy bots.
  • Services/Models: canConfigureAgentServices() requires agent management permissions.
  • Immutable fields (ID, CreatorID, BotUserID, CreateAt, DeleteAt) are preserved from the database record and not modifiable via the update endpoint. ✅

Data Sanitization

  • sanitizeAgentForUser() strips CustomInstructions from agent configs returned to non-admin users, preventing exposure of sensitive organizational procedures.
  • The /services endpoint returns a ServiceInfo projection that explicitly excludes API keys, AWS credentials, and other secrets. ✅

SQL Injection

  • All store queries use parameterized placeholders ($1, $2, etc.). No string concatenation of user input into SQL. ✅

Input Validation

  • Request body size capped at 512 KiB via MaxBytesReader.
  • Username validated against ^[a-z][a-z0-9._-]*$ regex.
  • ServiceID validated against existing admin-configured services.
  • BotConfig.Validate() enforces field constraints including access level ranges and CustomInstructions length limits. ✅

Secret Exposure

  • handleGetConfig (returns full config with API keys) is behind admin-only middleware (PermissionManageSystem).
  • handleFetchModelsForService uses stored service credentials server-side and returns only model metadata, never the credentials themselves.
  • Error messages from external API calls are logged server-side via gin's error handling and do not leak into HTTP response bodies. ✅

SSRF

  • handleFetchModelsForService only accepts a serviceID referencing admin-configured services; users cannot supply arbitrary URLs.
  • The admin-only handleFetchModels endpoint accepts user-supplied URLs but is restricted to system admins. ✅

Migration Safety

  • migrateLegacyConfigBotsToUserAgents uses a cluster mutex and idempotency flag to prevent concurrent or duplicate migrations.
  • Migrated agents have CreatorID = "" and AutoEnableNewMCPTools = true, preserving pre-existing behavior. ✅

No actionable findings to report.

Open in Web View Automation 

Sent by Cursor Automation: Find vulnerabilities

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants