Directus is a modern open-source headless CMS and data platform that wraps any SQL database with a REST and GraphQL API. Its security architecture relies on role-based access control but has common misconfiguration patterns: Directus grants broad permissions to the "Public" role by default in demonstration setups — when developers follow quick-start guides and forget to restrict these, all collection data is accessible without authentication; static API tokens created for frontend applications and embedded in JavaScript give persistent access to all collections the token's role can read; the Directus Flow system allows admin users to execute custom JavaScript operations using a sandboxed Node.js VM — misuse of this feature enables server-side code execution; the /files endpoint for file access uses Directus's permission system, but overly permissive file rules allow downloading all uploaded assets without authentication; GraphQL introspection on Directus's /graphql endpoint reveals all collection names, field definitions, and relationships; and Directus admin credentials established during initial setup are frequently reused across environments or set to weak values in self-hosted Docker deployments. This guide covers systematic Directus security assessment.
# Directus public role — test all endpoints without any authentication
# If the Public role has read permissions, collections return data without auth
# List accessible collections (no auth)
curl -s "https://directus.example.com/collections" 2>/dev/null | \
python3 -c "
import json,sys
d = json.load(sys.stdin)
if 'data' in d:
collections = [c['collection'] for c in d['data']
if not c['collection'].startswith('directus_')]
print(f'Collections (may be public): {collections}')
else:
print(f'Error: {d.get(\"errors\",d)}')
" 2>/dev/null
# Test each collection for public read access
COLLECTIONS=("articles" "posts" "products" "users" "orders" "settings" "pages")
for COL in "${COLLECTIONS[@]}"; do
CODE=$(curl -s -o /dev/null -w "%{http_code}" \
"https://directus.example.com/items/${COL}" 2>/dev/null)
if [ "$CODE" == "200" ]; then
COUNT=$(curl -s "https://directus.example.com/items/${COL}?limit=1&meta=total_count" \
2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(d.get('meta',{}).get('total_count',0))
" 2>/dev/null)
echo "PUBLIC READ: /items/${COL} — ${COUNT} records accessible without auth"
fi
done
# Check if directus_users is readable without auth (user email enumeration)
curl -s "https://directus.example.com/users" 2>/dev/null | \
python3 -c "
import json,sys
d = json.load(sys.stdin)
if 'data' in d:
users = d['data']
print(f'USER ENUMERATION: {len(users)} users readable without auth')
for u in users[:5]:
print(f\" {u.get('email','?')} role={u.get('role','?')}\")
" 2>/dev/null
| Security Test | Method | Risk |
|---|---|---|
| Collections publicly readable without authentication | GET /items/{collection} without credentials — Public role with read permission returns all data | High |
| User enumeration via unauthenticated /users endpoint | GET /users without auth — lists all user emails and role assignments when Public role has user read permission | High |
| API token in client-side JavaScript or source code | Grep JavaScript for Bearer tokens — static Directus tokens give persistent access to configured role's collections | High |
| GraphQL introspection exposes complete data model | POST /graphql with introspection query — reveals all collections, field types, relationships, and directives | Medium |
| Files accessible without authentication | GET /assets/{file-id} without auth — downloads uploaded files when Public role has file read permission | High |
| Flow engine JavaScript execution | Create Flow with Run Script operation — JavaScript executes in Node.js VM on Directus server with admin privileges | Critical |
Ironimo tests Directus deployments for collections accessible without authentication due to overly permissive Public role configurations, user email enumeration via the /users endpoint when Public role has user read permission, API tokens embedded in client-side JavaScript or application source code, GraphQL introspection exposing the complete data model without authentication, file assets downloadable without authorization when Public file read permission is enabled, and Directus admin credentials matching common setup defaults used in Docker Compose examples.
Start free scan