PocketBase Security Testing: Admin Credentials, API Access Control, JS VM Hook Execution, and File Upload

PocketBase is a popular open-source Go-based backend-as-a-service with a built-in SQLite database, file storage, authentication, and real-time subscriptions — distributed as a single binary it is widely used in indie applications and prototypes. Its security assessment covers: the admin dashboard at /_/ requires a superuser setup on first access — if deployment scripts or Docker images don't enforce this, the dashboard may be accessible to setup by any attacker who reaches it; PocketBase collection API rules determine who can read, create, update, and delete records — the common development pattern of setting rules to empty string (allow all) for rapid prototyping is frequently forgotten in production deployments; PocketBase's JavaScript hooks execute in a JSVM environment with access to $os, $http, and other bindings enabling server-side operations — admin users with hook creation access have server-side code execution; uploaded files are served from deterministic paths based on record ID and filename, making file contents accessible if the collection view rule allows unauthenticated access; and PocketBase's /api/collections endpoint lists all collection schemas to admin users and can reveal sensitive data model information. This guide covers systematic PocketBase security assessment.

Table of Contents

  1. PocketBase Discovery
  2. Admin Dashboard Setup and Credential Testing
  3. Collection API Rule Testing
  4. File Upload and Access Control
  5. PocketBase Security Hardening

Admin Dashboard Setup and Credential Testing

# PocketBase admin dashboard at /_/
# On first launch: /_/ shows a setup form — CRITICAL if accessible before admin is created

# Check if PocketBase is accessible and in setup mode
SETUP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  "https://pocketbase.example.com/_/" 2>/dev/null)
echo "Admin dashboard HTTP status: ${SETUP_STATUS}"

# Check if superuser setup has been completed
curl -s "https://pocketbase.example.com/api/health" 2>/dev/null | \
  python3 -c "
import json,sys
d = json.load(sys.stdin)
print(f\"PocketBase status: {d.get('code','?')} {d.get('message','?')}\")
" 2>/dev/null

# Test admin authentication endpoint with common weak credentials
for CRED in "admin@example.com:password" "admin@admin.com:admin" "test@test.com:password"; do
  EMAIL="${CRED%%:*}"
  PASS="${CRED##*:}"
  RESPONSE=$(curl -s -X POST \
    "https://pocketbase.example.com/api/admins/auth-with-password" \
    -H "Content-Type: application/json" \
    -d "{\"identity\":\"${EMAIL}\",\"password\":\"${PASS}\"}" 2>/dev/null)
  if echo "$RESPONSE" | python3 -c "
import json,sys
d=json.load(sys.stdin)
if d.get('token'):
    print(f'LOGIN SUCCESS with {\"$EMAIL\"}/{\"$PASS\"}')
    print(f'Token: {d[\"token\"][:20]}...')
" 2>/dev/null; then
    break
  fi
done

Collection API Rule Testing

# PocketBase API rules: empty string = allow all (most permissive)
# Common developer mistake: setting "" during development and forgetting to restrict

# Attempt to list all records from common collection names without auth
COLLECTIONS=("users" "posts" "articles" "products" "orders" "messages"
             "comments" "settings" "config" "todos" "notes" "files")
for COL in "${COLLECTIONS[@]}"; do
  RESPONSE=$(curl -s \
    "https://pocketbase.example.com/api/collections/${COL}/records" \
    2>/dev/null)
  STATUS=$(echo "$RESPONSE" | python3 -c "
import json,sys
d = json.load(sys.stdin)
if 'items' in d:
    total = d.get('totalItems', 0)
    print(f'OPEN: {total} records accessible without auth')
elif 'code' in d:
    print(f'Protected ({d[\"code\"]})')
else:
    print('Unknown response')
" 2>/dev/null)
  echo "/api/collections/${COL}/records: ${STATUS}"
done

# With admin token: list all collections and their API rules
ADMIN_TOKEN="your-admin-token"
curl -s "https://pocketbase.example.com/api/collections" \
  -H "Authorization: ${ADMIN_TOKEN}" 2>/dev/null | \
  python3 -c "
import json,sys
d = json.load(sys.stdin)
for col in d.get('items', []):
    rules = col.get('listRule','?')
    print(f\"{col['name']:30} listRule='{rules}' {'<-- OPEN' if rules=='' else ''}\")
" 2>/dev/null

PocketBase Security Hardening

PocketBase Security Hardening Checklist:
Security TestMethodRisk
Admin dashboard in setup mode accessible to unauthenticated usersGET /_/ — if no superuser configured, attacker creates admin account with full controlCritical
Collection API rules set to allow all (empty string)GET /api/collections/{name}/records without auth — open rule returns all records unauthenticatedHigh
Users collection readable without authenticationGET /api/collections/users/records — exposes all user emails, names, and profile dataHigh
JS hook code execution via admin JSVMCreate hook with $os.exec() via admin dashboard — OS command execution on PocketBase serverCritical
Uploaded files accessible without authenticationGET /api/files/{collection}/{record}/{filename} — deterministic path; if view rule is open, all files downloadableHigh
Weak admin credentialsPOST /api/admins/auth-with-password with common passwords — admin token grants full collection managementCritical

Automate PocketBase Security Testing

Ironimo tests PocketBase deployments for admin dashboard in setup mode allowing unauthenticated attacker-controlled superuser creation, collection API rules configured with empty string allowing all unauthenticated data access, users collection readable without authentication exposing all user emails and profile data, JS hook JSVM code execution capabilities accessible to admin users including $os bindings for OS-level operations, uploaded files accessible without authentication via predictable /api/files/ path patterns, and weak admin credentials susceptible to brute force via the admin authentication endpoint.

Start free scan