Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion splunklib/searchcommands/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

@mateusz834 mateusz834 Jul 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If filename is relative here, what will happen? Does realpath always return the absolute path?

Docs does not seem to say anything about that:

def realpath(filename, *, strict=False):
    """Return the canonical path of the specified filename, eliminating any
symbolic links encountered in the path."""

It only follows symbolic links.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raise ValueError(
f'Logging configuration file "{filename}" is outside the app directory'
)

if filename != _current_logging_configuration_file:
working_directory = getcwd()
chdir(app_root)
Expand All @@ -114,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")

Expand Down
33 changes: 33 additions & 0 deletions tests/unit/searchcommands/test_builtin_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,39 @@ def test_logging_configuration(self):
f"Expected ValueError, but logging_configuration={command.logging_configuration}"
)

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.realpath(__file__)
except ValueError:
pass
except BaseException as e:
pytest.fail(
f"Expected ValueError for a path outside the app directory, but {type(e)} was raised"
)
else:
pytest.fail(
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)
try:
command.logging_configuration = os.path.join("..", "..", "..", "__init__.py")

@mateusz834 mateusz834 Jul 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add an additional test with an absolute path, one that points to an app dir (success), and the other one that points to a different place (failure).

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()
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/searchcommands/test_environment.py
Original file line number Diff line number Diff line change
@@ -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))
Loading