Context
PR #494 (issue #493) closed the validate-vs-sanitize asymmetry on
mem_agent_register / mem_agent_search / mm agent register —
all three now reject hostile-shaped agent_id values via
validate_agent_id. PR #494's review surfaced a sibling gap on the
share path that isn't covered by that change:
mem_agent_share(target=...) accepts an arbitrary namespace string
(packages/memtomem/src/memtomem/server/tools/multi_agent.py:177-201),
documented as 'shared' or 'agent-runtime:{agent_id}'. The value
is not validated before it lands in storage:
result, stats = await _mem_add_core(
...
namespace=target, # ← unchecked
...
)
So a caller passing target="agent-runtime:foo:bar" plumbs straight
through _mem_add_core and persists a chunk under exactly the
malformed namespace shape PR #494 was filed to keep out of storage
on every other surface. Same blast radius as the issue-#493 register
/ search bug, just on a different verb.
The same comment also applies to the matching CLI subcommand
mm agent share (packages/memtomem/src/memtomem/cli/agent_cmd.py)
which forwards --target straight into index_engine.index_file(..., namespace=target) without validation.
What we want
A deliberate validation gate on target= for both mem_agent_share
and mm agent share, keyed on the agent-runtime: prefix:
- When
target == SHARED_NAMESPACE ("shared") — accept verbatim,
same as today.
- When
target.startswith(AGENT_NAMESPACE_PREFIX) — split off the
prefix, run validate_agent_id on the remainder, raise the same
invalid agent-id 'X' shape as the register / search path. Reject
before any storage write.
- When
target is anything else (legacy free-form namespaces) —
decide between (a) accept verbatim (preserves the existing
multi-namespace freedom for non-agent buckets) or (b) reject with a
clear "expected 'shared' or 'agent-runtime:<agent_id>'" error.
Option (a) matches the docstring framing today; option (b) tightens
the contract. Pick one in the PR description and document it in
the docstring.
Out of scope:
- Whole-namespace validation (a generic
validate_namespace_string
for any colon-prefixed namespace) — that's a wider change and
should not block this gap.
- Changing
target's parameter shape from a full namespace string to
a bare agent_id — would be a breaking API change and removes a
documented degree of freedom (the 'shared' literal).
Acceptance
mem_agent_share(target="agent-runtime:foo:bar", chunk_id=<uuid>)
returns Error: invalid agent-id 'foo:bar': ... and does NOT
call _mem_add_core (assert via mock or via "no new chunk row").
mm agent share --target agent-runtime:foo:bar <uuid> exits
non-zero with the same error fragment and does NOT call
index_engine.index_file.
- Regression test pinning that
target="shared" and
target="agent-runtime:planner" (canonical) still work end-to-end.
- Cross-surface error parity with
mem_agent_register /
mem_session_start (invalid agent-id 'X' core fragment).
- Docstring on
mem_agent_share updated to note the validation gate.
Provenance
Context
PR #494 (issue #493) closed the validate-vs-sanitize asymmetry on
mem_agent_register/mem_agent_search/mm agent register—all three now reject hostile-shaped
agent_idvalues viavalidate_agent_id. PR #494's review surfaced a sibling gap on theshare path that isn't covered by that change:
mem_agent_share(target=...)accepts an arbitrary namespace string(
packages/memtomem/src/memtomem/server/tools/multi_agent.py:177-201),documented as
'shared'or'agent-runtime:{agent_id}'. The valueis not validated before it lands in storage:
So a caller passing
target="agent-runtime:foo:bar"plumbs straightthrough
_mem_add_coreand persists a chunk under exactly themalformed namespace shape PR #494 was filed to keep out of storage
on every other surface. Same blast radius as the issue-#493 register
/ search bug, just on a different verb.
The same comment also applies to the matching CLI subcommand
mm agent share(packages/memtomem/src/memtomem/cli/agent_cmd.py)which forwards
--targetstraight intoindex_engine.index_file(..., namespace=target)without validation.What we want
A deliberate validation gate on
target=for bothmem_agent_shareand
mm agent share, keyed on theagent-runtime:prefix:target == SHARED_NAMESPACE("shared") — accept verbatim,same as today.
target.startswith(AGENT_NAMESPACE_PREFIX)— split off theprefix, run
validate_agent_idon the remainder, raise the sameinvalid agent-id 'X'shape as the register / search path. Rejectbefore any storage write.
targetis anything else (legacy free-form namespaces) —decide between (a) accept verbatim (preserves the existing
multi-namespace freedom for non-agent buckets) or (b) reject with a
clear "expected
'shared'or'agent-runtime:<agent_id>'" error.Option (a) matches the docstring framing today; option (b) tightens
the contract. Pick one in the PR description and document it in
the docstring.
Out of scope:
validate_namespace_stringfor any colon-prefixed namespace) — that's a wider change and
should not block this gap.
target's parameter shape from a full namespace string toa bare
agent_id— would be a breaking API change and removes adocumented degree of freedom (the
'shared'literal).Acceptance
mem_agent_share(target="agent-runtime:foo:bar", chunk_id=<uuid>)returns
Error: invalid agent-id 'foo:bar': ...and does NOTcall
_mem_add_core(assert via mock or via "no new chunk row").mm agent share --target agent-runtime:foo:bar <uuid>exitsnon-zero with the same error fragment and does NOT call
index_engine.index_file.target="shared"andtarget="agent-runtime:planner"(canonical) still work end-to-end.mem_agent_register/mem_session_start(invalid agent-id 'X'core fragment).mem_agent_shareupdated to note the validation gate.Provenance
mm agent register(#493) #494 review (concern 2):mem_agent_share(target=...)is theremaining unvalidated agent-runtime concat site after the
register / search fix. Filing here per the reviewer's "non-blocking
for this PR — worth a follow-up issue under the same mem_agent_register / mem_agent_search: validate vs sanitize divergence #493 banner"
guidance.