Skip to content

Commit 84f6dfa

Browse files
authored
Merge pull request #36 from mkreyman/fix/issue-35-import-path-traversal
fix(security): confine context_import to exports dir (#35)
2 parents dd53a8f + daf6471 commit 84f6dfa

6 files changed

Lines changed: 701 additions & 79 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.13.0] - 2026-06-05
11+
12+
### Fixed
13+
14+
- Server-reported version is now read from `package.json` at runtime instead of a hardcoded string, which had drifted to `0.10.0`. Added an E2E test asserting the reported version matches `package.json` so it can't fall out of sync again.
15+
16+
### Security
17+
18+
- **Arbitrary local file read via unvalidated `context_import` filePath** (#35)
19+
- `context_import` passed the caller-supplied `filePath` straight to `fs.readFileSync`, with no path confinement. A malicious MCP client — or a prompt-injected agent — could point it at any file the server process could read: full contents for any JSON file (imported into the session, then retrievable via `context_get`/`context_export`), and the leading bytes of any other file (echoed back inside the `JSON.parse` error message)
20+
- Imports are now confined to a server-owned exports directory (`<DATA_DIR>/exports`, overridable via `MEMORY_KEEPER_EXPORT_DIR`). Relative paths resolve against that directory; absolute paths are accepted only if they resolve — after following symlinks via `realpath` — to a location inside it. Traversal (`../`) and absolute paths outside the directory are rejected
21+
- `context_export` now writes to that same exports directory (previously the OS temp dir), so the export → import round trip stays within the confined location
22+
- Import failures no longer echo raw exception messages — at every stage (read, JSON parse, and the database write) a generic message is returned and the detail is logged server-side, so no file bytes or inserted values can leak back to the caller
23+
- Import is hardened further: the resolved path must be a regular file (directories are rejected), capped at 50 MB and 100k entries to avoid memory/CPU exhaustion, and the whole import runs in a single transaction so a malformed row can never leave an orphaned or partially-populated session. Per-item and session-name shape are validated; `merge` only merges when a current session actually exists (and the result is reported honestly)
24+
- The "not found" and "outside the exports directory" cases now return an identical generic message, removing a file-existence oracle, and the resolved absolute path is never echoed back to the caller
25+
- `context_export`'s tool description and the import path resolution now guard the exports-directory startup (graceful FATAL on an unreadable directory)
26+
- **Round-trip fidelity fixes** surfaced by review of the above: imported items now restore their `channel`, `is_private`, and `metadata` columns (previously dropped); file-cache rows with `NULL` content are preserved (previously dropped); a stored `size` of `0` is no longer wrongly recomputed; skipped-malformed counts and "checkpoints present but not imported" are reported instead of being silently lost; and `context_export` warns when a produced file is too large to be re-imported. The new current session is published only after the import transaction commits, so a failed import can no longer leave `currentSessionId` pointing at a rolled-back session
27+
- Added an E2E security regression test suite that reproduces the issue #35 PoC (arbitrary JSON read, `/etc/passwd` byte leak, `..` traversal) and covers symlink escape, the `exports-dir`-prefix sibling boundary, the no-existence-oracle guarantee, empty/non-string paths, directory paths, valid-JSON-but-not-an-export, malformed-item skipping, and a data-preserving round trip
28+
- Reported by Zhihao Zhang (@mcfly-zzh)
29+
1030
## [0.12.2] - 2026-04-07
1131

1232
### Fixed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,8 @@ mcp_context_search({
814814
Share context or backup your work:
815815

816816
```javascript
817-
// Export current session
817+
// Export current session — writes into the exports directory
818+
// (<DATA_DIR>/exports/, overridable via MEMORY_KEEPER_EXPORT_DIR)
818819
mcp_context_export(); // Creates memory-keeper-export-xxx.json
819820

820821
// Export specific session
@@ -823,7 +824,9 @@ mcp_context_export({
823824
format: 'json',
824825
});
825826

826-
// Import from file
827+
// Import from a file inside the exports directory.
828+
// A bare filename resolves against that directory; the absolute path
829+
// returned by context_export also works.
827830
mcp_context_import({
828831
filePath: 'memory-keeper-export-xxx.json',
829832
});
@@ -835,6 +838,17 @@ mcp_context_import({
835838
});
836839
```
837840

841+
> **Security note:** For safety, `context_import` only reads files inside the
842+
> server-owned exports directory (`<DATA_DIR>/exports/`, or
843+
> `MEMORY_KEEPER_EXPORT_DIR` if set). Absolute paths outside that directory and
844+
> `../` traversal are rejected, so the tool cannot be steered at arbitrary
845+
> files on disk. Drop any file you want to import into that directory first.
846+
> Point `MEMORY_KEEPER_EXPORT_DIR` at a dedicated directory — not at a home
847+
> folder or a tree containing secrets — since any JSON file inside it becomes
848+
> importable. (Prior to this change, exports were written to the OS temp
849+
> directory; existing exports there must be moved into the exports directory to
850+
> be re-imported.)
851+
838852
### Knowledge Graph (Phase 4)
839853

840854
Automatically extract entities and relationships from your context:

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mcp-memory-keeper",
3-
"version": "0.12.2",
3+
"version": "0.13.0",
44
"description": "MCP server for persistent context management in AI coding assistants",
55
"main": "dist/index.js",
66
"bin": {

0 commit comments

Comments
 (0)