Skip to content
Closed
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
4 changes: 4 additions & 0 deletions plugins/memory/hindsight/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,10 @@ def save_config(self, values, hermes_home):
pass
existing.update(values)
config_path.write_text(json.dumps(existing, indent=2))
try:
os.chmod(config_path, 0o600)
except OSError:
pass

def post_setup(self, hermes_home: str, config: dict) -> None:
"""Custom setup wizard — installs only the deps needed for the selected mode."""
Expand Down
5 changes: 5 additions & 0 deletions plugins/memory/honcho/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ def is_available(self) -> bool:
def save_config(self, values, hermes_home):
"""Write config to $HERMES_HOME/honcho.json (Honcho SDK native format)."""
import json
import os
from pathlib import Path
config_path = Path(hermes_home) / "honcho.json"
existing = {}
Expand All @@ -259,6 +260,10 @@ def save_config(self, values, hermes_home):
pass
existing.update(values)
config_path.write_text(json.dumps(existing, indent=2))
try:
os.chmod(config_path, 0o600)
except OSError:
pass

def get_config_schema(self):
return [
Expand Down
4 changes: 4 additions & 0 deletions plugins/memory/mem0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ def save_config(self, values, hermes_home):
pass
existing.update(values)
config_path.write_text(json.dumps(existing, indent=2))
try:
os.chmod(config_path, 0o600)
except OSError:
pass

def get_config_schema(self):
return [
Expand Down
4 changes: 4 additions & 0 deletions plugins/memory/supermemory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ def _save_supermemory_config(values: dict, hermes_home: str) -> None:
existing = {}
existing.update(values)
config_path.write_text(json.dumps(existing, indent=2, sort_keys=True) + "\n", encoding="utf-8")
try:
os.chmod(config_path, 0o600)
except OSError:
pass


def _detect_category(text: str) -> str:
Expand Down
13 changes: 13 additions & 0 deletions tests/plugins/memory/test_hindsight_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"""

import json
import os
import re
import stat
import sys
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
Expand Down Expand Up @@ -1549,3 +1551,14 @@ def test_local_embedded_shutdown_closes_inner_async_client_on_shared_loop(self,
assert embedded._client is None
assert provider._client is None


@pytest.mark.skipif(os.name == "nt", reason="POSIX mode bits not enforced on Windows")
def test_save_config_sets_owner_only_permissions(tmp_path):
"""hindsight/config.json must be written with 0o600 so API key is not world-readable."""
provider = HindsightMemoryProvider()
provider.save_config({"api_key": "hd-test-key"}, str(tmp_path))
config_file = tmp_path / "hindsight" / "config.json"
assert config_file.exists()
mode = stat.S_IMODE(config_file.stat().st_mode)
assert mode == 0o600, f"Expected 0o600 (owner-only), got {oct(mode)}"

14 changes: 14 additions & 0 deletions tests/plugins/memory/test_mem0_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"""

import json
import os
import stat

import pytest

from plugins.memory.mem0 import Mem0MemoryProvider
Expand Down Expand Up @@ -203,6 +206,17 @@ def test_prefetch_dict_response(self, monkeypatch):
# ---------------------------------------------------------------------------


@pytest.mark.skipif(os.name == "nt", reason="POSIX mode bits not enforced on Windows")
def test_save_config_sets_owner_only_permissions(tmp_path):
"""mem0.json must be written with 0o600 so API key is not world-readable."""
provider = Mem0MemoryProvider()
provider.save_config({"api_key": "m0-test-key"}, str(tmp_path))
config_file = tmp_path / "mem0.json"
assert config_file.exists()
mode = stat.S_IMODE(config_file.stat().st_mode)
assert mode == 0o600, f"Expected 0o600 (owner-only), got {oct(mode)}"


class TestMem0Defaults:
"""Ensure we don't break existing users' defaults."""

Expand Down
12 changes: 12 additions & 0 deletions tests/plugins/memory/test_supermemory_provider.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
import os
import stat
import threading

import pytest
Expand Down Expand Up @@ -409,3 +411,13 @@ def test_get_config_schema_minimal():
assert len(schema) == 1
assert schema[0]["key"] == "api_key"
assert schema[0]["secret"] is True


@pytest.mark.skipif(os.name == "nt", reason="POSIX mode bits not enforced on Windows")
def test_save_config_sets_owner_only_permissions(tmp_path):
"""supermemory.json must be written with 0o600 so API key is not world-readable."""
_save_supermemory_config({"api_key": "sm-test-key"}, str(tmp_path))
config_file = tmp_path / "supermemory.json"
assert config_file.exists()
mode = stat.S_IMODE(config_file.stat().st_mode)
assert mode == 0o600, f"Expected 0o600 (owner-only), got {oct(mode)}"
13 changes: 13 additions & 0 deletions tests/test_honcho_client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import json
import os
import stat
import tempfile
from pathlib import Path

import pytest

from plugins.memory.honcho.client import HonchoClientConfig
from plugins.memory.honcho import HonchoMemoryProvider


class TestHonchoClientConfigAutoEnable:
Expand Down Expand Up @@ -103,3 +105,14 @@ def test_falls_back_to_env_when_no_config_file(self, tmp_path, monkeypatch):

assert cfg.api_key == "fallback-key"
assert cfg.enabled is True # from_env() sets enabled=True


@pytest.mark.skipif(os.name == "nt", reason="POSIX mode bits not enforced on Windows")
def test_save_config_sets_owner_only_permissions(tmp_path):
"""honcho.json must be written with 0o600 so API key is not world-readable."""
provider = HonchoMemoryProvider()
provider.save_config({"api_key": "hc-test-key"}, str(tmp_path))
config_file = tmp_path / "honcho.json"
assert config_file.exists()
mode = stat.S_IMODE(config_file.stat().st_mode)
assert mode == 0o600, f"Expected 0o600 (owner-only), got {oct(mode)}"
Loading