{"id":"CVE-2026-54768","aliases":["EUVD-2026-51676","GHSA-jhh7-832h-f8hv"],"status":"published","title":"WPGraphQL has deprecated `user` field on SendPasswordResetEmailPayload that leaks user existence + profile (defeats explicit anti-enumeration design)","description":"WPGraphQL provides a GraphQL API for WordPress sites. From 2.0.0 until 2.15.1, the deprecated user field on SendPasswordResetEmailPayload lets an unauthenticated caller distinguish existing author-class accounts through the sendPasswordResetEmail mutation and obtain public profile fields. This issue is fixed in version 2.15.1.","severity":{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00445,"percentile":0.39144,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":false},"cwes":["CWE-204"],"published":"2026-07-31T22:24:29Z","modified":"2026-11-10T02:30:53Z","affected":[{"source":"ghsa","ecosystem":"Packagist","package":"wp-graphql/wp-graphql","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"2.6.0"}]}]},{"source":"osv","ecosystem":"Packagist","package":"wp-graphql/wp-graphql","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"2.6.0"}]}],"versions":["0.0.2","0.0.20","0.0.21","0.0.22","0.0.23","0.0.24","0.0.25","0.0.26","0.0.27","0.0.28","0.0.29","0.0.3","0.0.30","0.0.31","0.0.5","1.2.2","1.27.2","v0.0.10","v0.0.11","v0.0.12","v0.0.13","v0.0.14","v0.0.15","v0.0.16","v0.0.17","v0.0.18","v0.0.19","v0.0.32","v0.0.33","v0.0.34","v0.0.6","v0.0.7","v0.0.8","v0.0.9","v0.1.0","v0.1.1","v0.1.2","v0.1.3","v0.1.4","v0.10.0","v0.10.1","v0.10.2","v0.10.3","v0.11.0","v0.12.0","v0.12.1","v0.12.2","v0.12.3","v0.13.0","v0.13.1","v0.13.2","v0.13.3","v0.14.0","v0.15.0","v0.15.1","v0.15.2","v0.15.3","v0.15.4","v0.15.5","v0.15.6","v0.2.0","v0.2.1","v0.2.2","v0.2.3","v0.3.0","v0.3.01","v0.3.2","v0.3.3","v0.3.4","v0.3.5","v0.3.6","v0.3.7","v0.3.8","v0.4.0","v0.4.1","v0.4.2","v0.4.3","v0.5.0","v0.5.1","v0.6.0","v0.6.1","v0.6.2","v0.6.3","v0.7.0","v0.7.1","v0.8.0","v0.8.1","v0.8.2","v0.8.3","v0.8.4","v0.9.0","v0.9.1","v1.0","v1.0.1","v1.0.2","v1.0.3","v1.0.4","v1.0.5","v1.1.0","v1.1.1","v1.1.2","v1.1.3","v1.1.4","v1.1.5","v1.1.6","v1.1.7","v1.1.8.0","v1.10.0","v1.11.0","v1.11.1","v1.11.2","v1.11.3","v1.12.0","v1.12.1","v1.12.2","v1.12.3","v1.13.0","v1.13.1","v1.13.10","v1.13.2","v1.13.3","v1.13.4","v1.13.5","v1.13.6","v1.13.7","v1.13.8","v1.13.9","v1.14.0","v1.14.10","v1.14.2","v1.14.3","v1.14.4","v1.14.5","v1.14.6","v1.14.7","v1.14.8","v1.14.9","v1.15.0","v1.16.0","v1.17.0","v1.18.0","v1.18.1","v1.18.2","v1.19.0","v1.2.0","v1.2.1","v1.2.3","v1.2.4","v1.2.5","v1.2.6","v1.20.0","v1.21.0","v1.22.0","v1.22.1","v1.23.0","v1.24.0","v1.25.0","v1.26.0","v1.27.0","v1.27.1","v1.28.0","v1.28.1","v1.29.0","v1.29.1","v1.29.2","v1.29.3","v1.3.0","v1.3.1","v1.3.10","v1.3.2","v1.3.3","v1.3.4","v1.3.5","v1.3.6","v1.3.7","v1.3.8","v1.3.9","v1.30.0","v1.31.0","v1.31.1","v1.32.0","v1.32.1","v1.4.0","v1.4.1","v1.4.2","v1.4.3","v1.4.7","v1.5.0","v1.5.1","v1.5.2","v1.5.3","v1.5.4","v1.5.5","v1.5.6","v1.5.7","v1.5.8","v1.5.9","v1.6.0","v1.6.1","v1.6.10","v1.6.11","v1.6.12","v1.6.2","v1.6.3","v1.6.4","v1.6.5","v1.6.6","v1.6.7","v1.6.8","v1.6.9","v1.7.0","v1.7.1","v1.7.2","v1.8.0","v1.8.1","v1.8.2","v1.8.3","v1.8.4","v1.8.5","v1.8.6","v1.8.7","v1.9.0","v1.9.1","v2.0.0","v2.0.0-beta.1","v2.0.0-beta.2","v2.1.0","v2.1.1","v2.2.0","v2.3.0","v2.3.3","v2.3.6","v2.3.8","v2.5.0","v2.5.1","v2.5.2","v2.5.3","v2.5.4","v2.6.0"]}],"products":[{"vendor":"wp-graphql","product":"wp-graphql"}],"references":[{"url":"https://github.com/wp-graphql/wp-graphql/security/advisories/GHSA-jhh7-832h-f8hv","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/wp-graphql/wp-graphql/releases/tag/wp-graphql/v2.15.1","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-jhh7-832h-f8hv"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/54xxx/CVE-2026-54768.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54768","tags":["advisory"]},{"url":"https://github.com/wp-graphql/wp-graphql","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-54768","title":"WPGraphQL has deprecated `user` field on SendPasswordResetEmailPayload that leaks user existence + profile (defeats explicit anti-enumeration design)","description":"WPGraphQL provides a GraphQL API for WordPress sites. From 2.0.0 until 2.15.1, the deprecated user field on SendPasswordResetEmailPayload lets an unauthenticated caller distinguish existing author-class accounts through the sendPasswordResetEmail mutation and obtain public profile fields. This issue is fixed in version 2.15.1.","status":"published","published":"2026-07-31T22:24:40.639Z","modified":"2026-08-03T20:36:22.466Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium","assessor":"cna"}],"cwes":["CWE-204"],"updated_at":"2026-11-02T13:38:09.774038Z"}],"euvd":[{"id":"EUVD-2026-51676","description":"WPGraphQL provides a GraphQL API for WordPress sites. From 2.0.0 until 2.15.1, the deprecated user field on SendPasswordResetEmailPayload lets an unauthenticated caller distinguish existing author-class accounts through the sendPasswordResetEmail mutation and obtain public profile fields. This issue is fixed in version 2.15.1.","status":"published","published":"2026-07-31T22:24:40Z","modified":"2026-08-03T20:36:22Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T18:02:23.68186Z"}],"ghsa":[{"id":"GHSA-jhh7-832h-f8hv","title":"WPGraphQL has deprecated `user` field on SendPasswordResetEmailPayload that leaks user existence + profile (defeats explicit anti-enumeration design)","description":"## Summary\n\nThe `sendPasswordResetEmail` mutation in WPGraphQL is explicitly designed to prevent user enumeration. The resolver in `src/Mutation/SendPasswordResetEmail.php` states in a code comment:\n\n`// We obsfucate the actual success of this mutation to prevent user enumeration.`\n\nThe mutation always returns `success: true` regardless of whether the supplied username/email belongs to an existing user. The intended public output field is only `success: Boolean`.\n\nHowever, a deprecated `user` field is still registered on the `SendPasswordResetEmailPayload` output type in `src/Deprecated.php` (lines 433-450). This deprecated field resolves to a full `User` object when the supplied username/email corresponds to an existing author-class user, and `null` otherwise — completely undermining the anti-enumeration design.\n\nThe `@todo remove in 3.0.0` comment acknowledges the field is scheduled for removal, but it remains active in all 2.x releases, including current 2.14.1.\n\nDiscovered via source code review on May 29, 2026.\n\n## Details\n\nThe mutation resolver in `src/Mutation/SendPasswordResetEmail.php`:\n\n```php\n$payload = ['success' =\u003e true, 'id' =\u003e null];\n$user_data = self::get_user_data($input['username']);\nif (!$user_data) {\n    graphql_debug(...);\n    return $payload;  // id stays null\n}\n// ...send email, then...\nreturn ['id' =\u003e $user_data-\u003eID, 'success' =\u003e true];\n```\n\nThe intended public output field is only `success`. The `id` is internal-only state for downstream resolvers.\n\n`src/Deprecated.php` registers an additional `user` field on the same payload type:\n\n```php\nregister_graphql_field(\n    'SendPasswordResetEmailPayload',\n    'user',\n    [\n        'type' =\u003e 'User',\n        'deprecationReason' =\u003e static function () { return __('This field will be removed...'); },\n        'resolve' =\u003e static function ($payload, $args, AppContext $context) {\n            return !empty($payload['id'])\n                ? $context-\u003eget_loader('user')-\u003eload_deferred($payload['id'])\n                : null;\n        },\n    ],\n);\n```\n\nThis field reads the internal `$payload['id']` and resolves it through the standard user loader. The User Model's `allowed_restricted_fields` policy permits unauthenticated reads of public author fields (`databaseId`, `name`, `firstName`, `lastName`, `slug`, `description`, `uri`, `url`).\n\n## PoC\n\n```graphql\nmutation EnumerateUser {\n  sendPasswordResetEmail(input: { username: \"victim@example.com\" }) {\n    success\n    user {\n      databaseId\n      name\n      firstName\n      lastName\n      slug\n      description\n      uri\n    }\n  }\n}\n```\n\nBehavior:\n- Non-existing user/email → `data.sendPasswordResetEmail.user` is `null`\n- - Existing author-class user → `data.sendPasswordResetEmail.user` is a full User object with the listed fields populated\n- - `success` always returns `true`, preserving the appearance of obfuscation — the deprecated `user` field is the leak\n## Impact\n\n1. **Username/email enumeration:** unauthenticated attacker can verify whether any username or email is registered, with no WPGraphQL-side rate limiting\n2. 2. **Profile disclosure for author-class users:** for any user with published posts (including editors and administrators), the attacker obtains `databaseId`, `name`, `firstName`, `lastName`, `slug`, `description` (user bio), `uri` — substantially more than mere existence\n3. 3. **Bypasses partial hardening:** sites that disabled the REST API user endpoint, the user XML sitemap, and `?author=N` author redirects may still be vulnerable through this WPGraphQL path\n4. 4. **Spearphishing setup:** firstName/lastName/description for authors provides personalized phishing material\n## Recommended fix\n\nEither remove the deprecated `user` field entirely (advance the existing `@todo remove in 3.0.0`) or change the resolver to always return `null`:\n\n```diff\n'resolve' =\u003e static function ($payload, $args, AppContext $context) {\n-    return !empty($payload['id']) ? $context-\u003eget_loader('user')-\u003eload_deferred($payload['id']) : null;\n- +    // Always null — this deprecated field previously leaked user existence,\n- +    // undermining the anti-enumeration design of the sendPasswordResetEmail mutation.\n- +    return null;\n- },\n- ```\nDefense in depth — change the mutation resolver itself to not populate `$payload['id']` on real success:\n\n```diff\nreturn [\n-    'id'      =\u003e $user_data-\u003eID,\n- +    'id'      =\u003e null,\n-      'success' =\u003e true,\n- ];\n- ```\n\nLuke Granto — independent security researcher operating in good faith. Discovery via source code review of wp-graphql/wp-graphql v2.14.1, approximately 15 minutes from `git clone` to confirmed bug. No live exploitation against any third-party deployment.","status":"published","published":"2026-07-31T22:24:29Z","modified":"2026-07-31T22:24:31Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-204"],"updated_at":"2026-11-02T14:40:55.903445Z"}],"nvd":[{"id":"CVE-2026-54768","description":"WPGraphQL provides a GraphQL API for WordPress sites. From 2.0.0 until 2.15.1, the deprecated user field on SendPasswordResetEmailPayload lets an unauthenticated caller distinguish existing author-class accounts through the sendPasswordResetEmail mutation and obtain public profile fields. This issue is fixed in version 2.15.1.","status":"published","published":"2026-07-31T23:17:24.973Z","modified":"2026-09-10T20:30:11.423Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium","assessor":"secondary"}],"cwes":["CWE-204"],"updated_at":"2026-11-02T14:12:34.290716Z"}],"osv":[{"id":"CVE-2026-54768","title":"WPGraphQL has deprecated `user` field on SendPasswordResetEmailPayload that leaks user existence + profile (defeats explicit anti-enumeration design)","description":"WPGraphQL provides a GraphQL API for WordPress sites. From 2.0.0 until 2.15.1, the deprecated user field on SendPasswordResetEmailPayload lets an unauthenticated caller distinguish existing author-class accounts through the sendPasswordResetEmail mutation and obtain public profile fields. This issue is fixed in version 2.15.1.","status":"published","published":"2026-07-31T22:24:40.639Z","modified":"2026-11-10T02:30:53.11517483Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-204"],"updated_at":"2026-11-11T11:06:48.650013Z"},{"id":"GHSA-jhh7-832h-f8hv","title":"WPGraphQL has deprecated `user` field on SendPasswordResetEmailPayload that leaks user existence + profile (defeats explicit anti-enumeration design)","description":"## Summary\n\nThe `sendPasswordResetEmail` mutation in WPGraphQL is explicitly designed to prevent user enumeration. The resolver in `src/Mutation/SendPasswordResetEmail.php` states in a code comment:\n\n`// We obsfucate the actual success of this mutation to prevent user enumeration.`\n\nThe mutation always returns `success: true` regardless of whether the supplied username/email belongs to an existing user. The intended public output field is only `success: Boolean`.\n\nHowever, a deprecated `user` field is still registered on the `SendPasswordResetEmailPayload` output type in `src/Deprecated.php` (lines 433-450). This deprecated field resolves to a full `User` object when the supplied username/email corresponds to an existing author-class user, and `null` otherwise — completely undermining the anti-enumeration design.\n\nThe `@todo remove in 3.0.0` comment acknowledges the field is scheduled for removal, but it remains active in all 2.x releases, including current 2.14.1.\n\nDiscovered via source code review on May 29, 2026.\n\n## Details\n\nThe mutation resolver in `src/Mutation/SendPasswordResetEmail.php`:\n\n```php\n$payload = ['success' =\u003e true, 'id' =\u003e null];\n$user_data = self::get_user_data($input['username']);\nif (!$user_data) {\n    graphql_debug(...);\n    return $payload;  // id stays null\n}\n// ...send email, then...\nreturn ['id' =\u003e $user_data-\u003eID, 'success' =\u003e true];\n```\n\nThe intended public output field is only `success`. The `id` is internal-only state for downstream resolvers.\n\n`src/Deprecated.php` registers an additional `user` field on the same payload type:\n\n```php\nregister_graphql_field(\n    'SendPasswordResetEmailPayload',\n    'user',\n    [\n        'type' =\u003e 'User',\n        'deprecationReason' =\u003e static function () { return __('This field will be removed...'); },\n        'resolve' =\u003e static function ($payload, $args, AppContext $context) {\n            return !empty($payload['id'])\n                ? $context-\u003eget_loader('user')-\u003eload_deferred($payload['id'])\n                : null;\n        },\n    ],\n);\n```\n\nThis field reads the internal `$payload['id']` and resolves it through the standard user loader. The User Model's `allowed_restricted_fields` policy permits unauthenticated reads of public author fields (`databaseId`, `name`, `firstName`, `lastName`, `slug`, `description`, `uri`, `url`).\n\n## PoC\n\n```graphql\nmutation EnumerateUser {\n  sendPasswordResetEmail(input: { username: \"victim@example.com\" }) {\n    success\n    user {\n      databaseId\n      name\n      firstName\n      lastName\n      slug\n      description\n      uri\n    }\n  }\n}\n```\n\nBehavior:\n- Non-existing user/email → `data.sendPasswordResetEmail.user` is `null`\n- - Existing author-class user → `data.sendPasswordResetEmail.user` is a full User object with the listed fields populated\n- - `success` always returns `true`, preserving the appearance of obfuscation — the deprecated `user` field is the leak\n## Impact\n\n1. **Username/email enumeration:** unauthenticated attacker can verify whether any username or email is registered, with no WPGraphQL-side rate limiting\n2. 2. **Profile disclosure for author-class users:** for any user with published posts (including editors and administrators), the attacker obtains `databaseId`, `name`, `firstName`, `lastName`, `slug`, `description` (user bio), `uri` — substantially more than mere existence\n3. 3. **Bypasses partial hardening:** sites that disabled the REST API user endpoint, the user XML sitemap, and `?author=N` author redirects may still be vulnerable through this WPGraphQL path\n4. 4. **Spearphishing setup:** firstName/lastName/description for authors provides personalized phishing material\n## Recommended fix\n\nEither remove the deprecated `user` field entirely (advance the existing `@todo remove in 3.0.0`) or change the resolver to always return `null`:\n\n```diff\n'resolve' =\u003e static function ($payload, $args, AppContext $context) {\n-    return !empty($payload['id']) ? $context-\u003eget_loader('user')-\u003eload_deferred($payload['id']) : null;\n- +    // Always null — this deprecated field previously leaked user existence,\n- +    // undermining the anti-enumeration design of the sendPasswordResetEmail mutation.\n- +    return null;\n- },\n- ```\nDefense in depth — change the mutation resolver itself to not populate `$payload['id']` on real success:\n\n```diff\nreturn [\n-    'id'      =\u003e $user_data-\u003eID,\n- +    'id'      =\u003e null,\n-      'success' =\u003e true,\n- ];\n- ```\n\nLuke Granto — independent security researcher operating in good faith. Discovery via source code review of wp-graphql/wp-graphql v2.14.1, approximately 15 minutes from `git clone` to confirmed bug. No live exploitation against any third-party deployment.","status":"published","published":"2026-07-31T22:24:29Z","modified":"2026-07-31T22:41:46.30611Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-204"],"updated_at":"2026-11-02T15:46:50.336262Z"}]}}
