From b0e25c9cb29517a4cd829a50182289f1e7c261ff Mon Sep 17 00:00:00 2001 From: Hao Zhe Date: Wed, 20 May 2026 13:25:29 +0800 Subject: [PATCH] fix(memory): restrict OpenViking setup file permissions --- plugins/memory/openviking/__init__.py | 10 ++++++++ .../memory/test_openviking_provider.py | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/plugins/memory/openviking/__init__.py b/plugins/memory/openviking/__init__.py index 07df0e7d888..92775810e87 100644 --- a/plugins/memory/openviking/__init__.py +++ b/plugins/memory/openviking/__init__.py @@ -30,6 +30,7 @@ import json import logging import mimetypes import os +import stat import tempfile import threading import uuid @@ -511,6 +512,13 @@ def _env_writes_from_connection_values(values: dict) -> dict: return writes +def _restrict_secret_file_permissions(path: Path) -> None: + try: + path.chmod(stat.S_IRUSR | stat.S_IWUSR) + except OSError: + pass + + def _write_env_vars(env_path: Path, env_writes: dict, remove_keys: tuple[str, ...] = ()) -> None: env_path.parent.mkdir(parents=True, exist_ok=True) remove_set = set(remove_keys) - set(env_writes) @@ -530,6 +538,7 @@ def _write_env_vars(env_path: Path, env_writes: dict, remove_keys: tuple[str, .. if key not in updated_keys: new_lines.append(f"{key}={val}") env_path.write_text("\n".join(new_lines) + ("\n" if new_lines else ""), encoding="utf-8") + _restrict_secret_file_permissions(env_path) def _remember_ovcli_path(provider_config: dict, ovcli_path: Path) -> None: @@ -560,6 +569,7 @@ def _ovcli_data_from_connection_values(values: dict) -> dict: def _write_ovcli_config(path: Path, values: dict) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(_ovcli_data_from_connection_values(values), indent=2) + "\n", encoding="utf-8") + _restrict_secret_file_permissions(path) # --------------------------------------------------------------------------- diff --git a/tests/plugins/memory/test_openviking_provider.py b/tests/plugins/memory/test_openviking_provider.py index 754f7ff617c..ce6f7515507 100644 --- a/tests/plugins/memory/test_openviking_provider.py +++ b/tests/plugins/memory/test_openviking_provider.py @@ -1,4 +1,6 @@ import json +import os +import stat import zipfile from types import SimpleNamespace from unittest.mock import MagicMock @@ -20,6 +22,27 @@ def _clear_openviking_env(monkeypatch): monkeypatch.delenv(key, raising=False) +@pytest.mark.skipif(os.name == "nt", reason="POSIX file modes") +def test_openviking_env_writer_restricts_file_permissions(tmp_path): + env_path = tmp_path / ".env" + + openviking_module._write_env_vars(env_path, {"OPENVIKING_API_KEY": "secret"}) + + assert stat.S_IMODE(env_path.stat().st_mode) == 0o600 + + +@pytest.mark.skipif(os.name == "nt", reason="POSIX file modes") +def test_ovcli_config_writer_restricts_file_permissions(tmp_path): + config_path = tmp_path / "ovcli.conf" + + openviking_module._write_ovcli_config( + config_path, + {"endpoint": "http://remote.example", "api_key": "secret"}, + ) + + assert stat.S_IMODE(config_path.stat().st_mode) == 0o600 + + def test_linked_ovcli_config_is_read_at_runtime(tmp_path, monkeypatch): _clear_openviking_env(monkeypatch) ovcli_path = tmp_path / "ovcli.conf"