diff --git a/app/app.config.ts b/app/app.config.ts index fff85bfd..f25479e0 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -92,6 +92,11 @@ export default defineAppConfig({ to: '/guides/deployments', icon: 'directus-deployments', }, + { + label: 'Security', + to: '/guides/security/best-practices', + icon: 'i-ph-shield-check', + }, { label: 'AI', to: '/guides/ai/', diff --git a/content/configuration/extensions.md b/content/configuration/extensions.md index c0a7762a..d761704b 100644 --- a/content/configuration/extensions.md +++ b/content/configuration/extensions.md @@ -5,6 +5,10 @@ description: Configuration for extensions and the Directus Marketplace. :partial{content="config-env-vars"} +::callout{icon="material-symbols:info-outline"} +For guidance on restricting who can install or manage extensions, see [Security Best Practices](/guides/security/best-practices#extensions). +:: + | Variable | Description | Default Value | | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -------------- | | `EXTENSIONS_PATH`[1] | Path to your local extensions directory, or subdirectory within the configured storage location when `EXTENSIONS_LOCATION` is set. | `./extensions` | diff --git a/content/configuration/files.md b/content/configuration/files.md index b87cbeab..fa21b62d 100644 --- a/content/configuration/files.md +++ b/content/configuration/files.md @@ -5,6 +5,10 @@ description: Configuration for storage locations, metadata, upload limits, and t :partial{content="config-env-vars"} +::callout{icon="material-symbols:info-outline"} +For guidance on file-upload permissions, asset access tokens, and blocking internal IPs for file imports, see [Security Best Practices](/guides/security/best-practices#files--assets). +:: + By default, Directus stores all uploaded files locally on the file system or can also configure Directus to use external storage services. You can also configure _multiple_ storage adapters at the same time which allows you to choose where files are being uploaded on a file-by-file basis. In the Data Studio, files will automatically be uploaded to the first configured storage location (in this case `local`). The used storage location is saved under `storage` in the `directus_files` collection. diff --git a/content/configuration/flows.md b/content/configuration/flows.md index c02da2c6..55933414 100644 --- a/content/configuration/flows.md +++ b/content/configuration/flows.md @@ -6,6 +6,10 @@ description: Configure environment variables, memory, and timeout for Flows. :partial{content="config-env-vars"} +::callout{icon="material-symbols:info-outline"} +For guidance on who should be able to create and edit flows, and how to secure webhook triggers, see [Security Best Practices](/guides/security/best-practices#flows). +:: + | Variable | Description | Default Value | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------- | | `FLOWS_ENV_ALLOW_LIST` | A comma-separated list of environment variables. | `false` | diff --git a/content/configuration/security-limits.md b/content/configuration/security-limits.md index 194e9828..de162fd6 100644 --- a/content/configuration/security-limits.md +++ b/content/configuration/security-limits.md @@ -5,6 +5,10 @@ description: Configuration for access tokens, cookies, CSP, hashing, CORS, rate :partial{content="config-env-vars"} +::callout{icon="material-symbols:info-outline"} +This page documents environment variables. For in-app security configuration (permissions, system collections, public access), see [Security Best Practices](/guides/security/best-practices). +:: + | Variable | Description | Default Value | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | `SECRET`[1] | Secret string for the project. Used for secret signing. | Random value | diff --git a/content/configuration/theming.md b/content/configuration/theming.md index 18478b94..5619207e 100644 --- a/content/configuration/theming.md +++ b/content/configuration/theming.md @@ -9,6 +9,8 @@ The Directus Data Studio has been developed with customization and extensibility Theming options can be found in dedicated section in the Settings Module. + + ## Branding The branding section contains global settings that are applied as defaults throughout the Data Studio and favicon. @@ -54,6 +56,10 @@ body { This example is better served using the Theming Engine, but is a good example of Directus' flexibility of approach. +::callout{icon="material-symbols:info-outline"} +Custom CSS can be an injection vector, see [Harden Content Security Policy When Using Custom CSS](/guides/security/best-practices#harden-content-security-policy-when-using-custom-css) for configuration guidance. +:: + #### Action Styling The `--primary` variable (and its shades) control call-to-actions and all other elements within the App using the diff --git a/content/guides/03.auth/2.access-control.md b/content/guides/03.auth/2.access-control.md index 2b469866..485e2632 100644 --- a/content/guides/03.auth/2.access-control.md +++ b/content/guides/03.auth/2.access-control.md @@ -80,7 +80,7 @@ All public permissions are **off by default**. It is up to administrators to re- ::callout{icon="material-symbols:warning-rounded" color="warning"} Granting collection-level read access to the public role exposes **all items** in that collection to anyone, including unauthenticated users and bots. If that collection contains a mix of published and unpublished content, all of it will be accessible via the API unless you configure additional restrictions.

-Review the [Minimize Public Role Exposure](#minimize-public-role-exposure) best practice before enabling public access. +Review [Minimize the Public Role](/guides/security/best-practices#minimize-the-public-role) in Security Best Practices before enabling public access. :: ## Statuses @@ -174,37 +174,4 @@ If a request comes from IP `192.168.1.100`, only Policy A and Policy C remain ac ## Best Practices -### Restrict Junction Table Permissions - -When configuring many-to-many (M2M) relationships, don't overlook the junction collection. Giving a role full create access on a junction collection allows users to create arbitrary relationships between items via the API, even when both related collections have restricted permissions. - -::callout{icon="material-symbols:warning-rounded" color="warning"} -A user with unrestricted create access on a junction collection like `posts_tags` can associate any `posts` item with any `tags` item, regardless of their permissions on those collections. Always apply custom permissions on junction collections to match the intent of the related collection permissions. -:: - -To prevent this: - -- Use item rules on the junction collection to scope which relationships can be created. For example, filter on `$CURRENT_USER` to ensure users can only create junctions for items they own. -- Use field rules to limit which foreign key fields can be set. -- For more advanced validation logic, use [Flows](/guides/automate/flows) to inspect and reject junction writes that don't meet your criteria. - -### Limit User Creation Permissions - -Unrestricted create access on `directus_users` allows users to create new accounts and assign any role, including administrator roles. Always scope down create permissions on `directus_users`: - -- Use field presets to lock the `role` field to a specific value when new users are created. -- Use field validation rules to restrict which roles can be assigned. -- Remove the `role` field from field permissions entirely if the creating user should not control role assignment. - -### Minimize Public Role Exposure - -Only grant the public role access to data that is genuinely intended for unauthenticated consumption. Granting broad read access to a collection and relying on your frontend to filter results is not sufficient - the underlying API remains open to anyone. - -Before enabling public access on a collection, consider: - -- **Only expose what is necessary.** If only a subset of items should be public (for example, published blog posts but not drafts), use [custom permissions](/guides/auth/access-control#custom-permissions) to restrict which items and fields are accessible. -- **Test with an unauthenticated request.** Make a request to the API without a token to verify that only the intended data is returned. This is the simplest way to catch unintended exposure. - -### Restricting Unwanted Side Effects - -When allowing non admin users to update `directus_settings`, users might be able to gain information outside of their permissions. Allowing users to update the `theming_group` for example allows non admin users to inject any css into the app which can have unforseen consequences. To reduce the risk of abuse with css injections, configure [Content Security Policies](/configuration/security-limits#csp) to only allow trusted domains and sources. +See [Security Best Practices](/guides/security/best-practices) for guidance on additional best practices for access control. diff --git a/content/guides/05.files/1.upload.md b/content/guides/05.files/1.upload.md index b29ef974..0414856d 100644 --- a/content/guides/05.files/1.upload.md +++ b/content/guides/05.files/1.upload.md @@ -5,6 +5,10 @@ description: Learn to upload files to Directus via both the data studio or API. Multiple files can be uploaded simultaneously via both the data studio and via the API. File uploads are not limited to just images, they can be any kind of file. +::callout{icon="material-symbols:info-outline"} +If you allow uploads from the public role or unauthenticated users, review [Restrict Public File Uploads](/guides/security/best-practices#restrict-public-file-uploads) first. +:: + ## Data Studio ![The files module with a number of files visible in a gallery layout.](/img/796eb265-bce2-4faa-93d0-118dac406457.webp) diff --git a/content/guides/06.automate/1.flows.md b/content/guides/06.automate/1.flows.md index 36c9a086..3bc59a99 100644 --- a/content/guides/06.automate/1.flows.md +++ b/content/guides/06.automate/1.flows.md @@ -5,6 +5,10 @@ description: Flows enable custom, event-driven data processing and task automati Flows enable custom, event-driven data processing and task automation within Directus. Each flow is composed of one trigger, a series of operations, and a data chain that is passed between each step. +::callout{icon="material-symbols:info-outline"} +Flows run arbitrary code with elevated accountability. See [Security Best Practices](/guides/security/best-practices#flows) for guidance on restricting who can create, edit, or trigger them. +:: + ## Fields - **Name (required)** — The displayed name of the Flow. diff --git a/content/guides/09.extensions/0.overview.md b/content/guides/09.extensions/0.overview.md index 40ad0701..7b049313 100644 --- a/content/guides/09.extensions/0.overview.md +++ b/content/guides/09.extensions/0.overview.md @@ -11,6 +11,10 @@ Directus has been built to be extensible - both allowing for powerful enhancemen Extensions in Directus run within the same environment as the main application, allowing them to leverage existing access to underlying [services](/guides/extensions/api-extensions/services) and [UI components](/guides/extensions/app-extensions/ui-library). +::callout{icon="material-symbols:info-outline"} +Extensions execute code inside the Directus server. See [Security Best Practices](/guides/security/best-practices#extensions) for guidance on restricting extension management and Marketplace trust settings. +:: + ## App Extensions [App Extensions](/guides/extensions/app-extensions) extend the functionality of the Data Studio. diff --git a/content/guides/10.deployments/1.security.md b/content/guides/10.deployments/1.security.md index 0dc6673b..18baf2a0 100644 --- a/content/guides/10.deployments/1.security.md +++ b/content/guides/10.deployments/1.security.md @@ -6,6 +6,8 @@ headline: Deployments The Deployment module uses Directus native permissions to control access. This works the same way as other built-in modules like Flows and Insights. +For project-wide security guidance that applies to all modules (restricting system collections, public role exposure, junction tables, and more), see [Security Best Practices](/guides/security/best-practices). + ## Access Control The Deployment module is visible to any user with **read** access on the `directus_deployments` collection. All operations are enforced through standard Directus permissions, so users can only perform actions their role allows. diff --git a/content/guides/13.security/1.best-practices.md b/content/guides/13.security/1.best-practices.md new file mode 100644 index 00000000..4e3ca1cf --- /dev/null +++ b/content/guides/13.security/1.best-practices.md @@ -0,0 +1,173 @@ +--- +title: Security Best Practices +description: Harden a directus project. +--- + +This page is a security checklist written for project administrators wishing to harden their directus project. + +It does not cover infrastructure security (TLS, reverse proxy, database hardening) or environment variable tuning. For environment variables that govern tokens, cookies, CSP, CORS, and rate limiting, see [Security & Limits](/configuration/security-limits). + +## Restrict Access to System Collections + +[System collections](/guides/data-model/collections) control how your project behaves. Granting non-administrators create, update, or delete access on these collections is rarely intended, and often bypasses the intent of your data model permissions. + +### Flows + +Flows run arbitrary operations. They can execute custom scripts, make HTTP requests, mutate data across any collection, and run under a service account with elevated permissions. Update access on `directus_flows` or `directus_operations` lets a user rewrite an existing automation, including replacing the body of a running flow with a malicious payload. + +- Grant create, update, and delete on `directus_flows` and `directus_operations` to administrators only. +- For flows with a **Webhook** trigger, treat the trigger URL as a public endpoint. Require an access token or a shared secret in the request, and validate the payload inside the flow before taking action. +- Audit any flow that contains a **Run Script** operation before enabling it. Script operations execute Node.js code with the privileges of the flow's accountability. + +### Settings + +Updating `directus_settings` changes behavior for every user in the project. Fields on this collection include the project name, theming, authentication policy, and URLs used in outbound email templates. A non-administrator with update access can inject CSS into the Data Studio through the theming group, change links in password-reset emails, or disable password requirements. + +- Grant update on `directus_settings` to administrators only. +- If non-administrators need to edit a specific field, scope field permissions tightly, and pair the policy with a [Content Security Policy](/configuration/security-limits#csp) that restricts inline styles and untrusted sources. + +### Users, Roles & Policies + +Write access on `directus_users`, `directus_roles`, or `directus_policies` can be used to escalate privileges. A user with unscoped create on `directus_users` can create a new administrator account. A user with update on `directus_policies` can grant themselves any permission. + +- Grant create, update, and delete on `directus_users`, `directus_roles`, and `directus_policies` to administrators only. +- If users need to manage their own profile, scope update permissions with `id = $CURRENT_USER` and remove the `role` and `policies` fields from field permissions. +- If a role needs to invite users, use field presets to lock the `role` field to a specific non-administrator value, and remove the ability to edit `role` on existing users. + +::callout{icon="material-symbols:warning-rounded" color="warning"} +Unrestricted create access on `directus_users` allows a user to create accounts and assign any role, including the administrator role. Always set a field preset for the `role` field when granting create access. +:: + +### Permissions + +Permissions changes alter access control for the entire project. Update access on `directus_permissions` lets a user grant themselves access to any collection, field, or action. + +Grant create, update, and delete on `directus_permissions` to administrators only. + +### Extensions + +Extensions run code inside the Directus server. Enabling, disabling, or installing an extension changes how the project evaluates permissions, hooks, and custom endpoints. A user who can toggle extensions can disable a security-related extension, such as a custom permission validator or an audit hook, or install a new extension from the Marketplace. + +- Grant update on `directus_extensions` to administrators only. +- For self-hosted projects in production, keep [`MARKETPLACE_TRUST`](/configuration/extensions) set to its default `sandbox` value. Set it to `all` only if you vet every non-sandboxed extension before installation. +- Manage extensions through deployment (shipping them in your `extensions/` directory or a mounted `EXTENSIONS_LOCATION`) rather than through the Data Studio, so extension changes go through code review. + +### Data Model (Fields, Collections & Relations) + +Editing `directus_fields`, `directus_collections`, or `directus_relations` changes the database schema. A user with update access can remove validation rules, change field types, or drop relationship constraints, weakening the guarantees the rest of your permissions depend on. + +Grant create, update, and delete on `directus_fields`, `directus_collections`, and `directus_relations` to administrators only. + +## Audit Read Access on Sensitive System Collections + +Several system collections contain data that bypasses the permissions set on other collections, or exposes sensitive account and network information. Scope read access on these collections deliberately. + +### Activity + +The activity log records who performed each action, from which IP address, with which user agent, and against which items. Reading `directus_activity` exposes the network location and behavior of every user in the project. + +Grant read on `directus_activity` to administrators only, or scope it with an item rule such as `user = $CURRENT_USER` so users only see their own activity. + +### Revisions & Versions + +Revisions and versions store snapshots of item data at the time of each change. Reading `directus_revisions` or `directus_versions` returns the content of those snapshots, which bypasses the current read permissions on the source collection. + +- Grant read on `directus_revisions` and `directus_versions` to administrators only. +- If you need to expose revisions for a specific collection, scope with an item rule that matches the collection and mirrors the user's read permissions on the source collection. + +### Sessions + +`directus_sessions` stores active session tokens and metadata. Reading this collection exposes session identifiers that could be used to impersonate logged-in users. Update or delete access lets a user invalidate the sessions of other users. + +Grant all access on `directus_sessions` to administrators only. + +### Shares + +Shares have their own authentication flow, separate from user login. Reading `directus_shares` exposes share tokens, which grant access to the shared resource without a Directus account and bypass the project's regular permissions. + +Grant all access on `directus_shares` to administrators only. + +## Access Control + +### Minimize the Public Role + +The public role applies to every unauthenticated request. Granting read access on a collection to the public role exposes **all items** in that collection to anyone on the internet, including items you might assume are filtered by your frontend. + +- Only grant public access to collections that are genuinely intended for unauthenticated consumption. +- If only a subset of items should be public (for example, published posts but not drafts), use [custom permissions](/guides/auth/access-control#custom-permissions) to restrict which items and fields are accessible. +- Before deploying, test the public endpoints with an unauthenticated request. This is the simplest way to catch unintended exposure. + +### Scope Junction Table Permissions + +Junction tables connect two collections in a many-to-many relationship. Unrestricted create access on a junction collection lets a user associate any item on one side with any item on the other, regardless of the permissions on the related collections. A user with create on `posts_tags` can attach any tag to any post, even without read or update access to `posts`. + +- Apply custom permissions on junction collections that mirror the intent of the related collections. +- Use item rules scoped to `$CURRENT_USER` where appropriate to restrict which relationships can be created. +- Use field rules to limit which foreign-key values can be set. + +### Require Two-Factor Authentication for Administrators + +Administrator accounts can read and write any data in the project and change permissions. Compromising a single administrator account is equivalent to compromising the entire project. + +Require [Two-Factor Authentication](/guides/auth/2fa) for every user with an administrator policy, and review the administrator user list on a regular cadence. + +## Files & Assets + +### Restrict Public File Uploads + +The public role has no file upload permission by default, and that is the safe choice. Granting the public role create access on `directus_files` lets any unauthenticated request upload arbitrary files to your storage backend, leading to storage exhaustion, delivery of hostile content from your domain, and abuse of your project as an open file host. + +- Do not grant create on `directus_files` to the public role. +- If you need to accept uploads from unauthenticated users (for example, a contact form with attachments), receive the file through a flow that validates MIME type, size, and content, then writes the file under a service account. +- Configure [`FILES_MAX_UPLOAD_SIZE`](/configuration/files#upload-limits) and [`FILES_MIME_TYPE_ALLOW_LIST`](/configuration/files#upload-limits) to limit the shape of acceptable uploads. + +### Prefer Authorization Headers for Asset Requests + +Assets can be requested with an access token in the query string (`/assets/:id?access_token=...`) or with an `Authorization` header. Tokens in the query string are stored in server logs, browser history, and intermediary proxy logs, widening the exposure of every request. + +- Use the `Authorization: Bearer ` header for asset requests from server-side and authenticated client code. +- Reserve query-string tokens for cases where a header cannot be set (for example, direct `` usage), and prefer short-lived tokens for those cases. + +## Server Hardening + +### Block Internal IPs for File Imports + +The `/files/import` endpoint fetches a remote URL and stores the result as a file. The default [`IMPORT_IP_DENY_LIST`](/configuration/security-limits) only blocks `0.0.0.0` and the AWS instance metadata IP `169.254.169.254`. Private network ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), IPv4 localhost (`127.0.0.1`), and IPv6 localhost (`::1`) are not blocked by default. A user with permission to import files can use this endpoint to reach internal services behind your network perimeter, which is a server-side request forgery (SSRF) attack. + +Extend `IMPORT_IP_DENY_LIST` to cover private ranges and both IPv4 and IPv6 localhost. A conservative baseline: + +``` +IMPORT_IP_DENY_LIST=0.0.0.0,127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,fc00::/7,fe80::/10 +``` + +Review this list before granting file-import permissions to non-administrators, and extend it with any internal cloud metadata endpoints specific to your hosting provider. + +### Harden Content Security Policy When Using Custom CSS + +If your project applies custom CSS through the theming group in settings, an attacker with write access to `directus_settings` can pivot a CSS injection into data exfiltration. CSS can load background images from attacker-controlled URLs, encoding extracted values in the request path, so a permissive Content Security Policy turns a theming-field vulnerability into an outbound data leak. + +- Set explicit [`CONTENT_SECURITY_POLICY_DIRECTIVES__*`](/configuration/security-limits#csp) values that restrict `img-src`, `connect-src`, `font-src`, and `style-src` to `'self'` plus the exact domains your project needs. +- Restrict `ASSETS_CONTENT_SECURITY_POLICY_DIRECTIVES__*` separately for the assets endpoint, which serves user-uploaded content. +- Revisit the policy whenever you add a new CDN, embed, or outbound integration to the Data Studio. + +## Next Steps + +::card-group + +:::card{title="Access Control" icon="directus-auth" to="/guides/auth/access-control"} +Understand how permissions, policies, and roles compose access control in Directus. +::: + +:::card{title="Security & Limits" icon="i-ph-gear" to="/configuration/security-limits"} +Environment variables for tokens, cookies, CSP, CORS, rate limiting, and request limits. +::: + +:::card{title="Two-Factor Authentication" icon="directus-auth" to="/guides/auth/2fa"} +Set up and require 2FA for Data Studio and API logins. +::: + +:::card{title="Report a Security Issue" icon="i-ph-shield-warning" to="/community/reporting-and-support/security-reporting"} +How to responsibly disclose a security vulnerability in Directus. +::: + +::