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 in processPrimaryTokens() 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

Command icon 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

bluegeek9 created an issue. See original summary.

  • bluegeek9 committed 33ef649e on 1.0.x
    feat: #3570450 Missing Entity Access Checks in Token Replacements
    
bluegeek9’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.