Jul 23, 2026·5 min read·20 visits
An authorization validation bypass and configuration mapping bug in JupyterLab's PluginManager allow authenticated users to circumvent extension locks and disable or enable arbitrary plugins through direct API requests.
JupyterLab's PluginManager contains an authorization bypass vulnerability allowing authenticated users to modify the state of locked extensions or plugins. Although the frontend user interface visually locks and disables toggle components for administrative configurations, the backend API fails to perform robust validation. Standard users can craft direct HTTP API requests to modify plugin states, completely bypassing administrative restrictions.
JupyterLab relies on an extensible plugin system where individual components control standard interface capabilities, administrative rules, and external network interactions.
Administrators in shared deployments, such as JupyterHub environments, often use lock policies to prevent standard users from disabling critical controls. These locked components may include auditing scripts, storage restrictions, download limits, or proxy features.
Although the frontend application visually disables the relevant interface buttons, the backend API endpoint (/lab/api/plugins) fails to robustly enforce authorization limits when accessed directly. Standard users can exploit this server-side gap to execute commands on locked extensions, violating administrative boundaries.
The root cause of this vulnerability involves two distinct server-side validation gaps in the JupyterLab backend infrastructure.
The first gap lies in how lock rules are evaluated inside jupyterlab/extensions/manager.py. In JupyterLab, plugin identifiers follow a hierarchical format consisting of the parent extension name and the child plugin name separated by a colon, such as <extension-name>:<plugin-identifier>.
When administrators apply a lock to an entire extension, only the parent extension name is stored in the lock registry. When a standard user sends a request to disable a specific plugin, the _find_locked method only checks if the exact, fully qualified child identifier is in the lock registry. Because it fails to parse and inspect the parent portion of the identifier, the server incorrectly determines that the plugin is unlocked.
The second gap stems from a configuration mismatch during the application startup configuration. Standard server initialization permits the option --LabApp.lock_all_plugins=True to globally lock all plugins. The launcher in jupyterlab/labapp.py passes this configuration parameter using the key "all_locked", but the PluginManager initialization logic expects the key "lock_all". This typographical error causes the global lock setting to be silently dropped during server initialization.
Examining the patch diff reveals the corrective validation and key-mapping changes across the affected files.
In jupyterlab/extensions/manager.py, the validation check was refactored to parse child plugin strings and check both the specific identifier and the parent extension identifier against the lock rules list.
# Vulnerable Implementation
def _find_locked(self, plugins_or_extensions: list[str]) -> frozenset[str]:
for plugin in plugins_or_extensions:
if ":" in plugin:
# Only verifies the exact child identifier string
if plugin in self.options.lock_rules:
locked_subset.add(plugin)
# Patched Implementation
def _find_locked(self, plugins_or_extensions: list[str]) -> frozenset[str]:
for plugin in plugins_or_extensions:
if ":" in plugin:
# Extracts parent extension name and checks both levels
extension = plugin.split(":")[0]
if plugin in self.options.lock_rules or extension in self.options.lock_rules:
locked_subset.add(plugin)Additionally, the option dictionary key in jupyterlab/labapp.py was aligned with the exact string that the backend validation engine expected to find.
# Vulnerable Mapping
ext_options={
"lock_rules": lock_rules,
"all_locked": self.lock_all_plugins,
}
# Patched Mapping
ext_options={
"lock_rules": lock_rules,
"lock_all": self.lock_all_plugins,
}These modifications ensure that both targeted parent locks and global lock policies are correctly parsed, loaded, and evaluated by the server backend.
To exploit this vulnerability, an attacker must have authenticated access to a standard user account on the target JupyterLab server. Because the frontend interface prevents toggling via standard menus, the attacker bypasses the graphical console completely.
The attacker retrieves their active session cookie or authorization header and structures a manual HTTP POST request directed at /lab/api/plugins. This payload contains the parameters to disable the target security or auditing plugin.
POST /lab/api/plugins HTTP/1.1
Host: target-jupyterlab-server.com
Content-Type: application/json
Authorization: Token [user-api-token]
{
"cmd": "disable",
"plugin_name": "jupyterlab-hardening-extension:download-limiter"
}Because the administrative lock was applied to the parent extension (jupyterlab-hardening-extension) and not the explicit child string, the backend verification function determines that the plugin is unlocked. The server executes the modification, disabling the designated control and returning a success status code to the client.
This vulnerability is highly significant in multi-user Jupyter environments such as JupyterHub configurations, where host controls depend on locked plugins.
By systematically disabling locked plugins, an attacker can bypass operational limitations, escape container restrictions, and avoid active monitoring controls. This effectively deactivates any local auditing tools or transfer limitations applied by administrative security frameworks.
However, this vulnerability does not directly permit initial remote code execution or privilege escalation on the underlying host operating system. The attacker must possess authenticated login credentials to interact with the API, making it an internal post-authentication policy evasion vector.
The standard and recommended remediation path is to upgrade JupyterLab to the officially patched versions.
Administrators using the 4.5.x release line should update to version 4.5.10 or higher, and those on the 4.6.x line should upgrade to version 4.6.2 or higher. Any downstream wrapper distributions should have their requirements updated to reflect these dependency parameters.
If patching is not immediately feasible, administrators can temporarily mitigate the vulnerability by changing their deployment policies. Rather than applying locks at the parent extension level, lock files must register each child plugin identifier individually.
jupyter labextension lock <extension-name>:<child-plugin-id-1>
jupyter labextension lock <extension-name>:<child-plugin-id-2>This explicit locking ensures that direct API queries will match the exact strings in the lock list, neutralizing the parent parsing bypass.
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
jupyterlab Project Jupyter | >= 4.1.0, <= 4.5.9 | 4.5.10 |
jupyterlab Project Jupyter | >= 4.6.0, <= 4.6.1 | 4.6.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-602, CWE-863 |
| Attack Vector | Network |
| CVSS | 6.0 |
| Impact | High Integrity, Low Confidentiality |
| Exploit Status | PoC Available |
| KEV Status | Not Listed |
The application's UI visually disables/locks the interface components to prevent users from toggling extension states, but the backend fails to properly validate and reject direct API state-change requests from the client.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.