fix(honcho): harden self-hosted setup paths
Self-hosted Honcho setup had four sharp edges: - local/cloud URLs ending in /vN double-prefixed by the SDK (/v3/v3/... 404) - authenticated local servers had no setup prompt for a JWT/bearer token - profile-derived host keys could be dot-containing workspace IDs Honcho rejects - memory-provider config files with API keys written world-readable per umask This keeps existing behavior but makes those paths safer: - strip a trailing /vN version segment from any configured baseUrl before SDK init (the SDK's route builders always prepend their own version prefix); auth-skipping stays loopback-only - add an optional local JWT/bearer prompt in honcho setup, stored under hosts.<host>.apiKey - derive new profile host keys with underscores, still reading legacy hermes.<profile> blocks - write memory-provider config files atomically with 0600 via a shared utils.atomic_json_write(mode=) arg (honcho/hindsight/mem0/supermemory) - skip honcho.json parsing in gateway cache-busting unless Honcho is the active memory provider; memoize by honcho.json mtime when active - bust the gateway agent cache on memory.provider change - add a hermes memory setup <provider> one-liner so fresh installs can configure a named provider without the picker (the per-provider hermes <provider> subcommand only registers once that provider is active) Closes #20688, #29885, #26459, #30246, #33382, #32244. Co-authored-by: BROCCOLO1D
This commit is contained in:
committed by
kshitij
co-authored by
BROCCOLO1D
parent
aa32edcac5
commit
827ce602db
@@ -6,7 +6,9 @@ turn counting, tags), and schema completeness.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
@@ -1570,3 +1572,13 @@ class TestShutdown:
|
||||
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)}"
|
||||
|
||||
@@ -4,6 +4,10 @@ Salvaged from PRs #5301 (qaqcvc) and #5117 (vvvanguards).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
|
||||
import pytest
|
||||
|
||||
from plugins.memory.mem0 import Mem0MemoryProvider
|
||||
|
||||
@@ -202,6 +206,17 @@ class TestMem0ResponseUnwrapping:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@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."""
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
@@ -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)}"
|
||||
|
||||
Reference in New Issue
Block a user