VYPR
Moderate severityNVD Advisory· Published Nov 13, 2025· Updated Nov 14, 2025

Directus Vulnerable to Information Leakage in Existing Collections

CVE-2025-64749

Description

Directus is a real-time API and App dashboard for managing SQL database content. An observable difference in error messaging was found in the Directus REST API in versions of Directus prior to version 11.13.0. The /items/{collection} API returns different error messages for two cases: when a user tries to access an existing collection which they are not authorized to access, and when user tries to access a non-existing collection. The two differing error messages leak the existence of collections to users which are not authorized to access these collections. Version 11.13.0 fixes the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
directusnpm
< 11.13.011.13.0
@directus/apinpm
< 32.0.032.0.0

Affected products

1

Patches

1
f99c9b89071f

Merge from fork (#26109)

https://github.com/directus/directusBrainslugNov 4, 2025via ghsa
4 files changed · +15 17
  • api/src/middleware/collection-exists.ts+2 2 modified
    @@ -3,15 +3,15 @@
      */
     
     import type { RequestHandler } from 'express';
    -import { ForbiddenError } from '@directus/errors';
     import { systemCollectionRows } from '@directus/system-data';
     import asyncHandler from '../utils/async-handler.js';
    +import { createCollectionForbiddenError } from '../permissions/modules/process-ast/utils/validate-path/create-error.js';
     
     const collectionExists: RequestHandler = asyncHandler(async (req, _res, next) => {
     	if (!req.params['collection']) return next();
     
     	if (req.params['collection'] in req.schema.collections === false) {
    -		throw new ForbiddenError();
    +		throw createCollectionForbiddenError('', req.params['collection']);
     	}
     
     	req.collection = req.params['collection'];
    
  • api/src/permissions/modules/process-payload/process-payload.ts+6 12 modified
    @@ -1,4 +1,3 @@
    -import { ForbiddenError } from '@directus/errors';
     import type { Accountability, Filter, Item, PermissionsAction } from '@directus/types';
     import { parseFilter, validatePayload } from '@directus/utils';
     import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
    @@ -10,6 +9,10 @@ import { extractRequiredDynamicVariableContext } from '../../utils/extract-requi
     import { fetchDynamicVariableData } from '../../utils/fetch-dynamic-variable-data.js';
     import { contextHasDynamicVariables } from '../process-ast/utils/context-has-dynamic-variables.js';
     import { isFieldNullable } from './lib/is-field-nullable.js';
    +import {
    +	createCollectionForbiddenError,
    +	createFieldsForbiddenError,
    +} from '../process-ast/utils/validate-path/create-error.js';
     
     export interface ProcessPayloadOptions {
     	accountability: Accountability;
    @@ -38,9 +41,7 @@ export async function processPayload(options: ProcessPayloadOptions, context: Co
     		);
     
     		if (permissions.length === 0) {
    -			throw new ForbiddenError({
    -				reason: `You don't have permission to "${options.action}" from collection "${options.collection}" or it does not exist.`,
    -			});
    +			throw createCollectionForbiddenError('', options.collection);
     		}
     
     		const fieldsAllowed = uniq(permissions.map(({ fields }) => fields ?? []).flat());
    @@ -50,14 +51,7 @@ export async function processPayload(options: ProcessPayloadOptions, context: Co
     			const notAllowed = difference(fieldsUsed, fieldsAllowed);
     
     			if (notAllowed.length > 0) {
    -				const fieldStr = notAllowed.map((field) => `"${field}"`).join(', ');
    -
    -				throw new ForbiddenError({
    -					reason:
    -						notAllowed.length === 1
    -							? `You don't have permission to access field ${fieldStr} in collection "${options.collection}" or it does not exist.`
    -							: `You don't have permission to access fields ${fieldStr} in collection "${options.collection}" or they do not exist.`,
    -				});
    +				throw createFieldsForbiddenError('', options.collection, notAllowed);
     			}
     		}
     
    
  • api/src/permissions/modules/validate-access/validate-access.ts+2 3 modified
    @@ -3,6 +3,7 @@ import type { Accountability, PermissionsAction, PrimaryKey } from '@directus/ty
     import type { Context } from '../../types.js';
     import { validateCollectionAccess } from './lib/validate-collection-access.js';
     import { validateItemAccess } from './lib/validate-item-access.js';
    +import { createCollectionForbiddenError } from '../process-ast/utils/validate-path/create-error.js';
     
     export interface ValidateAccessOptions {
     	accountability: Accountability;
    @@ -21,9 +22,7 @@ export interface ValidateAccessOptions {
     export async function validateAccess(options: ValidateAccessOptions, context: Context) {
     	// Skip further validation if the collection does not exist
     	if (!options.skipCollectionExistsCheck && options.collection in context.schema.collections === false) {
    -		throw new ForbiddenError({
    -			reason: `You don't have permission to "${options.action}" from collection "${options.collection}" or it does not exist.`,
    -		});
    +		throw createCollectionForbiddenError('', options.collection);
     	}
     
     	if (options.accountability.admin === true) {
    
  • .changeset/late-cobras-report.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +'@directus/api': patch
    +---
    +
    +Improved error consistency
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.