Security hardening: input validation, argument whitelisting, concurrency & WAL fixes#647
Merged
bensig merged 1 commit intoMemPalace:developfrom Apr 12, 2026
Merged
Conversation
…g, concurrency safety, and WAL fixes Addresses findings from security audit (ref MemPalace#401): inconsistent sanitization across MCP tools, unfiltered argument dispatch allowing audit trail spoofing, SQLite concurrency issues, WAL permission race condition, sensitive content in logs, and internal error leakage to MCP callers. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
8 tasks
2 tasks
jphein
added a commit
to jphein/mempalace
that referenced
this pull request
Apr 12, 2026
… path diary_write() and diary_read() accept optional wing parameter. Stop hook derives project wing from transcript path instead of hardcoding wing_session-hook. Preserves MemPalace#647's sanitize_name validation and last_n clamping. Co-Authored-By: Claude Opus 4.6 <[email protected]>
bensig
added a commit
that referenced
this pull request
Apr 12, 2026
1. min_similarity backwards-compat: convert similarity to distance scale (1.0 - similarity) instead of passing raw value as max_distance 2. Restore structured error reporting (error + partial fields) in tool_status, tool_list_wings, tool_list_rooms, tool_get_taxonomy — reverts silent except:pass that dropped #647 security hardening 3. inode cache: remove falsy-zero short-circuit so missing DB file triggers reconnect instead of reusing stale client 4. _fetch_all_metadata: check for empty batch before extending/advancing offset to prevent infinite loop on concurrent deletion 5. KG initialization: only override path when --palace is explicit; default runs use KnowledgeGraph's built-in default path Co-authored-by: jphein <[email protected]>
bensig
added a commit
that referenced
this pull request
Apr 12, 2026
…esolves #635) (#667) * feat: MCP reliability — inode detection, WAL rotation, metadata cache, search limits Infrastructure hardening for the MCP server: - Detect palace DB replacement via inode tracking (repair command support) - WAL rotation to prevent unbounded WAL growth - _fetch_all_metadata() + _get_cached_metadata() with 60s TTL for taxonomy/status - _MAX_RESULTS cap (100) with limit clamping [1, _MAX_RESULTS] - max_distance parameter for similarity threshold in search - Handle all notifications/* methods, null arguments, method=None - Remove duplicate _client_cache = None declarations - searcher.py max_distance parameter passthrough Co-Authored-By: Claude Opus 4.6 <[email protected]> * feat: new MCP tools (get/list/update drawer, hook settings, memories filed), export, normalize New MCP tools: - mempalace_get_drawer: fetch single drawer by ID with full content - mempalace_list_drawers: paginated listing with wing/room filter - mempalace_update_drawer: update content/wing/room on existing drawers - mempalace_hook_settings: get/set hook behavior (silent_save, desktop_toast) - mempalace_memories_filed_away: check latest checkpoint status Also includes: - exporter.py: export palace as browsable markdown files - normalize.py: tool_use/tool_result capture for richer transcript mining - layers.py: updated for new tool integration - config.py: hook settings properties (hook_silent_save, hook_desktop_toast) Depends on PR 3 (reliability) for _MAX_RESULTS, _metadata_cache, WAL logging. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: normalize.py handles string messages and Read offset type mismatch Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: params null guard, L2→cosine docs, empty tool_use_map key guard - Handle explicit null in MCP params (request.get("params") or {}) - Fix search tool description: L2 → cosine distance (collection uses hnsw:space=cosine) - Guard against empty string key in tool_use_map from malformed JSONL entries Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: rename ambiguous var 'l' to 'line' (E741 lint) Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: address code review findings (5 issues) 1. min_similarity backwards-compat: convert similarity to distance scale (1.0 - similarity) instead of passing raw value as max_distance 2. Restore structured error reporting (error + partial fields) in tool_status, tool_list_wings, tool_list_rooms, tool_get_taxonomy — reverts silent except:pass that dropped #647 security hardening 3. inode cache: remove falsy-zero short-circuit so missing DB file triggers reconnect instead of reusing stale client 4. _fetch_all_metadata: check for empty batch before extending/advancing offset to prevent infinite loop on concurrent deletion 5. KG initialization: only override path when --palace is explicit; default runs use KnowledgeGraph's built-in default path Co-authored-by: jphein <[email protected]> --------- Co-authored-by: jp <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: jphein <[email protected]>
This was referenced Apr 12, 2026
skuznetsov
pushed a commit
to skuznetsov/mempalace
that referenced
this pull request
Apr 12, 2026
Merge origin/develop into codex/pg-backend-stage2 after upstream PRs MemPalace#647 and MemPalace#667 landed. Resolve the MCP server conflict by preserving the new cached metadata helpers, WAL hardening, drawer update/list tools, and exporter changes from develop while keeping the PostgreSQL backend abstraction: MCP collection access now goes through palace.get_collection(...) instead of constructing a Chroma PersistentClient directly. Keep the large-table optimization for PostgreSQL: metadata pagination no longer performs an exact col.count() pre-pass, MCP status derives total_drawers from fetched metadata rows, and MemoryStack.status() can use backend-provided estimated_count() so PostgreSQL can use catalog/statistics estimates rather than SELECT COUNT(*) on status paths. Also make tool_update_drawer backend-neutral by using upsert() instead of the Chroma-only update() method, with tests covering the estimated count path. Verification: uv run pytest -q (671 passed, 106 deselected); uv run ruff check .; git diff --check; git diff --cached --check; precise conflict-marker scan.
This was referenced Apr 12, 2026
5 tasks
jphein
added a commit
to jphein/mempalace
that referenced
this pull request
Apr 12, 2026
The schema-based argument filter (from MemPalace#647) strips all kwargs not declared in input_schema. This breaks handlers that accept **kwargs for pass-through to ChromaDB or other backends. Add inspect.Parameter.VAR_KEYWORD check before filtering — handlers with **kwargs receive all arguments unfiltered. Co-Authored-By: Claude Opus 4.6 <[email protected]>
3 tasks
jphein
added a commit
to jphein/mempalace
that referenced
this pull request
Apr 12, 2026
The schema-based argument filter (from MemPalace#647) strips all kwargs not declared in input_schema. This breaks handlers that accept **kwargs for pass-through to ChromaDB or other backends. Add inspect.Parameter.VAR_KEYWORD check before filtering — handlers with **kwargs receive all arguments unfiltered. Co-Authored-By: Claude Opus 4.6 <[email protected]>
bensig
pushed a commit
that referenced
this pull request
Apr 12, 2026
* fix: skip arg whitelist for handlers accepting **kwargs (#572) The schema-based argument filter (from #647) strips all kwargs not declared in input_schema. This breaks handlers that accept **kwargs for pass-through to ChromaDB or other backends. Add inspect.Parameter.VAR_KEYWORD check before filtering — handlers with **kwargs receive all arguments unfiltered. Co-Authored-By: Claude Opus 4.6 <[email protected]> * fix: guard inspect.signature failure, default to filtering Wrap inspect.signature() in try/except — on failure, default to filtering (safe fallback). Addresses Copilot feedback on fragility. Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Security hardening addressing findings from audit (ref #401). All changes are security-only — no features, no refactors, no behavior changes for valid inputs.
Findings and fixes by priority
HIGH — Inconsistent input validation
sanitize_name()was applied to write tools (tool_kg_add,tool_diary_write,tool_add_drawer) but not to their companion read/mutate tools. An MCP caller could pass unsanitized strings containing path traversal sequences or special characters to these unprotected tools:tool_kg_queryentitypassed to_entity_id()→ SQLsanitize_name()appliedtool_kg_invalidatesubject/predicate/objectto SQLsanitize_name()applied to all threetool_kg_timelineentityto SQLsanitize_name()appliedtool_diary_readagent_nameinto ChromaDB where clausesanitize_name()appliedAlso added
directionvalidation intool_kg_query— previously accepted any string.HIGH — Unfiltered argument dispatch
handle_request()passed**tool_argsdirectly from the MCP JSON-RPC request to tool handlers with no filtering. This allowed callers to set internal parameters likeadded_by(default"mcp") andsource_fileto arbitrary values, spoofing the WAL audit trail.Fix: Arguments are now whitelisted against the tool's declared
input_schemaproperties before dispatch. Undeclared keys are silently dropped.MEDIUM — SQLite concurrency (knowledge_graph.py)
KnowledgeGraphusescheck_same_thread=Falsewith a shared singleton connection but had no locking. Concurrent MCP requests could corrupt the database or produce inconsistent reads.Fix: Added
threading.Lock()around all read and write operations (add_entity,add_triple,invalidate,query_entity).MEDIUM — WAL file permission race condition
The WAL file was written first, then
chmod(0o600)was applied after — leaving a brief window where the file was world-readable on every single write.Fix:
touch(mode=0o600)at startupos.open()with0o600mode for atomic permission-safe writeschmod()call entirelyMEDIUM — Sensitive content in WAL logs
content_previewandentry_previewfields logged the first 200 characters of user content to the plaintext WAL file. This is an information disclosure risk for personal memory content.Fix: Content preview fields are now redacted in WAL entries (
[REDACTED N chars]). The WAL still logs operation type, IDs, timestamps, and metadata — sufficient for audit without exposing content.LOW — Internal error leakage
tool_check_duplicateandtool_diary_readreturnedstr(e)to MCP callers, potentially exposing internal file paths, database state, or stack traces.Fix: Generic error messages returned to callers. Full exceptions logged server-side via
logger.exception().LOW — No bounds on numeric parameters
max_hopsintool_traverse_graphhad no upper bound — could cause runaway graph traversallimitintool_searchhad no caplast_nintool_diary_readhad no capint(value),float(value)) could crash on invalid strings with no error handlingFix:
max_hopscapped to[1, 10]limitcapped to[1, 50]last_ncapped to[1, 100]-32602(Invalid params) on failureFiles changed
mempalace/mcp_server.py— All MCP-layer fixes (validation, whitelisting, WAL, error handling, bounds)mempalace/knowledge_graph.py— Threading lock for SQLite concurrency safetyTest plan
uv run pytest tests/ -x -q)tool_kg_queryrejects names with path traversal characterstool_kg_invalidaterejects unsanitized inputadded_by: "attacker") are dropped in dispatch0o600permissions from the starttool_traverse_graphwithmax_hops=999gets capped to 10-32602error instead of crashing🤖 Generated with Claude Code