You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #499 closed the bypass on caller-supplied namespace= / target= overrides at the session-start surfaces and mem_agent_share, completing the kin gap to the agent_id work in #491 / #494 / #498. But the mem_ns_* namespace CRUD tools still write user-supplied namespace strings into either app state or the storage row with no validation — and at least one of them (mem_ns_set) creates a transitive bypass back into the same session-row shape #496 was filed to close.
The transitive bypass
mem_session_start derives the session row's namespace from this priority chain (server/tools/session.py:101-112):
agent-runtime:<agent_id> when agent_id != "default" — gated via validate_agent_id
app.current_namespace — ungated
"default"
mem_ns_set writes app.current_namespace directly with no validation (server/tools/namespace.py:67-72):
mem_ns_set(namespace="agent-runtime:foo:bar") # silently accepted todaymem_session_start(agent_id="default") # falls through to step 3# → effective_ns = app.current_namespace = "agent-runtime:foo:bar"# → storage row lands with the same shape PR #499 closes elsewhere
The user-visible outcome — a malformed namespace in the sessions table — is identical to the bypass PR #499 closes; it just enters through a different door.
Other unguarded surfaces in the same file
mem_ns_set(namespace=...) — writes app.current_namespace (the transitive bypass above).
mem_ns_rename(old=..., new=...) — writes the new name into chunk rows via storage.rename_namespace.
mem_ns_assign(namespace=..., old_namespace=...) — writes the target namespace via storage.assign_namespace.
mem_ns_update(namespace=...) — writes namespace meta via storage.set_namespace_meta (already covered by validate_agent_id in mem_agent_register, so the meta row exists, but the lookup key is unvalidated).
mem_ns_delete(namespace=...) — read-shaped, but accepts arbitrary strings into the delete_by_namespace SQL filter; less urgent than the writers but worth pulling under the same gate for consistency.
All five currently apply only an if not namespace.strip(): return "Error: ..." empty-check.
What we want
Apply validate_namespace (introduced in #499) to every public surface that accepts a user-supplied namespace string on the mem_ns_* tools. Same forward-shield-on-input semantics: writes / state mutations refuse hostile shapes loudly, with the same Error: invalid namespace 'X': ... text as the session-start gates.
Acceptance
All five mem_ns_* tools reject hostile-shaped namespace= (and old_namespace= / new= where applicable) with Error: invalid namespace ... before storage / state is touched.
Regression test pinning that mem_ns_set(namespace="agent-runtime:foo:bar") followed by mem_session_start(agent_id="default") cannot land an agent-runtime:foo:bar session row via the current_namespace fallback.
CLI mirror: mm ns set (and any other CLI surfaces that wrap these) inherit the gate.
Context
PR #499 closed the bypass on caller-supplied
namespace=/target=overrides at the session-start surfaces andmem_agent_share, completing the kin gap to theagent_idwork in #491 / #494 / #498. But themem_ns_*namespace CRUD tools still write user-supplied namespace strings into either app state or the storage row with no validation — and at least one of them (mem_ns_set) creates a transitive bypass back into the same session-row shape #496 was filed to close.The transitive bypass
mem_session_startderives the session row's namespace from this priority chain (server/tools/session.py:101-112):namespace=argument — gated by fix: validate user-supplied namespace= overrides on session entry points #499agent-runtime:<agent_id>whenagent_id != "default"— gated viavalidate_agent_idapp.current_namespace— ungated"default"mem_ns_setwritesapp.current_namespacedirectly with no validation (server/tools/namespace.py:67-72):The user-visible outcome — a malformed namespace in the
sessionstable — is identical to the bypass PR #499 closes; it just enters through a different door.Other unguarded surfaces in the same file
mem_ns_set(namespace=...)— writesapp.current_namespace(the transitive bypass above).mem_ns_rename(old=..., new=...)— writes the new name into chunk rows viastorage.rename_namespace.mem_ns_assign(namespace=..., old_namespace=...)— writes the target namespace viastorage.assign_namespace.mem_ns_update(namespace=...)— writes namespace meta viastorage.set_namespace_meta(already covered byvalidate_agent_idinmem_agent_register, so the meta row exists, but the lookup key is unvalidated).mem_ns_delete(namespace=...)— read-shaped, but accepts arbitrary strings into thedelete_by_namespaceSQL filter; less urgent than the writers but worth pulling under the same gate for consistency.All five currently apply only an
if not namespace.strip(): return "Error: ..."empty-check.What we want
Apply
validate_namespace(introduced in #499) to every public surface that accepts a user-supplied namespace string on themem_ns_*tools. Same forward-shield-on-input semantics: writes / state mutations refuse hostile shapes loudly, with the sameError: invalid namespace 'X': ...text as the session-start gates.Acceptance
mem_ns_*tools reject hostile-shapednamespace=(andold_namespace=/new=where applicable) withError: invalid namespace ...before storage / state is touched.mem_ns_set(namespace="agent-runtime:foo:bar")followed bymem_session_start(agent_id="default")cannot land anagent-runtime:foo:barsession row via thecurrent_namespacefallback.mm ns set(and any other CLI surfaces that wrap these) inherit the gate.Out of scope
validate_agent_idconcat is safe by construction).mem_add/mem_searchnamespace=arguments — broader UX call covered separately if pursued.Provenance
mem_ns_setwritesapp.current_namespacedirectly with no validation… effective_ns = app.current_namespace = 'agent-runtime:foo:bar' → storage row lands with the same shape Validate user-supplied namespace= overrides on agent session entry points #496 was filed to close."