Repository navigation
fix(memory): reject a Honcho base_url that can never resolve - #5607
WillemJiang merged 2 commits into
Conversation
HonchoConfig only did str(...).rstrip('/') on backend_config.base_url, so a
scheme-less or non-http value was accepted at Gateway startup and then made
every memory read return empty and every write drop silently, with only a
per-operation WARNING. httpx turns 'localhost:8000' into the bogus scheme
'localhost:'. The same check already exists in the mem0 and OpenViking
backends, and honcho/README.md plus HonchoMemoryManager.from_config both
promise a bad URL fails fast at startup.
willem-bd
left a comment
There was a problem hiding this comment.
Reviewed at cd76fc5. Ran pytest tests/test_honcho_memory_backend.py at this SHA: 115 passed; ruff check and ruff format --check clean on both files. The guard itself is correct and matches the mem0/OpenViking phrasing and placement conventions, and the new parametrized tests cover the shapes claimed in the description. Three findings, one substantive: the PR fixes the scheme-less bypass of the api_key-over-plain-HTTP guard but leaves the case-variant bypass open, even though urlsplit here already computes the scheme it would need.
- drive the api_key-over-plain-HTTP check off urlsplit(...).scheme: urlsplit
lowercases the scheme while startswith("http://") does not, so
"HTTP://internal:8000" reached httpx over plain HTTP with the key set and
allow_insecure_http still False
- read hostname instead of netloc in the base_url guard, so an authority
without a host ("http://", "http://:8000") is rejected at config time like
the scheme-less shape this PR already removes
- test direct HonchoConfig(base_url=...) construction, the path the guard's
placement in __post_init__ exists to protect
willem-bd
left a comment
There was a problem hiding this comment.
Reviewed the follow-up at 68d232d: all three findings from the cd76fc5 review are fixed.
- Case-variant insecure-key bypass —
from_backend_confignow readsurlsplit(base_url).scheme == "http"; theHTTP://internal:8000+ api_key repro now raises theallow_insecure_httperror, pinned bytest_http_scheme_case_does_not_bypass_the_insecure_key_guard. - Host-less authorities —
not parsed_base_url.hostnamerejects bothhttp://andhttp://:8000in one predicate. - Direct-construction coverage —
test_direct_construction_hits_the_same_guardbuildsHonchoConfig(base_url=...)directly overlocalhost:8000,http://,http://:8000andftp://localhost:8000.
Verified independently at this head: pytest tests/test_honcho_memory_backend.py → 124 passed (was 115 at cd76fc5), ruff check and ruff format --check clean on both files, the default http://localhost:8000 still constructs, and a repo-wide grep at this SHA shows the test file is the only direct HonchoConfig( construction site, so the stricter guard breaks no other caller. Nothing new found; ready to merge from my side.
|
Thanks for verifying independently at this head — nothing further from my side. At the time of writing this the PR was kept a draft on purpose: the AI-assistance box in the body is unticked until the account owner has read the diff line by line, and #5588 was occupying this account's ready-PR slot here. Superseded 2026-09-24: the account owner has since read the diff and ticked the box, and @WillemJiang marked this ready for review at 08:15:22Z, so it is in the review queue now. Marking it ready from the author side was refused by GitHub ( |
…ce#5607) * fix(memory): reject a Honcho base_url that can never resolve HonchoConfig only did str(...).rstrip('/') on backend_config.base_url, so a scheme-less or non-http value was accepted at Gateway startup and then made every memory read return empty and every write drop silently, with only a per-operation WARNING. httpx turns 'localhost:8000' into the bogus scheme 'localhost:'. The same check already exists in the mem0 and OpenViking backends, and honcho/README.md plus HonchoMemoryManager.from_config both promise a bad URL fails fast at startup. * fix(memory): cover the scheme-case and host-less variants of the guard - drive the api_key-over-plain-HTTP check off urlsplit(...).scheme: urlsplit lowercases the scheme while startswith("http://") does not, so "HTTP://internal:8000" reached httpx over plain HTTP with the key set and allow_insecure_http still False - read hostname instead of netloc in the base_url guard, so an authority without a host ("http://", "http://:8000") is rejected at config time like the scheme-less shape this PR already removes - test direct HonchoConfig(base_url=...) construction, the path the guard's placement in __post_init__ exists to protect
Fixes #5606
Why
backend_configis operator-supplied YAML, andbase_urlis the one Honcho knob that is read without any check:config.py:105isstr(cfg.get("base_url", ...)).rstrip("/"). The most likely wrong spelling is also the natural one when pointing DeerFlow at a compose service or an internal host (base_url: honcho:8000), and httpx does not treat that as relative — it parses the host as the scheme, so every request fails later:The Gateway starts green and Honcho memory is permanently dead, with one WARNING per operation as the only trace. Two aggravating details measured on
main:api_key-over-plain-HTTP guard atconfig.py:108isbase_url.startswith("http://"), so a scheme-less URL skips it as well:{'base_url': 'localhost:8000', 'api_key': 'sk-test'}is accepted whileallow_insecure_httpstaysfalse.backends/honcho/README.md:36— "Config errors (bad URL, insecure key combination) fail fast at Gateway startup" — and theHonchoMemoryManager.from_configdocstring athoncho_manager.py:118, which is that startup path.This continues the posture the file was already hardened to, the same way #5555 did: #4783 put parse-time
ValueErrorguards in__post_init__for the scalar knobs, #5555 added_mapping/_numberfor the nested and numeric ones, and #4782 / #4821 are the same "silently accepted now, wrong later" class in the sibling backends.base_urlwas the remaining unguarded field, and mem0 (config.py:134-136) plus OpenViking (config.py:112-114) already validate it.What changed
HonchoConfig.__post_init__now rejects abase_urlwhose scheme is nothttp/https, or whose netloc is empty:Honcho backend: base_url must be an absolute http:// or https:// URL. It goes in__post_init__rather than only infrom_backend_configso direct construction is covered too — that is where this class's other guards already live, and it keeps the check next to the field it protects.urlsplitacceptshttp/httpswith any host, sohttp://host.docker.internal:8000(the documented deployment form),https://api.honcho.dev, an internalhttp://internal:8000, and portless/IPv6 forms all pass; the trailing-slash strip stays where it is; and for a realhttp://URL the existing insecure-key check still runs first, unchanged.TestHonchoConfig, in the parametrized style of the neighbouringtest_rejects_invalid_timeouts:test_malformed_base_url_rejected_as_config_error(7 cases),test_absolute_base_urls_still_accepted(4 cases),test_manager_from_config_fails_fast_on_malformed_base_url.Surface area
HonchoConfigconstruction only —backends/honcho/config.py(+9) and its test file (+13). No new dependency (urlsplitis stdlib and is already imported bymem0/config.pyin this same package tree), no schema, prompt, I/O or network change, and no other backend, caller or route is touched. For a configuration that loads and works today, nothing behaves differently; the only outcomes that change belong to URLs that could never have resolved.Bug fix verification
Reproduction is
tests/test_honcho_memory_backend.py::TestHonchoConfig::test_malformed_base_url_rejected_as_config_errorplus::test_manager_from_config_fails_fast_on_malformed_base_url. Both were measured on this branch's base,mainat0b7cef2e, by reverting onlyconfig.pyin place and keeping the new tests:Validation
I did not run
make test(the whole backend suite): on this Windows machine it has pre-existing environmental failures unrelated to this diff, documented in a comment on #5553, so I ran the three memory-backend suites that cover the changed file instead. Frontend checks were not run — this diff cannot reach them. The diff is small enough to read in one pass: 22 added lines, 0 removed.AI assistance
__post_init__(where this class's other guards already are) and the message wording were worked out and verified at file/line level by the agent, and every number above comes from commands run on this branch. The account owner has now read every line of this diff and ticked the box below (2026-09-24), so this is no longer unreviewed agent output.Review follow-up (68d232d)
Three findings from review, all addressed on top of the original commit:
keyed off
base_url.startswith("http://"), which is case-sensitive, whileurlsplitlowercases the scheme — so
HTTP://internal:8000was accepted with a key set andallow_insecure_httpleftFalse, and httpx sent the key in the clear. It now readsurlsplit(base_url).scheme == "http", the way the mem0 sibling already does(
mem0/config.py:137).__post_init__checkednetloc, which is truthy for":8000", sohttp://:8000was accepted into the same accepted-now/dead-later classthis PR removes. It checks
hostnamenow, which rejectshttp://andhttp://:8000in one predicate. The mem0 and OpenViking siblings keep their
netloccheck — that ispre-existing and outside this diff unless you want it here.
__post_init__precisely for that path.
test_direct_construction_hits_the_same_guardcoversHonchoConfig(base_url=...)overlocalhost:8000,http://,http://:8000andftp://localhost:8000, andtest_http_scheme_case_does_not_bypass_the_insecure_key_guardparametrizes
HTTP://,HtTp://andhttp://with and without the opt-in.Measured on this branch: the five new/extended cases run against the previous head
(
cd76fc56) give5 failed, 12 passed— the failures areDID NOT RAISEon thescheme-case and host-less shapes, so they fail on behaviour; at
68d232dftests/test_honcho_memory_backend.pygives124 passed(115 atcd76fc56) andtests/test_honcho_memory_backend.py+tests/blocking_io/test_honcho_memory_backend.pytogether give
125 passed.ruff checkandruff format --checkare clean on bothchanged files.