Problem/Motivation
The token replacement system in PrimaryEntityReferenceTokenHooks exposes entity labels and allows chained token access to referenced entities without verifying that the current user has permission to view those entities. This violates the principle of defense in depth and Drupal's entity access control system.
Specifically, the issues are found in:
src/Hook/PrimaryEntityReferenceTokenHooks.php, lines 176-182 (entity label exposure)src/Hook/PrimaryEntityReferenceTokenHooks.php, lines 192-228 (chained token access)
Security Impact
- Attackers could use tokens to access labels and properties of entities they don't have permission to view
- Violates Drupal's entity access control system
- Could leak sensitive information through entity labels (e.g., private node titles, unpublished content, restricted user names)
- Chained tokens enable deep property access without permission checks
Current Code
The problematic code returns entity labels without access checks:
// Handle field_name:primary token (returns entity label).
if (isset($tokens[$field_name . ':primary'])) {
$referenced_entity = $primary_item->entity;
if ($referenced_entity) {
$replacements[$tokens[$field_name . ':primary']] = $referenced_entity->label();
$bubbleable_metadata->addCacheableDependency($referenced_entity);
}
}
And allows chained token access without verification:
if ($primary_entity_tokens = $this->token->findWithPrefix($tokens, $field_name . ':primary')) {
$referenced_entity = $primary_item->entity;
if ($referenced_entity) {
$bubbleable_metadata->addCacheableDependency($referenced_entity);
// Generates tokens without access checks
$replacements += $this->token->generate(
$target_type,
$chained_tokens,
[$target_type => $referenced_entity],
$options,
$bubbleable_metadata
);
}
}
Proposed resolution
Add entity access checks before exposing entity data through tokens. The token system should respect entity access permissions for the current user.
Recommended Implementation
Add access checks using $referenced_entity->access('view') before returning any entity data:
// Handle field_name:primary token (returns entity label).
if (isset($tokens[$field_name . ':primary'])) {
$referenced_entity = $primary_item->entity;
if ($referenced_entity && $referenced_entity->access('view')) {
$replacements[$tokens[$field_name . ':primary']] = $referenced_entity->label();
$bubbleable_metadata->addCacheableDependency($referenced_entity);
}
}
Apply the same access check pattern to chained token access:
if ($primary_entity_tokens = $this->token->findWithPrefix($tokens, $field_name . ':primary')) {
$referenced_entity = $primary_item->entity;
if ($referenced_entity && $referenced_entity->access('view')) {
$bubbleable_metadata->addCacheableDependency($referenced_entity);
// Only generate tokens if user has view access
$replacements += $this->token->generate(
$target_type,
$chained_tokens,
[$target_type => $referenced_entity],
$options,
$bubbleable_metadata
);
}
}
Remaining tasks
- Review all token replacement code for missing access checks
- Add
$referenced_entity->access('view')checks before exposing entity data inprocessPrimaryTokens()method - Consider whether to check access with current user account or account from context
- Add cache context for user permissions:
$bubbleable_metadata->addCacheContexts(['user.permissions']) - Write kernel tests to verify access checks work correctly
- Test with unpublished nodes, restricted users, and other access-controlled entities
- Update module documentation
User interface changes
None. This is an internal security fix that doesn't affect the UI.
API changes
None. The token API remains unchanged, but now properly enforces entity access control.
Behavioral Change
Tokens that previously returned entity data regardless of permissions will now return empty values when the current user lacks view access to the referenced entity. This is the correct security behavior.
Data model changes
None
Issue fork primary_entity_reference-3570450
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
bluegeek9 commented