From 73eac07f4b1547e4652dcb6b38b12ad85116040d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20J=C4=99drecki?= Date: Tue, 7 Jul 2026 18:49:36 +0200 Subject: [PATCH 1/3] Disallow logging.conf from elsewhere than the app folder --- splunklib/searchcommands/environment.py | 6 +++++ .../searchcommands/test_builtin_options.py | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/splunklib/searchcommands/environment.py b/splunklib/searchcommands/environment.py index 83ee939f4..5dec95e02 100644 --- a/splunklib/searchcommands/environment.py +++ b/splunklib/searchcommands/environment.py @@ -97,6 +97,12 @@ def configure_logging(logger_name, filename=None): global _current_logging_configuration_file filename = path.realpath(filename) + app_root_real = path.realpath(app_root) + if path.commonpath([filename, app_root_real]) != app_root_real: # pyright: ignore[reportUnknownArgumentType] + raise ValueError( + f'Logging configuration file "{filename}" is outside the app directory' + ) + if filename != _current_logging_configuration_file: working_directory = getcwd() chdir(app_root) diff --git a/tests/unit/searchcommands/test_builtin_options.py b/tests/unit/searchcommands/test_builtin_options.py index 911321251..dd18c0589 100644 --- a/tests/unit/searchcommands/test_builtin_options.py +++ b/tests/unit/searchcommands/test_builtin_options.py @@ -137,6 +137,30 @@ def test_logging_configuration(self): f"Expected ValueError, but logging_configuration={command.logging_configuration}" ) + # logging_configuration raises a value error for an existing absolute path outside the app directory (RCE guard) + try: + command.logging_configuration = os.path.join(package_directory, "__init__.py") + except ValueError: + pass + except BaseException as e: + pytest.fail(f"Expected ValueError, but {type(e)} was raised") + else: + pytest.fail( + f"Expected ValueError, but logging_configuration={command.logging_configuration}" + ) + + # logging_configuration raises a value error when a relative path traverses outside the app directory (RCE guard) + try: + command.logging_configuration = os.path.join("..", "..", "..", "__init__.py") + except ValueError: + pass + except BaseException as e: + pytest.fail(f"Expected ValueError, but {type(e)} was raised") + else: + pytest.fail( + f"Expected ValueError, but logging_configuration={command.logging_configuration}" + ) + def test_logging_level(self): rebase_environment("app_without_logging_configuration") command = StubbedSearchCommand() From 63f0de16625b9eb621b2c180d471d98bfdb3df41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20J=C4=99drecki?= Date: Mon, 17 Aug 2026 17:50:23 +0200 Subject: [PATCH 2/3] Try addressing PR comments --- splunklib/searchcommands/environment.py | 15 ++++++++++++++- .../unit/searchcommands/test_builtin_options.py | 17 +++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/splunklib/searchcommands/environment.py b/splunklib/searchcommands/environment.py index 5dec95e02..474dacfeb 100644 --- a/splunklib/searchcommands/environment.py +++ b/splunklib/searchcommands/environment.py @@ -120,9 +120,22 @@ def configure_logging(logger_name, filename=None): _current_logging_configuration_file = None + +def _find_app_root(app_file: str) -> str: + """Return the app root directory for a search command script.""" + directory: str = path.abspath(path.dirname(app_file)) + while True: + parent, name = path.split(directory) + if name == "bin": + return parent + if parent == directory: + return path.dirname(path.abspath(path.dirname(app_file))) + directory = parent + + splunk_home = path.abspath(path.join(getcwd(), environ.get("SPLUNK_HOME", ""))) app_file = getattr(sys.modules["__main__"], "__file__", sys.executable) -app_root = path.dirname(path.abspath(path.dirname(app_file))) +app_root = _find_app_root(app_file) splunklib_logger, logging_configuration = configure_logging("splunklib") diff --git a/tests/unit/searchcommands/test_builtin_options.py b/tests/unit/searchcommands/test_builtin_options.py index dd18c0589..f2827c5ef 100644 --- a/tests/unit/searchcommands/test_builtin_options.py +++ b/tests/unit/searchcommands/test_builtin_options.py @@ -137,16 +137,25 @@ def test_logging_configuration(self): f"Expected ValueError, but logging_configuration={command.logging_configuration}" ) - # logging_configuration raises a value error for an existing absolute path outside the app directory (RCE guard) + inside_app_root_logging_configuration = os.path.join( + environment.app_root, "default", "logging.conf" + ) + command.logging_configuration = inside_app_root_logging_configuration + assert command.logging_configuration == inside_app_root_logging_configuration, ( + "logging_configuration should accept an absolute path inside the app directory" + ) + try: - command.logging_configuration = os.path.join(package_directory, "__init__.py") + command.logging_configuration = os.path.realpath(__file__) except ValueError: pass except BaseException as e: - pytest.fail(f"Expected ValueError, but {type(e)} was raised") + pytest.fail( + f"Expected ValueError for a path outside the app directory, but {type(e)} was raised" + ) else: pytest.fail( - f"Expected ValueError, but logging_configuration={command.logging_configuration}" + f"Expected ValueError for a path outside the app directory, but {command.logging_configuration=}" ) # logging_configuration raises a value error when a relative path traverses outside the app directory (RCE guard) From ea90efabb5af96d4cbfa794a53ddddf4edc6a9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20J=C4=99drecki?= Date: Tue, 18 Aug 2026 15:49:18 +0200 Subject: [PATCH 3/3] Add a test for _find_app_root() --- tests/unit/searchcommands/test_environment.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/unit/searchcommands/test_environment.py diff --git a/tests/unit/searchcommands/test_environment.py b/tests/unit/searchcommands/test_environment.py new file mode 100644 index 000000000..e2eb48ff5 --- /dev/null +++ b/tests/unit/searchcommands/test_environment.py @@ -0,0 +1,37 @@ +# Copyright © 2011-2026 Splunk, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"): you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import os + +import pytest + +from splunklib.searchcommands.environment import ( + _find_app_root, # pyright: ignore[reportPrivateUsage] +) + + +@pytest.mark.parametrize( + ("app_file_parts", "expected_app_root_parts"), + [ + (("apps", "my_app", "bin", "command.py"), ("apps", "my_app")), + (("apps", "my_app", "bin", "linux_x86_64", "command.py"), ("apps", "my_app")), + (("apps", "my_app", "bin", "foo", "bar", "command.py"), ("apps", "my_app")), + (("some", "other", "layout", "command.py"), ("some", "other")), + ], +) +def test_find_app_root( + app_file_parts: tuple[str, ...], expected_app_root_parts: tuple[str, ...] +) -> None: + app_file = os.path.join(*app_file_parts) + assert _find_app_root(app_file) == os.path.abspath(os.path.join(*expected_app_root_parts))