From 7c572fc670c21ce3e061afb3acbada369b7c9094 Mon Sep 17 00:00:00 2001 From: dfgvaetyj3456356-hash Date: Thu, 28 May 2026 02:56:28 -0500 Subject: [PATCH 1/2] security: fix path traversal in Space.from_yaml() Adds validation to reject path traversal sequences (..) in the yml_path parameter before opening the file. This prevents arbitrary file reads when user-controlled input is passed to Space.from_yaml(). --- skopt/space/space.py | 6 ++++++ skopt/tests/test_space.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/skopt/space/space.py b/skopt/space/space.py index 329b0c18c..e8f59a23d 100644 --- a/skopt/space/space.py +++ b/skopt/space/space.py @@ -1,4 +1,5 @@ import numbers +import os import numpy as np import yaml @@ -837,6 +838,11 @@ def from_yaml(cls, yml_path, namespace=None): Instantiated Space object """ + yml_path = str(yml_path) + if ".." in yml_path: + raise ValueError( + f"Path traversal is not allowed in yml_path. Received: {yml_path!r}" + ) with open(yml_path, 'rb') as f: config = yaml.safe_load(f) diff --git a/skopt/tests/test_space.py b/skopt/tests/test_space.py index bd93e4e1a..f826fe509 100644 --- a/skopt/tests/test_space.py +++ b/skopt/tests/test_space.py @@ -749,6 +749,11 @@ def test_space_from_yaml(): tmp.close() os.unlink(tmp.name) +def test_space_from_yaml_prevents_path_traversal(): + with pytest.raises(ValueError, match="Path traversal"): + Space.from_yaml("../../../etc/passwd") + + @pytest.mark.parametrize("name", [1, 1., True]) def test_dimension_with_invalid_names(name): with pytest.raises(ValueError) as exc: From c6a117d03a4ed95a78cc2857824db4e40c3c04cb Mon Sep 17 00:00:00 2001 From: Security Fix Date: Fri, 29 May 2026 04:28:08 -0500 Subject: [PATCH 2/2] security: add trusted_source warning for skopt.load() pickle deserialization - joblib.load() uses pickle internally, enabling arbitrary code execution - Add trusted_source=False parameter with RuntimeWarning - Users must opt-in with trusted_source=True for untrusted files --- skopt/utils.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/skopt/utils.py b/skopt/utils.py index a27e62c90..2352a4baf 100644 --- a/skopt/utils.py +++ b/skopt/utils.py @@ -2,6 +2,7 @@ from functools import wraps from sklearn.utils import check_random_state import numpy as np +import warnings from scipy.optimize import OptimizeResult from scipy.optimize import minimize as sp_minimize from sklearn.base import is_regressor @@ -148,7 +149,7 @@ def dump(res, filename, store_objective=True, **kwargs): dump_(res, filename, **kwargs) -def load(filename, **kwargs): +def load(filename, trusted_source=False, **kwargs): """ Reconstruct a skopt optimization result from a file persisted with skopt.dump. @@ -163,6 +164,11 @@ def load(filename, **kwargs): filename : string or `pathlib.Path` The path of the file from which to load the optimization result. + trusted_source : bool, default=False + If False, emit a warning about the security risks of loading + untrusted pickle/joblib files, which can lead to arbitrary code + execution. + **kwargs : other keyword arguments All other keyword arguments will be passed to `joblib.load`. @@ -171,6 +177,13 @@ def load(filename, **kwargs): res : `OptimizeResult`, scipy object Reconstructed OptimizeResult instance. """ + if not trusted_source: + warnings.warn( + "Loading an optimizer from an untrusted source can lead to " + "arbitrary code execution. Only load files from trusted sources. " + "Set trusted_source=True to suppress this warning.", + RuntimeWarning + ) return load_(filename, **kwargs)