Valtik Studios
Back to blog
Directushigh2026-03-0212 min

Directus Headless CMS: Role Escalation, File Library Exposure, and the Defaults That Bite

Directus is one of the most popular open-source headless CMS platforms, sitting behind thousands of production websites, mobile apps, and IoT data flows. It's also a recurring audit finding. Permission templates that don't scale, file library exposure, API access tokens with excessive privileges, and the Flows engine's hook execution that becomes an attack vector when misused.

TT
Tre Trebucchi·Founder, Valtik Studios. Penetration Tester

Founder of Valtik Studios. Pentester. Based in Connecticut, serving US mid-market.

Why Directus keeps showing up on B2B audits

We see this pattern show up on almost every engagement.

Directus is a headless CMS built on your own SQL database. You point Directus at Postgres, MySQL, SQLite, or similar. It introspects your schema and gives you an admin UI, REST API, GraphQL API, file management, user roles, and automation flows. Developers like it because it doesn't force you into its own database model. You keep control of your data.

It's deployed in production at thousands of companies. Marketing websites, product catalogs, mobile-app backends, content management for news organizations, data infrastructure for IoT projects. The platform has matured rapidly since 2020; Directus 11 shipped in late 2024 with significant refactoring.

It's also consistently misconfigured. The permission model is flexible enough that developers commonly get it wrong. The file library has its own access model separate from collections. The Flows engine (automation workflows) has hooks that can have security implications. API tokens are often over-privileged. And the default public permissions, while conservative in newer versions, still get loosened during development and never tightened back.

This post walks through the specific attack patterns we find on Directus deployments, the underlying misconfigurations. And the hardening every Directus production deployment should implement.

Attack pattern 1: Permissive public role

Directus has a built-in Public role that controls what unauthenticated users can do. In early versions, the Public role had broader defaults. Modern versions are more restrictive. But developers still modify Public role permissions during development for testing convenience. And often forget to tighten them in production.

Misconfiguration:

  • Public role has read permission on collections containing user data, orders, or other sensitive content
  • Public role has read permission on directus_users (exposes all users)
  • Public role has read permission on directus_files (exposes file metadata)
  • Public role permissions are set to $FULL_ACCESS on any collection

Enumeration:

# Directus exposes itself at /items/<collection> and /users
curl https://target.com/items/users
curl https://target.com/users
curl https://target.com/items/orders

Any response other than a 403 indicates Public role can access that data.

Real finding: a consumer app's Directus instance had Public role with read on their customers collection. The collection contained names, emails, phone numbers, addresses. A single unauthenticated curl returned 30,000+ customer records.

The fix:

  • Audit Public role in Settings → Access Control → Roles → Public
  • Set Public to "No Access" by default
  • Explicitly grant read-only access only to truly public collections (blog posts, published articles, public pricing)
  • Never grant Public access to directus_users, directus_files, or system collections

Attack pattern 2: Role hierarchy confusion

Directus roles don't have a formal hierarchy. Each role has its own permissions. Complex deployments often have 5-15 custom roles (Admin, Editor, Manager, Customer, Premium Customer, Support, etc.) with overlapping permission matrices.

Common misconfigurations:

  • A lower-privilege role accidentally has broader permissions on specific collections than intended
  • Permission inheritance assumptions that don't hold (Directus doesn't inherit)
  • Role hierarchy documented in code comments but not enforced in permissions
  • Collections added later that get default permissions not aligned with the role matrix

Real finding: a SaaS product had roles for "Customer" and "Premium Customer." The intended access was "Customer can read their own orders, Premium can read their own orders + view premium features." Due to permission drift, "Customer" role had read on the entire premium_content collection. A Free account could access all premium content by hitting the API directly.

The fix:

  • Document the intended permission matrix per role
  • Test each role's permissions with API calls, not the UI
  • Review permissions quarterly
  • Reset permissions to the documented matrix if drift is detected

Attack pattern 3: Item-level permissions bypass

Directus supports item-level permissions via filter expressions. "this role can only read items where user_id equals the requester's ID." Powerful feature, commonly misconfigured.

Common bugs:

  • Filter expression uses client-controllable data. user_id = $CURRENT_USER is safe. role_id = $CURRENT_ROLE where the client controls their role claim isn't.
  • Filter expression missing on some operations. Read filter set correctly, but update filter is missing, allowing any authenticated user to modify any item.
  • Filter expression with subtle logic bugs. user_id = $CURRENT_USER OR is_public = true. An attacker sets is_public = true on their own item, then reads others with the same filter pattern.

Real finding: a team collaboration tool had item-level filters for the tasks collection. Read filter: assigned_to = $CURRENT_USER. Update filter: not set (default allowed if any update permission existed). Users could reassign tasks to themselves via update, then read them. Information disclosure across teams.

The fix:

  • Set item-level filters for every operation (read, update, delete) that needs them
  • Test filters by logging in as different users and attempting to access each others' data
  • Use $CURRENT_USER system variable for ownership-based filters
  • Don't trust client-provided role or team claims without validating against the user record

Attack pattern 4: File library permission model

Directus has a separate permission model for files. Files are referenced from collections. But the files themselves have independent access control via the directus_files system collection.

Common misconfigurations:

  • Files uploaded via frontend user interfaces inherit the uploader's folder permissions, which are often too broad
  • Public folders intended for specific public assets but accidentally holding private files
  • File URL format (/assets/) is guessable if file IDs are sequential or predictable
  • Asset URL permissions not tied to the requesting user's session

Real finding: a document-sharing SaaS stored user-uploaded PDFs in Directus. File-level permissions were set to "authenticated users can read." Any logged-in user could enumerate file IDs and download every user's uploaded documents.

The fix:

  • Files used in authenticated contexts should have permissions matching the referencing collection
  • Use Directus's built-in file permission on specific folders
  • Consider using signed URLs for sensitive assets (Directus supports this in newer versions)
  • Don't expose directus_files to public or broad authenticated roles
  • Validate file access at the controller level for sensitive content

Attack pattern 5: Static tokens left in collections

Directus supports API tokens for server-to-server integration. Tokens are associated with user accounts and inherit that user's permissions.

Common misconfiguration:

  • Admin-scoped tokens stored in frontend code. An application stores a token with admin permissions in frontend JavaScript for "convenience." Anyone viewing the page source can extract the token and use it.
  • Tokens shared across multiple integrations. Rotation becomes impossible because rotating breaks many integrations at once.
  • Tokens stored in git commits. Leaked to public repos or retained in git history.
  • Tokens without expiration. Static tokens that persist indefinitely even after staff changes.

Real finding: a startup's frontend had a Directus API token in their JavaScript bundle with admin-level permissions. The token was used to fetch configuration data. Any visitor could copy the token and use it to create/modify/delete any data in the Directus instance.

The fix:

  • Never put admin tokens in frontend code. Use the frontend authentication flow instead.
  • Per-integration tokens with minimum-necessary permissions. Each integration gets its own dedicated user + token.
  • Rotate tokens on a schedule. Quarterly at minimum.
  • Monitor token usage. Directus activity logs show token usage patterns.
  • Use ephemeral tokens via the authentication flow where possible, instead of static API tokens.

Attack pattern 6: Flows engine abuse

Directus Flows is an automation system. Triggers (events, webhooks, schedules) invoke operations (send email, update data, call external APIs, run custom scripts).

Security-relevant Flow patterns:

  • Public-triggered Flows. Flows that accept unauthenticated webhooks can be abused for:
- Resource exhaustion (massive flow invocations)

- External API abuse (flow calls attacker-controlled URLs)

- Data manipulation (flows that modify data based on webhook input without validation)

  • Flows with elevated privileges. Flows can run with admin permissions. If their logic trusts user-provided data, the elevation becomes a vulnerability.
  • Flows that send email based on input. Abuse for phishing / spam relay if input isn't validated.

Real finding: a contact form created a Flow that ran on submission. The Flow sent an email to an admin address, then created a record in a leads collection. The flow also included a secondary step that called an external API with data from the submission. Attacker noticed and sent submissions with malicious API URL data → the flow called attacker-controlled URLs with Directus's infrastructure → used for various abuse patterns (SSRF, relay attacks on other services).

The fix:

  • Validate all Flow inputs. Never trust webhook payload data without explicit validation.
  • Allowlist external destinations for Flows that make outbound API calls.
  • Rate-limit Flow executions to prevent resource exhaustion.
  • Use minimum-necessary Flow permissions. If a Flow doesn't need admin, don't give it admin.
  • Review Flow changes. Directus Flows can be modified through the UI. Changes should be reviewed like code.

Attack pattern 7: Schema introspection exposure

Directus's schema introspection endpoint (GET /schema/snapshot) returns the entire database schema. All collections, all fields, all relations, all permissions. Useful for tooling. Also a detailed roadmap for attackers.

Common misconfiguration:

  • Schema snapshot endpoint accessible to Public or broad authenticated roles
  • Documentation pages publishing the schema unnecessarily

The fix:

  • Schema snapshot should be admin-only by default (it's in recent versions)
  • Review permissions on directus_collections, directus_fields, directus_relations

Attack pattern 8: Outdated version with known CVEs

Directus releases frequently, sometimes including security fixes. Self-hosted deployments commonly lag on updates.

Common missed patches:

  • Authentication bypass CVEs (historically several)
  • SQL injection in legacy query endpoints
  • Prototype pollution in older Node versions
  • Dependency vulnerabilities in Directus's own dependencies

The fix:

  • Subscribe to Directus GitHub releases
  • Update within 30 days of security release
  • Plan for automatic dependency updates
  • Consider Directus Cloud for managed updates

Attack pattern 9: GraphQL-specific exposures

Directus has a GraphQL endpoint at /graphql. GraphQL provides capability beyond REST:

  • Deep nested queries that can be expensive
  • Introspection queries that reveal schema
  • Aliased fields that bypass some permission checks
  • Fragment sharing that enables enumeration

Common bugs:

  • GraphQL introspection enabled in production
  • Query depth limits not configured. Single deep query can DoS the database
  • Aliasing bypass on field-level permissions (similar to the Hasura pattern we documented)
  • Authorization checks that miss GraphQL-specific query patterns

The fix:

  • Disable introspection in production unless specifically needed (SKIP_GRAPHQL_SCHEMA_INTROSPECTION=true)
  • Configure max query depth and complexity
  • Test GraphQL queries with alias bypass patterns
  • Apply the same permission model to GraphQL as to REST

Attack pattern 10: Misconfigured CORS

Directus allows CORS configuration per environment. Default settings are restrictive. Deployments frequently widen CORS for convenience.

Common misconfigurations:

  • CORS set to allow any origin with credentials (* with credentials is forbidden by browsers, but similar patterns like reflecting any Origin header happen)
  • CORS allows specific development origins that exist only on localhost
  • CORS whitelist includes subdomains that aren't used (potential for takeover)

The fix:

  • CORS should allow only specific production origins
  • No localhost origins in production
  • Review CORS origin allowlist regularly

The hardening checklist

If you run Directus in production, work through:

Authentication and roles

  • [ ] Public role reviewed and restricted to truly public data
  • [ ] Custom roles documented with intended permission matrix
  • [ ] Permissions tested via API calls for each role
  • [ ] Role audit performed quarterly
  • [ ] Admin access limited to specific individuals
  • [ ] MFA on admin accounts (configure via OAuth provider)

Permission model

  • [ ] Every collection has explicit permission rules
  • [ ] Item-level filters tested and working
  • [ ] Filters cover all operations (read, create, update, delete)
  • [ ] No collection uses $FULL_ACCESS for lower-privilege roles
  • [ ] directus_users and directus_files not readable by Public or broad roles

API access

  • [ ] No admin-level API tokens in frontend code
  • [ ] Per-integration tokens with minimum permissions
  • [ ] Token rotation schedule established (quarterly)
  • [ ] Token usage monitored

Files

  • [ ] File permissions match content sensitivity
  • [ ] File library not exposed to unauthenticated users (unless specifically public)
  • [ ] Sensitive files use signed URLs or controller-level access checks

Flows

  • [ ] Flow inputs validated
  • [ ] External URLs in Flows use allowlists
  • [ ] Flow execution rate-limited
  • [ ] Flow permissions minimum-necessary

GraphQL

  • [ ] Introspection disabled in production (unless needed)
  • [ ] Query depth limits configured
  • [ ] Aliasing and fragment attacks tested

Infrastructure

  • [ ] Directus version current (quarterly updates minimum)
  • [ ] Dependencies up to date
  • [ ] CORS origin allowlist production-only
  • [ ] Database credentials not exposed in environment files committed to git
  • [ ] Backups configured and tested

Monitoring

  • [ ] Activity logs retained (Directus stores them in DB, verify retention)
  • [ ] Alerts on unusual patterns (mass deletions, role changes, permission modifications)
  • [ ] Failed authentication attempts logged

For Directus Cloud vs self-hosted

Directus offers a managed Cloud product. Tradeoffs:

Directus Cloud:

  • Managed updates, backups, infrastructure
  • Monolens scaling and monitoring
  • Directus team operations responsibility
  • Pay for hosted compute

Self-hosted:

  • Full control
  • Lower marginal cost
  • All responsibility for security, updates, infrastructure
  • Typical deployment: Docker / Kubernetes

For most organizations, Directus Cloud is easier from a security perspective. The provider handles infrastructure-level risks. Self-hosted requires genuine operational discipline.

For small deployments

If you're running Directus for a small project:

  1. Use Directus Cloud or a managed deployment where possible
  2. Public role: No access
  3. Specific collections readable by Public only when they're public (blog posts, published content)
  4. Strong admin password, unique to this service
  5. Regular updates
  6. Activity log review monthly

This gets you from "default-insecure" to "reasonably-hardened" with minimal effort.

For enterprise deployments

Larger Directus deployments require:

  • Formal role matrix documentation with change management
  • Automated permission testing in CI (verify role permissions haven't drifted)
  • Token rotation and management infrastructure
  • Integration with enterprise identity provider (SAML, OIDC)
  • Audit logging to external SIEM
  • Network segmentation isolating Directus from other environments
  • Backup and disaster recovery testing
  • Penetration testing annually

For Valtik clients

Valtik's Directus security audits include all the patterns in this post plus:

  • Complete role matrix review
  • Collection-level and item-level permission testing
  • API token inventory and exposure review
  • Flow audit for security implications
  • Schema introspection and GraphQL testing
  • Infrastructure review (Docker, reverse proxy, database)

If you run Directus in production and haven't had an independent security review, reach out via https://valtikstudios.com.

The honest summary

Directus is a capable platform with a flexible permission model that requires explicit attention to configure correctly. The defaults are conservative in modern versions. The drift happens during development as permissions get loosened for testing convenience and not tightened afterward.

The path to a secure Directus deployment: document your intended permission model, implement it, test it. And review it periodically. This isn't unique to Directus. Every CMS and BaaS platform needs similar discipline. What's unique is Directus's flexibility, which creates both its value and its complexity.

Audit your deployment against the patterns above. Most find at least three applicable.

Sources

  1. Directus Official Documentation
  2. Directus Access Control
  3. Directus API Reference
  4. Directus Flows
  5. Directus File Library
  6. Directus GitHub Security Advisories
  7. Directus Cloud
  8. OWASP API Security Top 10
  9. GraphQL Security Cheat Sheet. OWASP
  10. Headless CMS Security Research. Valtik
directusheadless cmsbaas securityplatform securitypenetration testingapplication securityvulnerability assessmentresearch

Want us to check your Directus setup?

Our scanner detects this exact misconfiguration. plus dozens more across 38 platforms. Free website check available, no commitment required.

Get new research in your inbox
No spam. No newsletter filler. Only new posts as they publish.