{"id":"PYSEC-2026-1483","summary":"jupyterlab-git has a command injection vulnerability in \"Open Git Repository in Terminal\"","details":"## Overview\n\nOn many platforms, a third party can create a Git repository under a name that includes a shell command substitution [^1] string in the syntax `$(\u003ccommand\u003e)`. These directory names are allowed in macOS and a majority of Linux distributions [^2]. If a user starts `jupyter-lab` in a parent directory of this inappropriately-named Git repository, opens it, and clicks \"Git \u003e Open Git Repository in Terminal\" from the menu bar, then the injected command `\u003ccommand\u003e` is run in the user's shell without the user's permission.\n\nThis issue is occurring because when that menu entry is clicked, `jupyterlab-git` opens the terminal and runs `cd \u003cgit-repo-path\u003e` through the shell to set the current directory [^3]. Doing so runs any command substitution strings present in the directory name, which leads to the command injection issue described here. A previous patch provided an incomplete fix [^4].\n\n[^1]: https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html\n[^2]: https://www.gnu.org/software/libc/manual/html_node/File-Name-Portability.html\n[^3]: https://github.com/jupyterlab/jupyterlab-git/blob/7eb3b06f0092223bd5494688ec264527bbeb2195/src/commandsAndMenu.tsx#L175-L184\n[^4]: https://github.com/jupyterlab/jupyterlab-git/pull/1196\n \n\n## Scope of Impact\n\nThis issue allows for arbitrary code execution via command injection. A wide range of actions are permitted by this issue, including but not limited to: modifying files, exfiltrating data, halting services, or compromising the server's security rules.\n\nWe have scanned the source code of `jupyterlab-git` for other command injection risks, and have not found any at the time of writing.\n\nThis issue was reproduced on the latest release of `jupyterlab-git`, v0.51.0. The steps taken to reproduce this issue are described in the \"Proof-of-concept\" section below.\n \n\n## Proof-of-concept\n\n1. Create a new directory via `mkdir test/ && cd test/`.\n\n2. Create a new Git repository under `test/` with a command substitution string in the directory name by running these commands:\n\n```\nmkdir '$(touch pwned.txt)'\ncd '$(touch pwned.txt)/'\ngit init\ncd ..\n```\n\n3. Start JupyterLab from `test/` by running jupyter lab.\n4. With JupyterLab open in the browser, double click on `$(touch pwned.txt)` in the file browser.\n5. From the top menu bar, click \"Git \u003e Open Git Repository in Terminal\".\n6. Verify that `pwned.txt` is created under `test/`. This demonstrates the command injection issue described here.\n\n## Proof-of-concept mitigation\n\nThe issue can be mitigated by the patch shown below.\n\n\u003cdetails\u003e\u003csummary\u003ePatch (click to expand)\u003c/summary\u003e\n\n```diff\ndiff --git a/src/commandsAndMenu.tsx b/src/commandsAndMenu.tsx\nindex 3779a6c..71ddcea 100644\n--- a/src/commandsAndMenu.tsx\n+++ b/src/commandsAndMenu.tsx\n@@ -164,31 +164,13 @@ export function addCommands(\n     label: trans.__('Open Git Repository in Terminal'),\n     caption: trans.__('Open a New Terminal to the Git Repository'),\n     execute: async args =\u003e {\n-      const main = (await commands.execute(\n-        'terminal:create-new',\n-        args\n-      )) as MainAreaWidget\u003cITerminal.ITerminal\u003e;\n+      const cwd = gitModel.pathRepository;\n+      const main = (await commands.execute('terminal:create-new', {\n+        ...args,\n+        cwd\n+      })) as MainAreaWidget\u003cITerminal.ITerminal\u003e;\n \n-      try {\n-        if (gitModel.pathRepository !== null) {\n-          const terminal = main.content;\n-          terminal.session.send({\n-            type: 'stdin',\n-            content: [\n-              `cd \"${gitModel.pathRepository\n-                .split('\"')\n-                .join('\\\\\"')\n-                .split('`')\n-                .join('\\\\`')}\"\\n`\n-            ]\n-          });\n-        }\n-\n-        return main;\n-      } catch (e) {\n-        console.error(e);\n-        main.dispose();\n-      }\n+      return main;\n```\n\u003c/details\u003e\n\nThis patch removes the `cd \u003cgit-repo-path\u003e` shell command that causes the issue. To preserve the existing behavior, the `cwd` argument is set to `\u003cgit-repo-path\u003e` when a terminal session is created via the `terminal:create-new` JupyterLab command. This preserves the existing application behavior while mitigating the command injection issue.\n\nWe have verified that this patch works when applied to a local installation of `jupyterlab-git`. We have also verified that the `cwd` argument is available in all versions of JupyterLab 4, so this patch should be fully backwards-compatible.\n\n## Workarounds\n\nWe recommend that users upgrade to the patched versions listed on this GHSA. However, if a user is unable to upgrade, there are 3 different ways to mitigate this vulnerability without upgrading to a patch. \n\n1. Disable terminals on `jupyter-server` level:\n    ```\n    c.ServerApp.terminals_enabled =  False\n    ```\n\n2. Disable the terminals server extension:\n    ```\n    jupyter server extension disable jupyter_server_terminals\n    ```\n\n3. Disable the lab extension:\n    ```\n    jupyter labextension disable @jupyterlab/terminal-extension\n    ```","aliases":["CVE-2025-30370","GHSA-cj5w-8mjf-r5f8"],"modified":"2026-07-07T17:46:13.963271480Z","published":"2026-07-07T16:02:50.789127Z","references":[{"type":"WEB","url":"https://github.com/jupyterlab/jupyterlab-git/security/advisories/GHSA-cj5w-8mjf-r5f8"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-30370"},{"type":"WEB","url":"https://github.com/jupyterlab/jupyterlab-git/pull/1196"},{"type":"WEB","url":"https://github.com/jupyterlab/jupyterlab-git/commit/b46482993f76d3a546015c6a94ebed8b77fc2376"},{"type":"PACKAGE","url":"https://github.com/jupyterlab/jupyterlab-git"},{"type":"WEB","url":"https://github.com/jupyterlab/jupyterlab-git/blob/7eb3b06f0092223bd5494688ec264527bbeb2195/src/commandsAndMenu.tsx#L175-L184"},{"type":"PACKAGE","url":"https://pypi.org/project/jupyterlab-git"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-cj5w-8mjf-r5f8"}],"affected":[{"package":{"name":"jupyterlab-git","ecosystem":"PyPI","purl":"pkg:pypi/jupyterlab-git"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.51.1"}]}],"versions":["0.1.1","0.1.2","0.10.0","0.10.1","0.10.1rc0","0.11.0","0.11.0rc0","0.11.0rc1","0.2.0","0.2.2","0.20.0","0.20.0rc0","0.21.0","0.21.0a0","0.21.0a1","0.21.0rc0","0.21.1","0.22.0","0.22.1","0.22.2","0.22.3","0.23.0","0.23.1","0.23.2","0.23.3","0.24.0","0.3.0","0.30.0","0.30.0b1","0.30.0b2","0.30.0b3","0.30.1","0.31.0","0.31.0a0","0.32.0","0.32.1","0.32.2","0.32.3","0.32.4","0.33.0","0.34.0","0.34.1","0.34.2","0.35.0","0.36.0","0.37.0","0.37.1","0.38.0","0.39.0","0.39.1","0.39.2","0.39.3","0.4.4","0.40.0","0.40.1","0.41.0","0.42.0","0.42.0rc0","0.43.0","0.44.0","0.5.0","0.50.0","0.50.0a0","0.50.0a1","0.50.0a2","0.50.0rc0","0.50.1","0.50.2","0.51.0","0.6.0","0.6.1","0.8.0","0.8.1","0.9.0","0.9.0rc1","0.9.1"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/jupyterlab-git/PYSEC-2026-1483.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:L/AC:H/PR:L/UI:R/S:C/C:L/I:H/A:H"}]}