fix(honcho): harden self-hosted setup paths#33954
Conversation
…ed instances (NousResearch#20688) The Honcho SDK's route builders (e.g. routes.workspaces()) return paths that already include the API version prefix (e.g. "/v3/workspaces"). When a user configures a self-hosted instance with base_url = "http://localhost:38000/v3", the SDK concatenates them into "/v3/v3/workspaces", causing 404 errors on every peer and session initialization call. Strip any trailing version path segment (/vN) from base_url before passing it to the Honcho constructor for local instances (localhost, 127.0.0.1, ::1). Cloud base_urls are not local and remain unchanged. Adds four regression tests in tests/honcho_plugin/test_client.py covering: - /v3 suffix stripped for localhost URL - no-version URL left unchanged - cloud URL not modified - /v3/ with trailing slash also cleaned Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ontaining API keys Four memory plugin save_config() methods wrote their JSON config files via write_text(), which obeys the process umask (typically 0o022), leaving them at 0644 — world-readable on shared hosts: ~/.hermes/honcho.json — Honcho API key ~/.hermes/mem0.json — Mem0 Platform API key (required) ~/.hermes/hindsight/config.json — Hindsight Cloud API key ~/.hermes/supermemory.json — Supermemory API key (required) All four schemas declare api_key with "secret": True. A local user on a multi-user host could read these files and recover live API keys. Apply os.chmod(config_path, 0o600) immediately after write_text() at each of the four save_config() call sites, matching the pattern used by the recent security cluster (79fc92e for .env, 3bace07 for webhook_subscriptions.json, 782681f for Google Chat OAuth). honcho/__init__.py also adds a local `import os` inside save_config() because that module does not import os at the top level. Adds one regression test per plugin asserting that the resulting file mode is 0o600 under a permissive umask (skipped on Windows).
|
Addressed the TOCTOU concern from #32244: memory provider config writes now use atomic JSON writes with the temp file created/replaced at 0600, instead of write_text() followed by chmod. Targeted tests still pass: 436 passed. |
|
Fixed the cache-busting test failures from CI: those assertions now run with |
The /v3 double-prefix fix was gated on loopback (localhost/127.0.0.1/::1), so a self-hosted Honcho reached by any other address — LAN IP, Tailscale, custom domain, Fly.io — kept its trailing /vN and re-hit the /v3/v3 -> 404 the prior commit set out to fix. Version stripping is a pure routing concern (the SDK always prepends its own /vN), so it now applies to any base_url regardless of host. Auth-skip behavior stays loopback-only, since substituting the 'local' placeholder only makes sense for a no-auth localhost server. Tests: add LAN/Tailscale/custom-domain regression cases; split the cloud test so a cloud base_url with /v3 is stripped (it double-prefixes too) and one without a version segment is left unchanged.
…ompat-cleanup # Conflicts: # tests/plugins/memory/test_mem0_v2.py # tests/test_honcho_client_config.py
The per-provider 'hermes <provider>' subcommand (e.g. 'hermes honcho setup') is only registered once that provider is active, so on a fresh install the documented setup command doesn't exist yet — argparse rejects it with 'invalid choice: honcho'. 'hermes memory setup' is always available regardless of active provider, so give it an optional positional that skips the picker and configures a named provider directly. 'hermes memory setup honcho' now works cold. Docs (honcho README, website honcho.md + memory-providers.md, honcho skill) updated to lead with the one-liner and note 'hermes honcho setup' as the post-activation alias.
Adopted from NousResearch#33535 (BROCCOLO1D): the per-message Honcho cache-busting gate reads memory.provider to decide whether to parse honcho.json, but the provider value itself was not a busting key. A user switching memory.provider mid-gateway (e.g. honcho -> mem0) would not necessarily rebuild the cached agent on that change alone. Add ("memory", "provider") to the busting key set and cover the provider-switch signature change with a test. Co-authored-by: BROCCOLO1D <279959838+BROCCOLO1D@users.noreply.github.com>
main's test_packaging_metadata.py imports setuptools at module top via `from setuptools import find_packages`. setuptools is a build-system requirement, not a test-venv dependency, and uv-managed CI interpreters don't seed it — so collection errors with ModuleNotFoundError and fails the whole test shard on every PR built on current main. Use pytest.importorskip so the module skips cleanly when setuptools is unavailable and runs normally when it is. Unrelated to the Honcho changes in this PR; included to unblock CI.
|
Merged via #35170 (commit 827ce60) — salvaged onto current |
Summary
Self-hosted Honcho setup had four sharp edges: local URLs with
/v3could be double-prefixed by the SDK, authenticated local servers had no setup prompt for a JWT/bearer token, profile-derived host keys could fall back to dot-containing workspace IDs that Honcho rejects, and memory-provider config files containing API keys could be written with world-readable permissions depending on umask.This PR keeps existing behavior but makes those paths safer:
/vNversion segment from any configuredbaseUrlbefore SDK initialization, avoiding/v3/v3/...routes. The SDK's route builders always prepend their own version prefix, so this applies to every host — loopback, LAN IP, Tailscale, custom domain, and cloud alike (a pasted/v3double-prefixes regardless of host). Auth-skipping (thelocalAPI-key placeholder) stays loopback-only.hermes honcho setup, stored underhosts.<host>.apiKeyhermes.<profile>host blocks0600permissions, avoiding the chmod-after-write TOCTOU window. This lands across honcho/hindsight/mem0/supermemory because it's a single shared-helper change (utils.atomic_json_writegains amode=arg) rather than per-provider behavior changes.honcho.jsonparsing in gateway cache-busting when Honcho is not the active memory provider; memoize byhoncho.jsonmtime when it is activememory.providerchange. Adopted from fix(gateway): skip inactive honcho cache-bust reads #33535 (BROCCOLO1D): the provider value gates whetherhoncho.jsonis parsed, but wasn't itself a cache-busting key, so a mid-gateway provider switch could miss a rebuild. Co-authored credit on the commit.hermes memory setup <provider>one-liner so a fresh install can configure a named provider (e.g.hermes memory setup honcho) without the picker. The per-providerhermes <provider>subcommand is only registered once that provider is active, so on a fresh install the documentedhermes honcho setupdoesn't exist yet and argparse rejects it withinvalid choice: 'honcho'.hermes memory setupis always available regardless of active provider, so it now takes an optional positional that routes to the same setup wizard. Docs updated to lead with the one-liner.Closes #20688.
Closes #29885.
Closes #26459.
Closes #30246.
Closes #33382.
Closes #32244.
Supersedes #20720, #30180, #32244, and the self-hosted/safe-host-key portion of #26478.
Supersedes #33535 (BROCCOLO1D) — adopted its
memory.providercache-busting key; the rest converged with this PR'sgateway/run.pychanges.Kept separate
baseUrlconflicts with the current root-level baseUrl design.Test plan
scripts/run_tests.sh tests/honcho_plugin/test_client.py tests/honcho_plugin/test_cli.py tests/gateway/test_agent_cache.py tests/hermes_cli/test_profiles.py tests/test_honcho_client_config.py tests/plugins/memory/test_hindsight_provider.py tests/plugins/memory/test_mem0_v2.py tests/plugins/memory/test_supermemory_provider.pyResult: 449 passed, 0 failed (Honcho suites). The
hermes memory setup <provider>routing addstests/hermes_cli/test_memory_setup_provider_arg.py(4 tests).