VYPR
High severityNVD Advisory· Published Mar 4, 2026· Updated Mar 4, 2026

Craft affected by IDOR via GraphQL @parseRefs

CVE-2026-28696

Description

Craft is a content management system (CMS). Prior to 4.17.0-beta.1 and 5.9.0-beta.1, the GraphQL directive @parseRefs, intended to parse internal reference tags (e.g., {user:1:email}), can be abused by both authenticated users and unauthenticated guests (if a Public Schema is enabled) to access sensitive attributes of any element in the CMS. The implementation in Elements::parseRefs fails to perform authorization checks, allowing attackers to read data they are not authorized to view. This vulnerability is fixed in 4.17.0-beta.1 and 5.9.0-beta.1.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Craft CMS GraphQL @parseRefs directive lacks authorization checks, allowing attackers to read sensitive attributes of any element.

Vulnerability

Overview

CVE-2026-28696 is an authorization bypass vulnerability in Craft CMS's GraphQL @parseRefs directive. The Elements::parseRefs method, which resolves internal reference tags (e.g., {user:1:email}), fails to perform canView() authorization checks before fetching and returning element attributes [1][2]. This allows both authenticated users and unauthenticated guests (if a Public Schema is enabled) to access sensitive data from any element type, including entries, assets, users, and categories [2].

Exploitation

Details

An attacker can exploit this by crafting GraphQL queries that include @parseRefs directives with reference tags targeting arbitrary elements. The vulnerability is polymorphic: getElementTypeByRefHandle allows referencing any element type, and because Craft elements use __get() to resolve custom field handles, attackers are not limited to core attributes [2]. For example, a payload like {user:1:email} can leak user email addresses, while {entry:456:myConfidentialField} can exfiltrate custom field data from private entries. Additionally, accessing methods that trigger errors (e.g., {user:1:authKey}) can expose full server stack traces in GraphQL error responses, revealing internal paths [2].

Impact

Successful exploitation leads to unauthorized disclosure of sensitive information, including user email addresses, usernames, admin status, custom field data, and internal file system paths. This can facilitate privilege escalation, user enumeration, and further attacks against the CMS [2]. The vulnerability is particularly severe when a Public Schema is enabled, as it allows unauthenticated exploitation [1][2].

Mitigation

The vulnerability is fixed in Craft CMS versions 4.17.0-beta.1 and 5.9.0-beta.1 [1][2]. Users should upgrade immediately. The fix makes the @parseRefs directive optional and likely introduces proper authorization checks [4]. No workarounds are documented; disabling the Public Schema may reduce the attack surface but does not fully mitigate the risk for authenticated users.

AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
craftcms/cmsPackagist
>= 4.0.0-RC1, < 4.17.0-beta.14.17.0-beta.1
craftcms/cmsPackagist
>= 5.0.0-RC1, < 5.9.0-beta.15.9.0-beta.1

Affected products

2

Patches

1
4d98a07e4758

Make `@parseRefs` optional

https://github.com/craftcms/cmsbrandonkellyJan 5, 2026via ghsa
7 files changed · +68 6
  • CHANGELOG-WIP.md+1 0 modified
    @@ -9,6 +9,7 @@
     ### Development
     - Added support for referencing environment variables anywhere within settings that support them (e.g. `foo/$ENV_NAME/bar` or `foo-${ENV_NAME}-bar`). ([#17949](https://github.com/craftcms/cms/pull/17949))
     - Added the `uuid()` Twig function.
    +- The `@parseRefs` GraphQL directive is now optional for each GraphQL schema. (GHSA-7x43-mpfg-r9wj)
     
     ### Extensibility
     - Added `craft\services\Search::deleteOrphanedIndexJobs()`.
    
  • src/config/app.php+1 1 modified
    @@ -4,7 +4,7 @@
         'id' => 'CraftCMS',
         'name' => 'Craft CMS',
         'version' => '4.16.17',
    -    'schemaVersion' => '4.17.0.0',
    +    'schemaVersion' => '4.17.0.1',
         'minVersionRequired' => '3.7.11',
         'basePath' => dirname(__DIR__), // Defines the @app alias
         'runtimePath' => '@storage/runtime', // Defines the @runtime alias
    
  • src/helpers/Gql.php+2 0 modified
    @@ -352,6 +352,8 @@ public static function createFullAccessSchema(): GqlSchema
                 $traverser($group);
             }
     
    +        $schema->scope[] = 'directive:parseRefs';
    +
             return $schema;
         }
     
    
  • src/migrations/m260105_191335_parseRefs_schema_component.php+40 0 added
    @@ -0,0 +1,40 @@
    +<?php
    +
    +namespace craft\migrations;
    +
    +use Craft;
    +use craft\db\Migration;
    +
    +/**
    + * m260105_191335_parseRefs_schema_component migration.
    + */
    +class m260105_191335_parseRefs_schema_component extends Migration
    +{
    +    /**
    +     * @inheritdoc
    +     */
    +    public function safeUp(): bool
    +    {
    +        $gql = Craft::$app->getGql();
    +        $schemas = $gql->getSchemas();
    +
    +        foreach ($schemas as $schema) {
    +            if (in_array('directive:parseRefs', $schema->scope)) {
    +                continue;
    +            }
    +
    +            $schema->scope[] = 'directive:parseRefs';
    +            $gql->saveSchema($schema);
    +        }
    +
    +        return true;
    +    }
    +
    +    /**
    +     * @inheritdoc
    +     */
    +    public function safeDown(): bool
    +    {
    +        return true;
    +    }
    +}
    
  • src/services/Gql.php+7 3 modified
    @@ -375,7 +375,7 @@ public function getSchemaDef(?GqlSchema $schema = null, bool $prebuildSchema = f
                     'typeLoader' => TypeLoader::class . '::loadType',
                     'query' => TypeLoader::loadType('Query'),
                     'mutation' => TypeLoader::loadType('Mutation'),
    -                'directives' => $this->_loadGqlDirectives(),
    +                'directives' => $this->_loadGqlDirectives($schema),
                 ];
     
                 // If we're not required to pre-build the schema the relevant GraphQL types will be added to the Schema
    @@ -1391,21 +1391,25 @@ private function _registerGqlMutations(): void
         /**
          * Get GraphQL query definitions
          *
    +     * @param GqlSchema|null $schema
          * @return GqlDirective[]
          */
    -    private function _loadGqlDirectives(): array
    +    private function _loadGqlDirectives(?GqlSchema $schema): array
         {
             /** @var class-string<Directive>[] $directiveClasses */
             $directiveClasses = [
                 // Directives
                 FormatDateTime::class,
                 Markdown::class,
                 Money::class,
    -            ParseRefs::class,
                 StripTags::class,
                 Trim::class,
             ];
     
    +        if (in_array('directive:parseRefs', $schema->scope)) {
    +            $directiveClasses[] = ParseRefs::class;
    +        }
    +
             if (!Craft::$app->getConfig()->getGeneral()->disableGraphqlTransformDirective) {
                 $directiveClasses[] = Transform::class;
             }
    
  • src/templates/graphql/schemas/_edit.twig+15 2 modified
    @@ -35,7 +35,7 @@
     
                 <li>
                     {{ checkbox({
    -                    label: props.label,
    +                    label: raw(props.label|md(inlineOnly=true, encode=true)),
                         name: 'permissions[]',
                         value: permissionName,
                         checked: checked,
    @@ -101,7 +101,7 @@
             </div>
         {% endfor %}
     
    -    <hr/>
    +    <hr>
         <h2>{{ 'Choose the available mutations for this schema:'|t('app') }}</h2>
     
         {% for category, catPermissions in schemaComponents.mutations|filter %}
    @@ -113,6 +113,19 @@
             </div>
         {% endfor %}
     
    +  <hr>
    +  <h2>{{ 'Choose optional features available to this schema:'|t('app') }}</h2>
    +
    +  <div class="user-permissions">
    +    {{ permissionList(schema, {
    +      'directive:parseRefs': {
    +        label: '{name} directive'|t('app', {
    +          name: '`@parseRefs`',
    +        }),
    +        warning: 'Provides read-only access to user data and most content.',
    +      },
    +    }) }}
    +  </div>
     
     {% endblock %}
     
    
  • src/translations/en/app.php+2 0 modified
    @@ -1154,6 +1154,7 @@
         'Propagating {type}' => 'Propagating {type}',
         'Propagation Key Format' => 'Propagation Key Format',
         'Propagation Method' => 'Propagation Method',
    +    'Provides read-only access to user data and most content.' => 'Provides read-only access to user data and most content.',
         'Province' => 'Province',
         'Pruning extra revisions' => 'Pruning extra revisions',
         'Public Schema' => 'Public Schema',
    @@ -1977,6 +1978,7 @@
         '{first}-{last} of {total}' => '{first}–{last} of {total}',
         '{names} {total, plural, =1{is installed as a trial} other{are installed as trials}}.' => '{names} {total, plural, =1{is installed as a trial} other{are installed as trials}}.',
         '{name} active, more info' => '{name} active, more info',
    +    '{name} directive' => '{name} directive',
         '{name} folder' => '{name} folder',
         '{name} has been added, but an error occurred when installing it.' => '{name} has been added, but an error occurred when installing it.',
         '{name} is licensed for the {licenseEdition} edition, but the {currentEdition} edition is installed.' => '{name} is licensed for the {licenseEdition} edition, but the {currentEdition} edition is installed.',
    

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.