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.
# 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
# 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
| Security Test | Method | Risk |
|---|---|---|
| Server API key with broad scope in client JavaScript | Extract standard_ prefixed API key from JavaScript bundle — provides server-level access bypassing all collection permissions | Critical |
| Collection with 'any' read permission | GET /v1/databases/{id}/collections/{id}/documents without auth — returns all documents unauthenticated | High |
| Collection with 'users' read — all users see all documents | GET /v1/databases/{id}/collections/{id}/documents with any user JWT — returns all users' documents without ownership filter | High |
| Storage bucket with public read permission | GET /v1/storage/buckets/{id}/files without auth — lists and downloads all stored files | High |
| Function environment variable in error output | Trigger function with invalid input causing error — verbose error messages may include environment variable names and values | High |
| User enumeration via authentication error messages | POST /v1/account/sessions/email — different error messages for existing vs non-existing email addresses | Medium |
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