Directus Security Testing: Default Credentials, API Role Bypass, File Access Control, and GraphQL Introspection

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.

Table of Contents

  1. Directus Discovery
  2. Public Role Collection Exposure
  3. API Token Identification and Scope Testing
  4. GraphQL Introspection
  5. Directus Security Hardening

Public Role Collection Exposure

# 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

Directus Security Hardening

Directus Security Hardening Checklist:
Security TestMethodRisk
Collections publicly readable without authenticationGET /items/{collection} without credentials — Public role with read permission returns all dataHigh
User enumeration via unauthenticated /users endpointGET /users without auth — lists all user emails and role assignments when Public role has user read permissionHigh
API token in client-side JavaScript or source codeGrep JavaScript for Bearer tokens — static Directus tokens give persistent access to configured role's collectionsHigh
GraphQL introspection exposes complete data modelPOST /graphql with introspection query — reveals all collections, field types, relationships, and directivesMedium
Files accessible without authenticationGET /assets/{file-id} without auth — downloads uploaded files when Public role has file read permissionHigh
Flow engine JavaScript executionCreate Flow with Run Script operation — JavaScript executes in Node.js VM on Directus server with admin privilegesCritical

Automate Directus Security Testing

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