Aug 1, 2026·7 min read·4 visits
A deprecated field resolver in WPGraphQL's password reset mutation leaks user existence and profile metadata, undermining anti-enumeration controls.
An observable response discrepancy vulnerability in WPGraphQL versions 2.0.0 through 2.15.0 allows unauthenticated remote attackers to enumerate users and extract public profile metadata. Although the password reset mutation is designed to return a uniform success response to prevent enumeration, a legacy deprecated field resolver bypasses this mechanism by resolving the associated user profile if the target account exists.
The vulnerability resides in WPGraphQL, an open-source WordPress plugin that exposes a GraphQL API for interacting with WordPress sites. Specifically, the flaw exists within the implementation of the sendPasswordResetEmail mutation and its corresponding output type payload. Although the mutation was designed to prevent user enumeration, legacy field definitions undermine this defense.
By default, password reset mechanisms should not reveal whether a provided username or email address exists in the database. When a system provides distinct responses based on the validity of an identity, it introduces an observable response discrepancy. WPGraphQL intended to mask this by always returning a success indicator.
However, a deprecated GraphQL field resolver on the response payload class fails to respect this design. Unauthenticated attackers can request this deprecated field to determine the existence of any user on the system. If the user exists, the application leaks their public profile data; if not, it returns null.
This behavior exposes a severe information disclosure vector on WordPress sites running WPGraphQL. Because the GraphQL endpoint is publicly accessible by default, this vulnerability exposes system configurations, user handles, and administrative metadata.
The root cause of this vulnerability lies in an architectural misalignment between the password reset mutation resolver and a legacy field definition. The primary resolver is defined in src/Mutation/SendPasswordResetEmail.php. To prevent user enumeration, this resolver contains logic to always output a status of true, while conditionally populating an internal payload parameter.
When a mutation request is processed, the resolver checks if the requested username or email belongs to a registered WordPress user. If the user exists, the internal array $payload is populated with the user's database ID. If the user does not exist, the ID field remains set to null. Crucially, the public-facing schema for the payload was modified to hide this ID, returning only the success status to the client.
However, the file src/Deprecated.php registers a legacy field named user directly on the SendPasswordResetEmailPayload type. The field resolver for this legacy property extracts the internal database ID from the parent payload. If the ID is not null, the resolver triggers a deferred load of the corresponding User object, returning it to the query execution pipeline.
Because the GraphQL engine processes fields requested by the client, an attacker can explicitly request the deprecated user field. WPGraphQL allows unauthenticated clients to read public fields of user objects, meaning the full user metadata is returned to the client when a matching account is found. This behavior bypasses the protection and allows systematic enumeration.
Analyzing the vulnerable code path demonstrates how the legacy resolver exposes the hidden internal state. In src/Deprecated.php, the legacy user field is registered on the payload type with the following resolver function:
// src/Deprecated.php (vulnerable state)
register_graphql_field(
'SendPasswordResetEmailPayload',
'user',
[
'type' => 'User',
'deprecationReason' => static function () { return __('This field will be removed...'); },
'resolve' => static function ($payload, $args, AppContext $context) {
// Vulnerability: Resolves the user if the internal 'id' is populated
return !empty($payload['id'])
? $context->get_loader('user')->load_deferred($payload['id'])
: null;
},
],
);The mutation resolver in src/Mutation/SendPasswordResetEmail.php populates the ID when a match is found, believing this ID is internal and unexposed to the public Schema:
// src/Mutation/SendPasswordResetEmail.php (vulnerable state)
$payload = ['success' => true, 'id' => null];
$user_data = self::get_user_data($input['username']);
if (!$user_data) {
return $payload; // ID remains null, success is true
}
// ... executes password reset process ...
return [
'id' => $user_data->ID, // ID is exposed to the deprecated resolver payload
'success' => true,
];To resolve this flaw, the developers decoupled the internal payload representation from the database ID entirely. The patch hardcodes the deprecated resolver to always return null, and ensures the mutation resolver no longer leaks the ID in its return array. Below is the patch implementation:
// src/Deprecated.php (patched state)
register_graphql_field(
'SendPasswordResetEmailPayload',
'user',
[
'type' => 'User',
'deprecationReason' => static function () { return __('This field will be removed...'); },
'resolve' => static function ($payload, $args, AppContext $context) {
// Patch: Always return null to prevent information exposure
return null;
},
],
);Additionally, the mutation payload was modified to prevent returning the database ID in the successful execution return statement:
// src/Mutation/SendPasswordResetEmail.php (patched state)
return [
'id' => null, // Hardcoded to null to prevent resolution down the chain
'success' => true,
];This fix is complete and robust because it removes the data propagation path completely. No variant attacks can re-exploit this code path since the internal database ID never reaches any schema resolvers.
Exploitation of this vulnerability requires no authentication and can be performed via standard HTTP POST requests directed at the /graphql endpoint. An attacker crafts a GraphQL query that targets the sendPasswordResetEmail mutation and requests the deprecated user field nested within the payload.
The payload is structured to request public profile fields of the resolved user, such as databaseId, name, firstName, lastName, and slug. A standard attack payload looks as follows:
mutation EnumerateUser {
sendPasswordResetEmail(input: { username: "target-user" }) {
success
user {
databaseId
name
slug
}
}
}If the queried username does not exist on the system, the server returns a JSON response indicating success, but the user block is null. This mimics a successful submission without leaking state:
{
"data": {
"sendPasswordResetEmail": {
"success": true,
"user": null
}
}
}If the queried username belongs to an existing user, the deprecated resolver executes and returns the populated User object. The attacker receives detailed account information, confirming the account existence and harvesting username slugs:
{
"data": {
"sendPasswordResetEmail": {
"success": true,
"user": {
"databaseId": 4,
"name": "Jane Doe",
"slug": "jane-doe"
}
}
}
}The impact of this information disclosure vulnerability is high, particularly for installations relying on obscured administrative handles to prevent credential-based attacks. By extracting user database IDs and slugs, attackers bypass standard security mechanisms designed to limit exposure.
WordPress hardening guidelines frequently recommend disabling the default WP REST API users endpoint (/wp-json/wp/v2/users) and blocking the standard ?author=N query redirection parameter. This vulnerability completely bypasses those mitigations, restoring full enumeration capability via the GraphQL API interface.
The harvested data simplifies brute-force attacks and credential stuffing campaigns, since attackers can construct exact target lists. Furthermore, public fields like user descriptions or bios provide context-rich information that can be utilized to craft high-credibility spearphishing messages targeting specific administrators.
The CVSS v4.0 base score is rated at 6.9, reflecting network accessibility with low complexity and no privilege requirements. While confidentiality is impacted, the vulnerability does not directly permit data modification or service interruption.
The primary and recommended remediation strategy is to upgrade the WPGraphQL plugin to version 2.15.1 or newer. This update disables the data leak in both the deprecated field handler and the mutation payload returning process.
When an immediate upgrade is not feasible, administrators can apply a manual hotfix by editing the affected PHP source files. In src/Deprecated.php, replace the return statement inside the user field resolver with return null;. In src/Mutation/SendPasswordResetEmail.php, modify the successful return array to assign 'id' => null.
Detection of exploitation attempts can be performed by analyzing web server logs and HTTP request bodies. Intrusion detection systems (IDS) and web application firewalls (WAF) can inspect POST requests to the /graphql route. A signature can be deployed to flag or block requests that call sendPasswordResetEmail while requesting the nested user field.
# Web Application Firewall detection pattern
mutation.*sendPasswordResetEmail.*user\s*\{Any request matching this pattern is highly anomalous and indicates potential enumeration activity, as legitimate applications have no valid reason to query a deprecated field inside a password reset transaction.
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| Product | Affected Versions | Fixed Version |
|---|---|---|
WPGraphQL wp-graphql | >= 2.0.0, < 2.15.1 | 2.15.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-204 |
| Attack Vector | Network |
| CVSS v4.0 | 6.9 (Medium) |
| Exploit Status | Proof of Concept Available |
| CISA KEV Status | Not Listed |
| Ransomware Association | No Known Use |
| Impact | Unauthenticated Username Enumeration and Metadata Disclosure |
The product behaves differently when queries contain valid values versus invalid values, allowing attackers to determine database record existence.
An open redirect vulnerability exists in core-geonetwork from versions 3.12.0 until 4.2.16 and 4.4.11 due to unsafe redirect validation in GeonetworkOAuth2LoginAuthenticationFilter and KeycloakAuthenticationProcessingFilter. An attacker can construct a protocol-relative URL to bypass local redirection checks and redirect authenticated users to malicious external domains.
Between versions 1.0.0 and 1.3.1, the gemini-bridge Model Context Protocol (MCP) server failed to restrict candidate file paths to the workspace root when processing files in inline mode. This allowed unauthenticated local users, or remote attackers executing prompt-injection payloads against connected AI agents, to traverse the directory tree and read arbitrary system files. The retrieved contents were subsequently forwarded to the external Gemini AI CLI and returned in the round-trip response.
FileBrowser Quantum (a fork of Filebrowser) prior to version 1.4.3-beta is vulnerable to multiple directory traversal flaws in its subtitle handler endpoint (`GET /api/media/subtitles`). This allows authenticated users with standard access to read arbitrary text files on the host system.
A security vulnerability in sigstore-go prior to version 1.2.1 allowed the use of expired or retired self-managed long-lived public keys wrapped in an ExpiringKey configuration to successfully sign code or artifacts. Because the verification pipeline verified the cryptographic signatures and RFC 3161 timestamps but failed to perform a temporal boundary check on the public key's validity window, verifiers running affected versions would mistakenly accept signatures produced outside of the key's designated operational lifetime.
Improper input validation of the supiOrSuci field in free5GC Authentication Server Function (AUSF) allows unauthenticated remote attackers to trigger an unhandled parsing exception, resulting in a Denial of Service (DoS) and internal stack trace exposure.
The zaino-state crate contains two critical flaws in its block reorganization and state synchronization logic. An unbounded recursive async function handling block reorganization fails to validate cyclic relationships, enabling network peers to cause infinite loops that exhaust CPU and memory resources. Furthermore, a logical pruning error during non-finalized block cache trimming can purge all cached blocks, triggering an immediate panic and crash of the daemon.