docs icon indicating copy to clipboard operation
docs copied to clipboard

Clarify `polices` in the `roles` and `users` endpoints are `directus_access` records

Open wmasman opened this issue 2 months ago • 4 comments

Describe the Bug

In Directus 11.x (tested on 11.1.1, containerized/Fly.io), when a Role is linked to a Policy via the directus_access table, the Directus API and Permission Engine incorrectly use the id (primary key) of the directus_access record as the Policy UUID, ignoring the actual policy foreign key column.

This causes "Ghost Policies" where the API reports the Role is linked to a non-existent Policy UUID (which is actually the Access record's UUID), resulting in 403 Forbidden errors even when the database link is correct.

To Reproduce

Create a Role (e.g., "Test Role"). Create a Policy (e.g., "Test Policy") with some permissions. Link them via SQL (simulating what the UI does, or a migration): INSERT INTO directus_access (id, role, policy, sort) VALUES ( gen_random_uuid(), -- Generates a random UUID, e.g., '5fd3eefd...' '', '', -- The actual policy, e.g., 'a1111111...' 1 ); Restart Directus (to flush cache). Query the Role via API: GET /roles/ Observe: The policies array contains '5fd3eefd...' (the Access ID), NOT 'a1111111...' (the Policy ID). Result: Permissions defined in "Test Policy" are not applied. Expected Behavior Directus should read the policy column from directus_access to determine which Policy is linked to the Role. The id of the access record should be irrelevant to the permission logic.

Root Cause Analysis It appears the internal logic for hydrating Role policies maps the id field of the junction table object to the policy ID, instead of mapping the policy field.

Workaround Force the directus_access.id to be identical to the directus_access.policy UUID.

INSERT INTO directus_access (id, role, policy, sort) VALUES ( '', -- ID MUST MATCH POLICY ID '', '', 1 ); When id === policy, the bug is masked because reading the wrong column still yields the correct UUID.

Environment Directus Version: 11.x

Deployment: Docker / Fly.io

Directus Version

11.12.0

Hosting Strategy

Self-Hosted (Docker Image)

Database

PostgreSQL (Neon)

wmasman avatar Dec 06 '25 08:12 wmasman

CMS-1559

EDU-502

linear[bot] avatar Dec 06 '25 08:12 linear[bot]

Related issues:

  • #24756
  • #24267

Was this useful? Use the reactions below to give feedback.

triagster[bot] avatar Dec 06 '25 08:12 triagster[bot]

I haven't been able to reproduce the 403 error response issue with the current information on the latest, I'm going to close it for now. That said, I'm happy to continue the conversation here and will reopen the issue once a reproducible case is found.

The policies array containing directus_access records is expected as it is a M2M using the directus_access table as a junction table. By default (e.g. no expansion) we always show the junctions tables ids not the other sides (i.e. policies).

While this is documented for /policies it is missing for /roles, I'll move this to the docs repo so we can fix that.

Issue Reproduction Attempt

  • Assuming the missing role id in the directus_access record insert was a mistake

https://github.com/user-attachments/assets/bdb0d335-fb2f-417f-92bf-e88b1c733b07

ComfortablyCoding avatar Dec 09 '25 18:12 ComfortablyCoding

Documentation should be updated to be inline with https://github.com/directus/directus/pull/23621

ComfortablyCoding avatar Dec 09 '25 18:12 ComfortablyCoding