Skip to content

Validate user-supplied namespace strings on mem_ns_* CRUD tools (transitive bypass to session rows) #500

@memtomem

Description

@memtomem

Context

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):

  1. Explicit namespace= argument — gated by fix: validate user-supplied namespace= overrides on session entry points #499
  2. agent-runtime:<agent_id> when agent_id != "default" — gated via validate_agent_id
  3. app.current_namespaceungated
  4. "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 today
mem_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.

Out of scope

Provenance

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions