CVE-2026-86254 Overview
CVE-2026-86254 is an incomplete authorization bypass in the wger open-source fitness tracking application. The flaw resides in wger/core/views/user.py, where three user-management views retain a legacy gym-scope check using raw integer comparison instead of the is_same_gym() helper. Gym staff accounts with gym=None affiliation and the gym.manage_gym permission can delete, deactivate, or reactivate any other user with gym=None. The root cause is Python's None != None evaluating as False, causing the scope check to pass unexpectedly. The vulnerability is tracked under CWE-862: Missing Authorization.
Critical Impact
Authenticated attackers with gym-manager privileges can permanently delete, lock out, or reactivate any unaffiliated user account across the wger instance.
Affected Products
- wger fitness manager (all versions through master branch prior to the fix)
- Self-hosted wger deployments with multi-gym staff accounts
- wger instances exposing the gym.manage_gym permission to non-scoped staff
Discovery Timeline
- 2026-09-06 - CVE-2026-86254 published to NVD
- 2026-09-08 - Last updated in NVD database
Technical Details for CVE-2026-86254
Vulnerability Analysis
The wger project scopes administrative actions per gym so that gym staff can only manage users affiliated with their own gym. A prior authorization fix introduced the is_same_gym() helper to standardize this check across user-management views. Three views in wger/core/views/user.py responsible for user deletion, deactivation, and activation were not migrated to the helper. These views continue to compare request.user.userprofile.gym_id against the target user's gym_id using a direct inequality operator.
When both values are None, the comparison None != None returns False, so the guard fails to reject the request. Any staff account with the gym.manage_gym permission and no gym affiliation is treated as being in the same scope as every other unaffiliated user. This grants the attacker the ability to permanently remove accounts, lock legitimate users out through deactivation, or reverse defensive deactivations performed by administrators.
Root Cause
The root cause is inconsistent authorization enforcement. Raw identifier comparison does not model the semantics of a null tenant scope, while is_same_gym() explicitly handles that case. The three unmigrated views therefore expose a scope-confusion condition classified under CWE-862.
Attack Vector
Exploitation requires an authenticated account with gym.manage_gym and gym=None. The attacker issues standard authenticated HTTP POST requests to the vulnerable user delete, deactivate, and activate endpoints, supplying the user ID of any other gym=None account. The server passes the flawed scope check and executes the requested state change. See the GitHub Security Advisory GHSA-mw8f-w6p8-xrf4 and the VulnCheck Advisory on the Incomplete Authorization Fix for full technical details.
Detect Identity Attacks the Moment Authentication Fails
Singularity™ Identity surfaces credential misuse, lateral movement, and privilege escalation in real time, so you can contain the breach before it spreads.
Stop Identity AttacksDetection Methods for CVE-2026-86254
Indicators of Compromise
- Unexpected DELETE or state-change events on user records where the acting staff account and target user both have gym_id set to NULL.
- Django audit log entries showing user deactivation or reactivation performed by accounts without an assigned gym.
- Sudden lockouts reported by users whose accounts share the unaffiliated (gym=None) scope.
Detection Strategies
- Instrument the three affected views in wger/core/views/user.py to log the acting user, target user, and both gym_id values on every invocation.
- Alert on any successful user-management action where either the actor or target has gym_id IS NULL.
- Correlate authentication events with subsequent administrative actions to identify staff accounts abusing gym.manage_gym.
Monitoring Recommendations
- Forward Django request logs and application audit trails to a centralized SIEM for retention and correlation.
- Monitor privileged permission grants such as gym.manage_gym, especially when assigned to accounts without a gym affiliation.
- Track user account state transitions (active, inactive, deleted) and baseline normal rates to surface anomalies.
How to Mitigate CVE-2026-86254
Immediate Actions Required
- Audit all wger staff accounts and remove gym.manage_gym from any account that does not require it.
- Assign an explicit gym value to every staff account so that no privileged user remains in the gym=None scope.
- Review recent user deletion, deactivation, and activation events for unauthorized changes and restore affected accounts.
Patch Information
Apply the upstream fix from the wger project that migrates the three affected views in wger/core/views/user.py to use the is_same_gym() helper. Refer to the GitHub Security Advisory GHSA-mw8f-w6p8-xrf4 for the corrected commit and release guidance. Upgrade to the patched branch or backport the helper into the three vulnerable views in self-managed forks.
Workarounds
- Restrict gym.manage_gym permission assignment through Django admin until the patch is applied.
- Enforce a database constraint or application middleware that rejects user-management requests where actor gym_id is NULL.
- Place the vulnerable endpoints behind an authenticated reverse-proxy rule that blocks requests from staff accounts without a gym affiliation.
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

