fix: hide agent bot access tokens from agents (#14830)

## Summary

Keeps Agent Bot list and show access available to agents while
restricting account bot access tokens to administrators.

## Why

Agents need Agent Bot metadata for existing product workflows, but the
bot access token can be replayed against bot-authorized APIs and should
not be exposed to them.

## What changed

- serialize `access_token` only for administrators
- verify agents can read Agent Bot metadata without receiving the token
- verify administrators still receive the token from index and show
responses

## Validation

`bundle exec rspec
spec/controllers/api/v1/accounts/agent_bots_controller_spec.rb`

27 examples, 0 failures.

Related follow-up:
[CW-7595](https://linear.app/chatwoot/issue/CW-7595/standardize-one-time-credential-disclosure-across-chatwoot-apis)



---------

Co-authored-by: Gaurav Singhal <gauravsinghal@Gauravs-Mac-mini.local>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Gaurav Singhal
2026-07-14 20:00:39 -07:00
committed by GitHub
co-authored by Gaurav Singhal Sojan Jose
parent 8b4f3e226e
commit 13db36609d
2 changed files with 27 additions and 5 deletions
@@ -6,6 +6,6 @@ json.outgoing_url resource.outgoing_url unless resource.system_bot?
json.bot_type resource.bot_type
json.bot_config resource.bot_config
json.account_id resource.account_id
json.access_token resource.access_token if resource.access_token.present?
json.access_token resource.access_token if resource.access_token.present? && Current.account_user&.administrator?
json.secret resource.secret if !resource.system_bot? && Current.account_user&.administrator?
json.system_bot resource.system_bot?
@@ -15,7 +15,7 @@ RSpec.describe 'Agent Bot API', type: :request do
end
end
context 'when it is an authenticated user' do
context 'when it is an authenticated agent' do
it 'returns all the agent_bots in account along with global agent bots' do
global_bot = create(:agent_bot)
get "/api/v1/accounts/#{account.id}/agent_bots",
@@ -25,7 +25,7 @@ RSpec.describe 'Agent Bot API', type: :request do
expect(response).to have_http_status(:success)
expect(response.body).to include(agent_bot.name)
expect(response.body).to include(global_bot.name)
expect(response.body).to include(agent_bot.access_token.token)
expect(response.body).not_to include(agent_bot.access_token.token)
expect(response.body).not_to include(global_bot.access_token.token)
end
@@ -54,6 +54,17 @@ RSpec.describe 'Agent Bot API', type: :request do
expect(account_bot_response).to include('thumbnail')
end
end
context 'when it is an authenticated administrator' do
it 'returns the account bot access token' do
get "/api/v1/accounts/#{account.id}/agent_bots",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(response.body).to include(agent_bot.access_token.token)
end
end
end
describe 'GET /api/v1/accounts/{account.id}/agent_bots/:id' do
@@ -65,7 +76,7 @@ RSpec.describe 'Agent Bot API', type: :request do
end
end
context 'when it is an authenticated user' do
context 'when it is an authenticated agent' do
it 'shows the agent bot' do
get "/api/v1/accounts/#{account.id}/agent_bots/#{agent_bot.id}",
headers: agent.create_new_auth_token,
@@ -73,7 +84,7 @@ RSpec.describe 'Agent Bot API', type: :request do
expect(response).to have_http_status(:success)
expect(response.body).to include(agent_bot.name)
expect(response.body).to include(agent_bot.access_token.token)
expect(response.body).not_to include(agent_bot.access_token.token)
end
it 'will show a global agent bot' do
@@ -91,6 +102,17 @@ RSpec.describe 'Agent Bot API', type: :request do
expect(response.parsed_body).not_to include('outgoing_url')
end
end
context 'when it is an authenticated administrator' do
it 'returns the account bot access token' do
get "/api/v1/accounts/#{account.id}/agent_bots/#{agent_bot.id}",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(response.body).to include(agent_bot.access_token.token)
end
end
end
describe 'POST /api/v1/accounts/{account.id}/agent_bots' do