Skip to content

Commit 34414a8

Browse files
authored
Disallow logging.conf from elsewhere than the app folder (#825)
1 parent b77f3f9 commit 34414a8

5 files changed

Lines changed: 103 additions & 46 deletions

File tree

.basedpyright/baseline.json

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42226,32 +42226,6 @@
4222642226
}
4222742227
}
4222842228
],
42229-
"./tests/unit/searchcommands/__init__.py": [
42230-
{
42231-
"code": "reportUnknownParameterType",
42232-
"range": {
42233-
"startColumn": 23,
42234-
"endColumn": 27,
42235-
"lineCount": 1
42236-
}
42237-
},
42238-
{
42239-
"code": "reportMissingParameterType",
42240-
"range": {
42241-
"startColumn": 23,
42242-
"endColumn": 27,
42243-
"lineCount": 1
42244-
}
42245-
},
42246-
{
42247-
"code": "reportUnknownArgumentType",
42248-
"range": {
42249-
"startColumn": 64,
42250-
"endColumn": 68,
42251-
"lineCount": 1
42252-
}
42253-
}
42254-
],
4225542229
"./tests/unit/searchcommands/chunked_data_stream.py": [
4225642230
{
4225742231
"code": "reportUnknownParameterType",
@@ -42751,14 +42725,6 @@
4275142725
}
4275242726
],
4275342727
"./tests/unit/searchcommands/test_builtin_options.py": [
42754-
{
42755-
"code": "reportUnknownVariableType",
42756-
"range": {
42757-
"startColumn": 57,
42758-
"endColumn": 75,
42759-
"lineCount": 1
42760-
}
42761-
},
4276242728
{
4276342729
"code": "reportDeprecated",
4276442730
"range": {
@@ -42963,14 +42929,6 @@
4296342929
"lineCount": 1
4296442930
}
4296542931
},
42966-
{
42967-
"code": "reportUnknownVariableType",
42968-
"range": {
42969-
"startColumn": 38,
42970-
"endColumn": 56,
42971-
"lineCount": 1
42972-
}
42973-
},
4297442932
{
4297542933
"code": "reportUnannotatedClassAttribute",
4297642934
"range": {

splunklib/searchcommands/environment.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ def configure_logging(logger_name, filename=None):
9797
global _current_logging_configuration_file
9898
filename = path.realpath(filename)
9999

100+
app_root_real = path.realpath(app_root)
101+
if path.commonpath([filename, app_root_real]) != app_root_real: # pyright: ignore[reportUnknownArgumentType]
102+
raise ValueError(
103+
f'Logging configuration file "{filename}" is outside the app directory'
104+
)
105+
100106
if filename != _current_logging_configuration_file:
101107
working_directory = getcwd()
102108
chdir(app_root)
@@ -114,9 +120,21 @@ def configure_logging(logger_name, filename=None):
114120

115121
_current_logging_configuration_file = None
116122

123+
124+
def _find_app_root(app_file: str, splunk_home: str) -> str:
125+
"""Return the app root directory for a search command script."""
126+
splunk_apps_dir = path.join(splunk_home, "etc", "apps")
127+
app_relpath = path.relpath(path.abspath(app_file), splunk_apps_dir)
128+
app_dir = app_relpath.split(path.sep, 1)[0]
129+
if app_dir == path.pardir: # app_file not in $SPLUNK_HOME/etc/apps
130+
return path.dirname(path.abspath(path.dirname(app_file)))
131+
132+
return path.join(splunk_apps_dir, app_dir)
133+
134+
117135
splunk_home = path.abspath(path.join(getcwd(), environ.get("SPLUNK_HOME", "")))
118136
app_file = getattr(sys.modules["__main__"], "__file__", sys.executable)
119-
app_root = path.dirname(path.abspath(path.dirname(app_file)))
137+
app_root = _find_app_root(app_file, splunk_home)
120138

121139
splunklib_logger, logging_configuration = configure_logging("splunklib")
122140

tests/unit/searchcommands/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
project_root = path.dirname(path.dirname(package_directory))
2323

2424

25-
def rebase_environment(name):
25+
def rebase_environment(name: str) -> None:
2626
environment.app_root = path.join(package_directory, "apps", name)
2727
logging.Logger.manager.loggerDict.clear()
2828
del logging.root.handlers[:]

tests/unit/searchcommands/test_builtin_options.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from splunklib.searchcommands import environment
2525
from splunklib.searchcommands.decorators import Configuration
2626
from splunklib.searchcommands.search_command import SearchCommand
27-
from tests.unit.searchcommands import package_directory, rebase_environment
27+
from tests.unit.searchcommands import rebase_environment
2828

2929

3030
# portable log level names
@@ -126,7 +126,7 @@ def test_logging_configuration(self):
126126

127127
try:
128128
command.logging_configuration = os.path.join(
129-
package_directory, "non-existent.logging.conf"
129+
os.path.dirname(os.path.realpath(__file__)), "non-existent.logging.conf"
130130
)
131131
except ValueError:
132132
pass
@@ -137,6 +137,39 @@ def test_logging_configuration(self):
137137
f"Expected ValueError, but logging_configuration={command.logging_configuration}"
138138
)
139139

140+
inside_app_root_logging_configuration = os.path.join(
141+
environment.app_root, "default", "logging.conf"
142+
)
143+
command.logging_configuration = inside_app_root_logging_configuration
144+
assert command.logging_configuration == inside_app_root_logging_configuration, (
145+
"logging_configuration should accept an absolute path inside the app directory"
146+
)
147+
148+
try:
149+
command.logging_configuration = os.path.realpath(__file__)
150+
except ValueError:
151+
pass
152+
except BaseException as e:
153+
pytest.fail(
154+
f"Expected ValueError for a path outside the app directory, but {type(e)} was raised"
155+
)
156+
else:
157+
pytest.fail(
158+
f"Expected ValueError for a path outside the app directory, but {command.logging_configuration=}"
159+
)
160+
161+
# logging_configuration raises a value error when a relative path traverses outside the app directory (RCE guard)
162+
try:
163+
command.logging_configuration = os.path.join("..", "..", "..", "__init__.py")
164+
except ValueError:
165+
pass
166+
except BaseException as e:
167+
pytest.fail(f"Expected ValueError, but {type(e)} was raised")
168+
else:
169+
pytest.fail(
170+
f"Expected ValueError, but logging_configuration={command.logging_configuration}"
171+
)
172+
140173
def test_logging_level(self):
141174
rebase_environment("app_without_logging_configuration")
142175
command = StubbedSearchCommand()
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright © 2011-2026 Splunk, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"): you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
import os
16+
17+
import pytest
18+
19+
from splunklib.searchcommands.environment import (
20+
_find_app_root, # pyright: ignore[reportPrivateUsage]
21+
)
22+
23+
_SPLUNK_HOME = os.path.join(os.sep, "opt", "splunk")
24+
_APPS_DIRECTORY = os.path.join(_SPLUNK_HOME, "etc", "apps")
25+
26+
27+
@pytest.mark.parametrize(
28+
("app_file_parts", "expected_app_root_parts"),
29+
[
30+
# A script located directly in the app's bin directory
31+
(("my_app", "bin", "command.py"), ("my_app",)),
32+
# A script located in a subdirectory of bin, including one that happens to be
33+
# named "bin" itself; the app root is still $SPLUNK_HOME/etc/apps/my_app
34+
(("my_app", "bin", "foo", "bin", "command.py"), ("my_app",)),
35+
],
36+
)
37+
def test_find_app_root(
38+
app_file_parts: tuple[str, ...], expected_app_root_parts: tuple[str, ...]
39+
) -> None:
40+
app_file = os.path.join(_APPS_DIRECTORY, *app_file_parts)
41+
expected_app_root = os.path.join(_APPS_DIRECTORY, *expected_app_root_parts)
42+
assert _find_app_root(app_file, _SPLUNK_HOME) == expected_app_root
43+
44+
45+
def test_find_app_root_falls_back_on_nonstandard_splunk_home() -> None:
46+
app_file = os.path.join("some", "other", "layout", "command.py")
47+
expected_app_root = os.path.dirname(os.path.abspath(os.path.dirname(app_file)))
48+
assert _find_app_root(app_file, _SPLUNK_HOME) == expected_app_root

0 commit comments

Comments
 (0)