Skip to content

fix(migrate): harden importer path resolution and memory copy safety#109314

Merged
steipete merged 1 commit into
mainfrom
claude/migrate-importer-path-safety
Jul 16, 2026
Merged

fix(migrate): harden importer path resolution and memory copy safety#109314
steipete merged 1 commit into
mainfrom
claude/migrate-importer-path-safety

Conversation

@steipete

Copy link
Copy Markdown
Contributor

What Problem This Solves

An adversarial deep review (four independent passes) of the memory-migration importers surfaced confirmed path-resolution and copy-safety bugs, two of them privacy/security issues that leak files outside the intended source tree into indexed agent memory:

  • Claude autoMemoryDirectory: "~" imported the entire home directory. Validation exempted bare ~, which expands to os.homedir(); every Markdown file under home (up to scan caps) was copied into indexed memory. Claude Code's own contract only permits absolute or ~/-prefixed values.
  • Source hardlinks aliased arbitrary files. The shared memory copier rejected symlinks/hardlinks on the destination but read the source without hardlink rejection, so a Markdown hardlink inside auto-memory (e.g. aliasing ~/.ssh/id_rsa) was copied verbatim into memory.
  • Hermes HERMES_HOME set to the root ignored the active profile, importing the default profile's memory instead of the selected one.
  • Codex CODEX_HOME (and CLAUDE_CONFIG_DIR) were trimmed, resolving a different directory than the tools themselves use for space-bearing paths.
  • Dangling-symlink destinations were reported as clean "planned" copies but rejected at apply (preview/apply disagreement) across all three importers.
  • An inaccessible configured Claude memory directory (EACCES) silently produced an empty migration with no error.

Why This Change Was Made

Each fix mirrors the upstream tool's own contract, verified against source:

  • Reject bare ~ for Claude auto-memory; require absolute or ~/.
  • The shared memory copier (copyMemoryMigrationFileItem) now reads the source through an fs-safe root with hardlinks: "reject" + symlinks: "reject", matching the destination guard.
  • Hermes resolves the active profile when HERMES_HOME names the root, and trusts it verbatim only when it already names profiles/<name> — exactly Hermes's own heuristic (hermes_cli/main.py:461-473, issue fix(errors): distinguish process termination from billing errors #22502), including the HERMES_S6_SUPERVISED_CHILD default-slot exception.
  • Nonempty CODEX_HOME and CLAUDE_CONFIG_DIR are preserved verbatim (only empty = unset), matching upstream (codex-rs/utils/home-dir/src/lib.rs:14-49).
  • Destination existence uses lstat; non-regular targets surface as conflicts during planning across Codex, Claude, and Hermes.
  • Configured Claude memory probing propagates EACCES/EPERM with the path (only ENOENT/ENOTDIR are silent), via a scoped helper so unrelated optional-dir checks are unchanged.
  • Default Claude discovery honors CLAUDE_CONFIG_DIR; explicit --from classification stays on unambiguous signals (.claude basename / configured default) to avoid misreading ordinary repos.

User Impact

Importing your own memory behaves the same on the happy path (proven live below); malformed or hostile source layouts now fail closed instead of copying unintended files into indexed memory, and Hermes/Codex users on named profiles or relocated homes import the correct source.

Evidence

  • Focused tests green: node scripts/run-vitest.mjs extensions/migrate-claude extensions/migrate-hermes extensions/codex/src/migration src/plugin-sdk/migration-runtime.test.ts src/gateway/server-methods/migrations.test.ts (incl. new regression tests per fix; ~230 tests).
  • Live attack proof against a real gateway built from this branch: a Markdown hardlink aliasing an outside secret imported as an error (legit sibling file migrated, secret NOT written to the workspace); a settings.json autoMemoryDirectory: "~" made Claude detection fail closed with no home-tree scan. Ground-truth grep of the workspace: no leaked bytes.
  • Structured autoreview (gpt-5.6-sol): clean after resolving four raised findings; the Hermes-profile finding was reconciled against Hermes source (deep-review correct, one autoreview claim rejected with a code citation comment).
  • Upstream contracts personally inspected: ../codex/codex-rs/utils/home-dir/src/lib.rs, ../hermes-agent/hermes_cli/main.py.

@steipete
steipete merged commit 54eb03f into main Jul 16, 2026
152 of 157 checks passed
@steipete
steipete deleted the claude/migrate-importer-path-safety branch July 16, 2026 19:56
@steipete

Copy link
Copy Markdown
Contributor Author

Merged via squash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant