Appwrite Security Testing: API Keys, Database Rules, Function Execution, and Team Permissions

Appwrite is a popular open-source Backend-as-a-Service that provides authentication, databases, storage, and serverless functions — widely used as a self-hosted Firebase alternative in web and mobile applications. Its security assessment covers several misconfiguration patterns: Appwrite server-side API keys are scoped with permission strings like databases.read and functions.execute — API keys with overly broad scopes (all.*) embedded in client-side JavaScript or mobile apps give any user who extracts them server-level project access bypassing all collection permissions; Appwrite database collections use role-based document permissions — collections configured with any read permission expose all documents to unauthenticated users, while users read permission exposes all documents to any authenticated user regardless of ownership; Appwrite Cloud Functions accept HTTP trigger requests and execute server-side code — functions with APPWRITE_FUNCTION_API_KEY or other secrets in environment variables may leak those secrets through verbose error messages or logging; Appwrite storage buckets with public any read permission allow unauthenticated users to enumerate and download all stored files; and Appwrite teams allow organizing users into groups with shared resource access — team members with overly permissive roles receive access to resources beyond their functional requirement. This guide covers systematic Appwrite security assessment.

Table of Contents

  1. API Key Scope and Extraction Testing
  2. Database Collection Permission Testing
  3. Storage Bucket Access Control
  4. Function Environment Variable Exposure
  5. Appwrite Security Hardening

API Key Scope and Extraction Testing

# Appwrite API key extraction from client-side code
# API keys starting with "standard_" are server-side keys that should NEVER be in client code

# Search JavaScript bundles for Appwrite API keys
curl -s "https://app.example.com/assets/index.js" 2>/dev/null | \
  grep -oE '"(standard|custom)_[a-f0-9]{64}"' | head -5
# Also check for project ID and endpoint in client config:
# new Client().setEndpoint('...').setProject('...').setKey('...')

# With extracted API key, test scope:
APPWRITE_URL="https://appwrite.example.com"
PROJECT_ID="your-project-id"
API_KEY="standard_your_extracted_key"

# List all databases (requires databases.read scope)
curl -s "${APPWRITE_URL}/v1/databases" \
  -H "X-Appwrite-Project: ${PROJECT_ID}" \
  -H "X-Appwrite-Key: ${API_KEY}" \
  2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
dbs = d.get('databases',[])
print(f'Databases accessible ({len(dbs)} total):')
for db in dbs:
    print(f\"  {db['name']} (id={db['\$id']}) — {db.get('enabled','?')}\")
" 2>/dev/null

# List users (requires users.read scope — most sensitive)
curl -s "${APPWRITE_URL}/v1/users" \
  -H "X-Appwrite-Project: ${PROJECT_ID}" \
  -H "X-Appwrite-Key: ${API_KEY}" \
  2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
users = d.get('users',[])
print(f'Users: {len(users)} / {d.get(\"total\",0)}')
for u in users[:5]:
    print(f\"  {u.get('email','?')} id={u.get('\$id','?')} name={u.get('name','?')}\")
" 2>/dev/null

Database Collection Permission Testing

# Appwrite collection permissions control document-level access
# 'any' role = unauthenticated access; 'users' role = any authenticated user

APPWRITE_URL="https://appwrite.example.com"
PROJECT_ID="your-project-id"
DB_ID="your-database-id"

# Test collection access without authentication
for COLLECTION_ID in "users" "profiles" "orders" "messages" "settings"; do
  RESPONSE=$(curl -s \
    "${APPWRITE_URL}/v1/databases/${DB_ID}/collections/${COLLECTION_ID}/documents" \
    -H "X-Appwrite-Project: ${PROJECT_ID}" 2>/dev/null)
  TOTAL=$(echo "$RESPONSE" | python3 -c "
import json,sys
d=json.load(sys.stdin)
if 'total' in d:
    print(f'OPEN: {d[\"total\"]} documents')
elif 'code' in d:
    print(f'Auth required ({d[\"code\"]})')
" 2>/dev/null)
  echo "Collection ${COLLECTION_ID}: ${TOTAL}"
done

# With user JWT — test cross-user document access (broken object-level auth)
USER_JWT="your-user-jwt-token"
# Try to access another user's documents by changing userId filter
curl -s "${APPWRITE_URL}/v1/databases/${DB_ID}/collections/orders/documents" \
  -H "X-Appwrite-Project: ${PROJECT_ID}" \
  -H "X-Appwrite-JWT: ${USER_JWT}" \
  2>/dev/null | python3 -c "
import json,sys
d = json.load(sys.stdin)
docs = d.get('documents',[])
print(f'Documents returned: {len(docs)} / {d.get(\"total\",0)}')
# Check if docs belong to other users
for doc in docs[:5]:
    print(f\"  owner={doc.get('\$permissions',[])} id={doc.get('\$id','?')}\")
" 2>/dev/null

Appwrite Security Hardening

Appwrite Security Hardening Checklist:
Security TestMethodRisk
Server API key with broad scope in client JavaScriptExtract standard_ prefixed API key from JavaScript bundle — provides server-level access bypassing all collection permissionsCritical
Collection with 'any' read permissionGET /v1/databases/{id}/collections/{id}/documents without auth — returns all documents unauthenticatedHigh
Collection with 'users' read — all users see all documentsGET /v1/databases/{id}/collections/{id}/documents with any user JWT — returns all users' documents without ownership filterHigh
Storage bucket with public read permissionGET /v1/storage/buckets/{id}/files without auth — lists and downloads all stored filesHigh
Function environment variable in error outputTrigger function with invalid input causing error — verbose error messages may include environment variable names and valuesHigh
User enumeration via authentication error messagesPOST /v1/account/sessions/email — different error messages for existing vs non-existing email addressesMedium

Automate Appwrite Security Testing

Ironimo tests Appwrite deployments for server-side API keys with broad scopes embedded in client-side JavaScript bundles, database collections with 'any' or 'users' read permissions exposing all documents without ownership-based filtering, storage buckets with public read access allowing unauthenticated file enumeration and download, Cloud Function environment variable exposure through verbose error messages, team permission over-granting giving members access to resources beyond their functional requirements, and authentication endpoint user enumeration via differential error responses.

Start free scan