fix: security hardening, performance, and input validation - #539
anthonyonazure wants to merge 1 commit into
Conversation
Security: - Sanitize all KG tool inputs (entity, subject, predicate, object) via sanitize_name() to block path traversal, null bytes, and overlong strings - Validate date params (as_of, valid_from, ended) against YYYY-MM-DD format - Validate direction param against allowed values (outgoing/incoming/both) - Replace WAL content_preview with content_hash (sha256) to prevent sensitive plaintext from being stored in the audit log - Sanitize all error messages returned to MCP clients — log full exceptions server-side, return generic messages to prevent leaking system paths and database internals Performance: - Add metadata cache with 2-second TTL so status/wings/rooms/taxonomy tools share one collection scan instead of four independent 10K fetches - Cache auto-invalidates on write operations (add_drawer, delete, diary) - Pass cached ChromaDB collection to search_memories() instead of creating a new PersistentClient per search call Functional: - Fix drawer ID collision: hash full content instead of content[:100], preventing different documents with identical prefixes from overwriting - Remove duplicate _client_cache/_collection_cache initialization - Replace all silent except:pass blocks with logger.error() calls - Expose query_relationship() as mempalace_kg_query_relationship MCP tool - Add 16 new tests covering input validation, error sanitization, WAL hashing, drawer ID uniqueness, cache invalidation, and the new tool Authored-by: Anthony Clendenen <anthonyonazure@users.noreply.github.com>
web3guru888
left a comment
There was a problem hiding this comment.
Comprehensive audit with real fixes across security, performance, and correctness. This is exactly the kind of hardening the MCP surface needed.
Approved with one minor concern flagged below.
What's solid:
-
Metadata cache (
_META_CACHE, 2s TTL): Smart approach. Coalescingstatus/wings/rooms/taxonomyfrom four independent 10K scans into one cached fetch is a real win for installations with large palaces. Cache invalidation ontool_add_drawer,tool_delete_drawer, andtool_diary_writelooks complete — all three write paths correctly call_invalidate_meta_cache(). -
drawer_idhash over full content (removing[:100]truncation): This was a latent collision bug. Two drawers differing only after character 100 in the same wing/room would have been silently overwritten. Correct fix. -
WAL content → SHA-256 hash: Right call. WAL files are often left world-readable and could contain sensitive content. The test in
TestWALSecuritycovers exactly the right scenario. -
Error message sanitization:
"Search failed"/"Duplicate check failed"/"Failed to file drawer"are generic without being useless. Good pattern. -
_validate_date+sanitize_nameon KG tools: Path traversal rejection tested, date format validated. Thedirectionenum check is clean. -
tool_kg_query_relationship: Useful new tool — querying by predicate is a common pattern and it was missing. Validated inputs, follows the samecount/factsresponse shape astool_kg_query. -
collection=colpassed tosearch_memories: Avoids creating a secondPersistentClientper search call, which was the root cause of the WAL flush issue in #538.
One concern:
tool_list_rooms previously pushed where={"wing": wing} to ChromaDB when a wing filter was specified. The new version fetches all metadata from cache and filters in Python. For the common case (single wing query, 10K drawer palace) this is slower — you load all metadata then discard most of it. The cache helps when tool_list_rooms is called right after tool_status, but not when called standalone with a wing argument.
Suggest keeping the ChromaDB where filter for the non-cached path when wing is specified, or accepting this as a known tradeoff in the PR description (the 2s TTL means it'll often be cached anyway in burst-query scenarios).
Not a blocker — correctness is preserved. Just worth documenting.
260 new test lines covering the new behaviors: input validation, cache invalidation, WAL hashing, error sanitization, and the new relationship query tool. That's the right way to ship a hardening PR.
web3guru888
left a comment
There was a problem hiding this comment.
Good set of hardening changes — a few notes from our integration work:
Metadata cache — the concept is solid. Calling tool_status(), tool_list_wings(), and tool_list_rooms() in the same agent loop currently triggers redundant full-collection scans, so caching the result for 2s is the right shape. One caveat: _get_all_metadata() still hard-codes limit=10000, so the cache is caching a potentially-truncated snapshot rather than a complete one. For palaces beyond 10K drawers this doesn't fix the underlying bug — it just makes the wrong answer faster. The paginated fetch pattern from #484/_fetch_all_metadata() should be the data source here. Cache the complete result, not the clipped one.
Cache invalidation — _invalidate_meta_cache() called after writes is exactly right. Stale cache after an add/upsert is a nasty class of bug (agent lists rooms, doesn't see the one it just wrote, writes again). This is the part of the implementation I'd prioritize keeping.
Security: error message sanitization — good call. Leaking ChromaDB stack traces or raw file paths to MCP clients is a real concern in multi-tenant or remotely-hosted setups. The sanitized surface should also cover the new sanitize_name() call sites — make sure malformed names don't bubble back through exception paths.
add() → upsert() in convo_miner.py — same fix as mrdeeme's #542 and the pattern correction in #298. This one keeps appearing across forks, which suggests it should be part of the official migration guide somewhere ("if you're on <0.6, you were calling add() — switch to upsert()").
Overlap with #540/#542 — there's meaningful surface overlap with #540 (KG path injection fix) and #542 (comprehensive hardening including the same upsert fix + Unicode normalization). Worth coordinating with those authors or the maintainer to decide which changes land where, so they don't conflict on merge.
Summary
Comprehensive audit of the MCP server surface area, addressing security, performance, and functional issues:
sanitize_name(), validate date/direction params, replace WAL plaintext content previews with SHA-256 hashes, sanitize error messages returned to MCP clients to prevent leaking system paths and DB internalsstatus/wings/rooms/taxonomyshare one collection scan instead of four independent 10K fetches; pass cached ChromaDB collection tosearch_memories()instead of creating a newPersistentClientper callcontent[:100]), remove duplicate cache variable initialization, replace all silentexcept: passwithlogger.error(), exposequery_relationship()asmempalace_kg_query_relationshipMCP toolFiles changed
mempalace/mcp_server.pymempalace/searcher.pycollectionparam, sanitize error messagetests/conftest.pytests/test_mcp_server.pytests/test_searcher.pyBreaking changes
content_hashinstead ofcontent_preview— tools that parse the WAL for content previews will need updatingsearch_memories()has a new optionalcollectionparameter (backwards compatible)Test plan
mempalace_kg_query_relationshiptool registered and functional