From 72844bfec20100514011379bd99f661b60c2c199 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:28:46 +0000 Subject: [PATCH 1/5] Path resolution by kernel manager and providers --- jupyter_client/manager.py | 6 ++++++ jupyter_client/provisioning/local_provisioner.py | 12 ++++++++++++ jupyter_client/provisioning/provisioner_base.py | 15 +++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/jupyter_client/manager.py b/jupyter_client/manager.py index 088acd6c..22fa0fe6 100644 --- a/jupyter_client/manager.py +++ b/jupyter_client/manager.py @@ -279,6 +279,12 @@ def client(self, **kwargs: t.Any) -> BlockingKernelClient: # Kernel management # -------------------------------------------------------------------------- + def resolve_path(self, path: str) -> t.Optional[str]: + """Resolve path to given file.""" + assert self.provisioner is not None + print('kernel manager resolving path!', path, 'to', self.provisioner.resolve_path(path)) + return self.provisioner.resolve_path(path) + def update_env(self, *, env: t.Dict[str, str]) -> None: """ Allow to update the environment of a kernel manager. diff --git a/jupyter_client/provisioning/local_provisioner.py b/jupyter_client/provisioning/local_provisioner.py index 42d8d32d..f543aff2 100644 --- a/jupyter_client/provisioning/local_provisioner.py +++ b/jupyter_client/provisioning/local_provisioner.py @@ -3,6 +3,7 @@ # Distributed under the terms of the Modified BSD License. import asyncio import os +import pathlib import signal import sys from typing import TYPE_CHECKING, Any, Dict, List, Optional @@ -31,6 +32,7 @@ class LocalProvisioner(KernelProvisionerBase): # type:ignore[misc] pgid = None ip = None ports_cached = False + cwd = None @property def has_process(self) -> bool: @@ -217,8 +219,18 @@ async def launch_kernel(self, cmd: List[str], **kwargs: Any) -> KernelConnection self.pid = self.process.pid self.pgid = pgid + self.cwd = kwargs.get('cwd', pathlib.Path.cwd()) return self.connection_info + async def resolve_path(self, path_str: str) -> Optional[str]: + """Resolve path to given file.""" + path = pathlib.Path(path_str).expanduser() + if path.is_absolute(): + return path.as_posix() + if self.cwd: + return (pathlib.Path(self.cwd) / path).resolve().as_posix() + return None + @staticmethod def _scrub_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]: """Remove any keyword arguments that Popen does not tolerate.""" diff --git a/jupyter_client/provisioning/provisioner_base.py b/jupyter_client/provisioning/provisioner_base.py index eff89432..d90c68a2 100644 --- a/jupyter_client/provisioning/provisioner_base.py +++ b/jupyter_client/provisioning/provisioner_base.py @@ -219,6 +219,21 @@ def get_stable_start_time(self, recommended: float = 10.0) -> float: """ return recommended + def resolve_path(self, path: str) -> Optional[str]: + """ + Returns the path resolved relative to kernel working directory. + + For example, path `my_code.py` for a kernel started in `/tmp/` + should result in `/tmp/my_code.py`, while path `~/test.py` for + a kernel started in `/home/my_user/` should resolve to the + (fully specified) `/home/my_user/test.py` path. + + The provisioner may choose not to resolve any paths, or restrict + the resolution to paths local to the kernel working directory + to prevent path traversal and exposure of file system layout. + """ + return None + def _finalize_env(self, env: Dict[str, str]) -> None: """ Ensures env is appropriate prior to launch. From 15b622b8a645473b5573370435299b5dc9dd257d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:32:27 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- jupyter_client/manager.py | 2 +- jupyter_client/provisioning/local_provisioner.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyter_client/manager.py b/jupyter_client/manager.py index 22fa0fe6..cc07e117 100644 --- a/jupyter_client/manager.py +++ b/jupyter_client/manager.py @@ -282,7 +282,7 @@ def client(self, **kwargs: t.Any) -> BlockingKernelClient: def resolve_path(self, path: str) -> t.Optional[str]: """Resolve path to given file.""" assert self.provisioner is not None - print('kernel manager resolving path!', path, 'to', self.provisioner.resolve_path(path)) + print("kernel manager resolving path!", path, "to", self.provisioner.resolve_path(path)) return self.provisioner.resolve_path(path) def update_env(self, *, env: t.Dict[str, str]) -> None: diff --git a/jupyter_client/provisioning/local_provisioner.py b/jupyter_client/provisioning/local_provisioner.py index f543aff2..a15c6f21 100644 --- a/jupyter_client/provisioning/local_provisioner.py +++ b/jupyter_client/provisioning/local_provisioner.py @@ -219,7 +219,7 @@ async def launch_kernel(self, cmd: List[str], **kwargs: Any) -> KernelConnection self.pid = self.process.pid self.pgid = pgid - self.cwd = kwargs.get('cwd', pathlib.Path.cwd()) + self.cwd = kwargs.get("cwd", pathlib.Path.cwd()) return self.connection_info async def resolve_path(self, path_str: str) -> Optional[str]: From f2daee73b3bf8529414014160ca8021e9a8236ae Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:12:12 +0000 Subject: [PATCH 3/5] Cleanup --- jupyter_client/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jupyter_client/manager.py b/jupyter_client/manager.py index cc07e117..623f9ce5 100644 --- a/jupyter_client/manager.py +++ b/jupyter_client/manager.py @@ -282,7 +282,6 @@ def client(self, **kwargs: t.Any) -> BlockingKernelClient: def resolve_path(self, path: str) -> t.Optional[str]: """Resolve path to given file.""" assert self.provisioner is not None - print("kernel manager resolving path!", path, "to", self.provisioner.resolve_path(path)) return self.provisioner.resolve_path(path) def update_env(self, *, env: t.Dict[str, str]) -> None: From 2e6a5ed149f5035302f89b5fb5c2064eb746072b Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Thu, 1 Feb 2024 15:45:00 +0000 Subject: [PATCH 4/5] Do not resolve for non-existent files --- jupyter_client/provisioning/local_provisioner.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jupyter_client/provisioning/local_provisioner.py b/jupyter_client/provisioning/local_provisioner.py index a15c6f21..a835050d 100644 --- a/jupyter_client/provisioning/local_provisioner.py +++ b/jupyter_client/provisioning/local_provisioner.py @@ -208,6 +208,7 @@ async def pre_launch(self, **kwargs: Any) -> Dict[str, Any]: async def launch_kernel(self, cmd: List[str], **kwargs: Any) -> KernelConnectionInfo: """Launch a kernel with a command.""" + scrubbed_kwargs = LocalProvisioner._scrub_kwargs(kwargs) self.process = launch_kernel(cmd, **scrubbed_kwargs) pgid = None @@ -225,10 +226,10 @@ async def launch_kernel(self, cmd: List[str], **kwargs: Any) -> KernelConnection async def resolve_path(self, path_str: str) -> Optional[str]: """Resolve path to given file.""" path = pathlib.Path(path_str).expanduser() - if path.is_absolute(): + if not path.is_absolute() and self.cwd: + path = (pathlib.Path(self.cwd) / path).resolve() + if path.exists(): return path.as_posix() - if self.cwd: - return (pathlib.Path(self.cwd) / path).resolve().as_posix() return None @staticmethod From f4b82e546297e1aa16ef8a0ee757e71060cfd02d Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:43:26 +0000 Subject: [PATCH 5/5] Fix spurious `async` --- jupyter_client/provisioning/local_provisioner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyter_client/provisioning/local_provisioner.py b/jupyter_client/provisioning/local_provisioner.py index a835050d..bf3369c7 100644 --- a/jupyter_client/provisioning/local_provisioner.py +++ b/jupyter_client/provisioning/local_provisioner.py @@ -223,7 +223,7 @@ async def launch_kernel(self, cmd: List[str], **kwargs: Any) -> KernelConnection self.cwd = kwargs.get("cwd", pathlib.Path.cwd()) return self.connection_info - async def resolve_path(self, path_str: str) -> Optional[str]: + def resolve_path(self, path_str: str) -> Optional[str]: """Resolve path to given file.""" path = pathlib.Path(path_str).expanduser() if not path.is_absolute() and self.cwd: