Skip to content

fix(mcp): reject symlink path escapes#93

Merged
sampleXbro merged 5 commits into
sampleXbro:developfrom
fengjikui:codex/mcp-writer-symlink-containment
Jul 8, 2026
Merged

fix(mcp): reject symlink path escapes#93
sampleXbro merged 5 commits into
sampleXbro:developfrom
fengjikui:codex/mcp-writer-symlink-containment

Conversation

@fengjikui

Copy link
Copy Markdown
Contributor

What does this PR do?

Reject MCP read/write/delete paths that escape canonical directories through symlinks by canonicalizing the allowed root and target before touching files.

This covers:

  • skill supporting-file reads through safeRead
  • canonical feature writes through safeWrite
  • canonical handler direct get / update reads
  • skill SKILL.md direct reads and supporting-file writes/deletes

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • Test coverage

Checklist

  • Tests written first (TDD)
  • All CI checks pass (pnpm test, pnpm lint, pnpm typecheck)
  • Changeset added (pnpm changeset) for user-visible changes

Validation

Failing-first checks before the fix:

  • node_modules/.bin/vitest run tests/unit/mcp/writers/safe-read.test.ts tests/unit/mcp/writers/safe-write.test.ts failed because symlinked paths resolved/read/wrote outside instead of rejecting.
  • node_modules/.bin/vitest run tests/unit/mcp/handlers/canonical-factory-edges.test.ts tests/unit/mcp/handlers/skills.test.ts failed for direct handler reads/writes through symlinked paths.

Passing checks after the fix:

  • node_modules/.bin/vitest run tests/unit/mcp/writers/safe-read.test.ts tests/unit/mcp/writers/safe-write.test.ts tests/unit/mcp/handlers/canonical-factory-edges.test.ts tests/unit/mcp/handlers/skills.test.ts — 45 passed
  • node_modules/.bin/vitest run tests/unit/mcp — 34 files / 360 tests passed
  • node_modules/.bin/tsc --noEmit
  • node_modules/.bin/eslint src tests
  • node_modules/.bin/prettier --check src/mcp/handlers/canonical-factory.ts src/mcp/handlers/skills.ts src/mcp/writers/path-containment.ts src/mcp/writers/safe-read.ts src/mcp/writers/safe-write.ts tests/unit/mcp/handlers/canonical-factory-edges.test.ts tests/unit/mcp/handlers/skills.test.ts tests/unit/mcp/writers/safe-read.test.ts tests/unit/mcp/writers/safe-write.test.ts
  • git diff --check
  • node_modules/.bin/tsx src/cli/index.ts lessons validate — exit 0, graph ok with existing warnings

Note: I used direct node_modules/.bin/* commands instead of pnpm scripts because the repo's local lessons warn that pnpm 11 can mutate the pnpm 10 lockfile shape in this checkout.

@sampleXbro sampleXbro added the bug Something isn't working label Jul 7, 2026
@sampleXbro

Copy link
Copy Markdown
Owner

Thanks for this, @fengjikui. It's a genuinely good catch. A committed symlink under .agentsmesh survives a Git clone, so anyone running their agent on a shared or cloned repository could have configuration or skill files read from - or written to or deleted - outside the project. Great work with the failing tests first and the changeset. 🙏

While reviewing the merge, we found that the fix needs to be expanded to fully close the class.

  • Anchor containment at the project root, not .agentsmesh. A .agentsmesh boundary canonicalizes through a symlinked .agentsmesh and cancels, so a symlinked .agentsmesh parent still escapes. This includes a destructive out-of-tree write via the settings handlers. Anchoring at projectRoot closes the whole class.
  • Remaining surfaces include settings config writes (update_config, permissions, hooks, ignore, mcp) and their read-before-write pre-reads, canonical list, and create/delete paths.
  • Windows CI: The symlink tests require it.skipIf(process.platform == 'win32') because unprivileged symlink() throws EPERM on Windows-latest.
  • Minor: The diff also removes an unrelated dist/cli.js lesson trigger. Please drop that, and this is probably better targeted at develop.

I've put together a completion that builds directly on your commit with the same approach plus the above, including tests and an end-to-end check. I'm happy to push it to your branch so it lands under your name or leave the details here for you to apply—your call. Either way, the credit stays yours.

…ete surfaces

Builds on @fengjikui's PR sampleXbro#93. Anchors containment at the project root — a
`.agentsmesh` boundary canonicalizes through a symlinked `.agentsmesh` and
cancels, so a symlinked parent still escaped — and extends the guard to every
remaining MCP filesystem surface:

- settings config reads AND writes (get/update config, permissions, hooks,
  ignore, mcp servers) via the readYaml/atomicWrite/safeConfigWrite choke points
- canonical list(); create() and delete() on canonical + skills
- read-before-write pre-reads (updatePermissions/updateIgnore append)

Hardening: fail-closed canonicalize (only ENOENT reconstructs), assert-before
existence probe (removes an out-of-project filename oracle), dangling-symlink doc
note, and it.skipIf(win) on all symlink tests (unprivileged Windows symlink()
throws EPERM on the windows-latest CI matrix).

Also drops the unrelated lessons.json trigger churn that rode in the original PR.

Co-authored-by: sampleXbro <55853222+sampleXbro@users.noreply.github.com>
@sampleXbro sampleXbro changed the base branch from master to develop July 8, 2026 20:00
@sampleXbro sampleXbro changed the base branch from develop to master July 8, 2026 20:01
@sampleXbro sampleXbro changed the base branch from master to develop July 8, 2026 20:07
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.78049% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/mcp/handlers/settings.ts 96.42% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/mcp/handlers/canonical-factory.ts 97.82% <100.00%> (+0.12%) ⬆️
src/mcp/handlers/skills.ts 100.00% <100.00%> (ø)
src/mcp/writers/path-containment.ts 100.00% <100.00%> (ø)
src/mcp/writers/safe-config-write.ts 100.00% <100.00%> (ø)
src/mcp/writers/safe-read.ts 100.00% <100.00%> (+10.00%) ⬆️
src/mcp/writers/safe-write.ts 100.00% <100.00%> (ø)
src/mcp/handlers/settings.ts 99.21% <96.42%> (+0.10%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sampleXbro sampleXbro merged commit 5d38ee4 into sampleXbro:develop Jul 8, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants