Clarify `polices` in the `roles` and `users` endpoints are `directus_access` records
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...'
'
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 (
'
Environment Directus Version: 11.x
Deployment: Docker / Fly.io
Directus Version
11.12.0
Hosting Strategy
Self-Hosted (Docker Image)
Database
PostgreSQL (Neon)
Related issues:
- #24756
- #24267
Was this useful? Use the reactions below to give feedback.
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_accessrecord insert was a mistake
https://github.com/user-attachments/assets/bdb0d335-fb2f-417f-92bf-e88b1c733b07
Documentation should be updated to be inline with https://github.com/directus/directus/pull/23621