Repository: mlflow/mlflow

Issue:
[BUG] Security Vulnerability

Discussion:
github-actions[bot]: <!-- documentation preview -->


Documentation preview for a5cc0dc94a1b259eaf96fcbf7fb9d858df837a0c will be available [here](https://output.circle-artifacts.com/output/job/3fb50dcc-1f6c-4037-bcbf-12659e24e452/artifacts/0/docs/build/html/index.html) when [this CircleCI job](https://circleci.com/gh/mlflow/mlflow/82388) completes successfully.

<details>
<summary>More info</summary>

- Ignore this comment if this PR does not change the documentation.
- It takes a few minutes for the preview to be available.
- The preview is updated when a new commit is pushed to this PR.
- This comment was created by https://github.com/mlflow/mlflow/actions/runs/7127115627.

</details>


Patch:
diff --git a/mlflow/recipes/cards/__init__.py b/mlflow/recipes/cards/__init__.py
index 24361b8ac..dafdaa89f 100644
--- a/mlflow/recipes/cards/__init__.py
+++ b/mlflow/recipes/cards/__init__.py
@@ -120,9 +120,10 @@ class CardTab:
 
         :return: a HTML string
         """
-        import jinja2
+        from jinja2 import BaseLoader
+        from jinja2.sandbox import SandboxedEnvironment
 
-        j2_env = jinja2.Environment(loader=jinja2.BaseLoader()).from_string(self.template)
+        j2_env = SandboxedEnvironment(loader=BaseLoader()).from_string(self.template)
         return j2_env.render({**self._context})
 
 
diff --git a/mlflow/utils/uri.py b/mlflow/utils/uri.py
index bc2171830..331648b8e 100644
--- a/mlflow/utils/uri.py
+++ b/mlflow/utils/uri.py
@@ -41,6 +41,10 @@ def is_local_uri(uri, is_tracking_or_registry_uri=True):
         return False
 
     parsed_uri = urllib.parse.urlparse(uri)
+    scheme = parsed_uri.scheme
+    if scheme == "":
+        return True
+
     if parsed_uri.hostname and not (
         parsed_uri.hostname == "."
         or parsed_uri.hostname.startswith("localhost")
@@ -48,8 +52,7 @@ def is_local_uri(uri, is_tracking_or_registry_uri=True):
     ):
         return False
 
-    scheme = parsed_uri.scheme
-    if scheme == "" or scheme == "file":
+    if scheme == "file":
         return True
 
     if is_windows() and len(scheme) == 1 and scheme.lower() == pathlib.Path(uri).drive.lower()[0]:
@@ -427,6 +430,10 @@ def validate_path_is_safe(path):
     """
     from mlflow.utils.file_utils import local_file_uri_to_path
 
+    exc = MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)
+    if any((s in path) for s in ("#", "%23")):
+        raise exc
+
     if is_file_uri(path):
         path = local_file_uri_to_path(path)
     if (
@@ -436,4 +443,4 @@ def validate_path_is_safe(path):
         or pathlib.PurePosixPath(path).is_absolute()
         or (is_windows() and len(path) >= 2 and path[1] == ":")
     ):
-        raise MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)
+        raise exc


Test patch:
diff --git a/tests/tracking/test_rest_tracking.py b/tests/tracking/test_rest_tracking.py
index 3adeea3a8..d56eecd77 100644
--- a/tests/tracking/test_rest_tracking.py
+++ b/tests/tracking/test_rest_tracking.py
@@ -548,6 +548,8 @@ def test_validate_path_is_safe_windows_good(path):
         "./../path",
         "path/../to/file",
         "path/../../to/file",
+        "file://a#/..//tmp",
+        "file://a%23/..//tmp/",
         "/etc/passwd",
         "/etc/passwd%00.jpg",
         "/etc/passwd%00.html",
diff --git a/tests/utils/test_uri.py b/tests/utils/test_uri.py
index 758642e9b..b776795c6 100644
--- a/tests/utils/test_uri.py
+++ b/tests/utils/test_uri.py
@@ -99,6 +99,8 @@ def test_is_local_uri():
     assert is_local_uri("file://localhost:5000/mlruns")
     assert is_local_uri("file://127.0.0.1/mlruns")
     assert is_local_uri("file://127.0.0.1:5000/mlruns")
+    assert is_local_uri("//proc/self/root")
+    assert is_local_uri("/proc/self/root")
 
     assert not is_local_uri("file://myhostname/path/to/file")
     assert not is_local_uri("https://whatever")