Skip to content

test(docx-mcp): harden vitest path allowlist against /tmp symlink topology#105

Merged
stevenobiajulu merged 2 commits intomainfrom
104-harden-test-path-allowlist-20260427
Apr 27, 2026
Merged

test(docx-mcp): harden vitest path allowlist against /tmp symlink topology#105
stevenobiajulu merged 2 commits intomainfrom
104-harden-test-path-allowlist-20260427

Conversation

@stevenobiajulu
Copy link
Copy Markdown
Member

Summary

  • Fix setup-path-roots.ts to compute the workspace root with the right segment count (was landing on packages/, broke the packages/docx-core join), and realpath every candidate before writing SAFE_DOCX_ALLOWED_ROOTS so /tmp/private/tmp topology no longer drops fixtures off the allowlist.
  • Improve assertSuccess in session-test-utils.ts to surface error.code + error.message on failure instead of the bare expected true received false.
  • Add a path_policy regression test that pins symlinked-root equivalence using a self-built /tmp/realdir + symlink pair (works on Linux too).

Fixes: #104

Why

A worktree under /tmp (which is /private/tmp on macOS) was producing PATH_NOT_ALLOWED from openDocument even though setup-path-roots already added cwd and the repo path. Two papercuts: the "repo root" walk was off by one segment, and the env entries weren't realpathed before being compared against the realpathed file path. The assertSuccess change is the smaller half of the same problem — when the gate did fire, the test failure didn't show the error code, so root-causing burned ~10 minutes per occurrence.

Test plan

  • npm run test:run --workspace=packages/docx-mcp — all 638 tests green.
  • npm run lint:workspaces — no errors (one preexisting warning unrelated to this change).
  • npm run check:spec-coverage — pass.
  • env -u SAFE_DOCX_ALLOWED_ROOTS npx vitest --root packages/docx-mcp run src/tools/path_policy.test.ts src/tools/path_policy_symlink_bounds.test.ts src/tools/open_document.test.ts — passes from a worktree under /tmp//private/tmp without any env var override.
  • New regression test enforceReadPathPolicy > allows access when allowed root and file path differ only by symlink exercises both directions of the symlink-root equivalence and was added rather than just relying on existing coverage.

…ology

The docx-mcp suite uses openDocument, which gates reads through
enforceReadPathPolicy against SAFE_DOCX_ALLOWED_ROOTS. A vitest setup
file is responsible for adding the workspace root and process.cwd() so
tests work regardless of where the worktree lives, but two papercuts
made env-only PATH_NOT_ALLOWED failures recur:

1. setup-path-roots.ts resolved "repo root" three segments up from
   src/testing/, which lands on packages/ rather than the actual
   workspace root. As a side effect, path.join(repoRoot, 'packages',
   'docx-core') resolved to the bogus packages/packages/docx-core and
   nothing under the workspace root proper was on the allowlist.
2. The setup added raw paths like /tmp/worktree-foo, never realpathed.
   On macOS /tmp is a symlink to /private/tmp; enforceReadPathPolicy
   realpaths the requested file (giving /private/tmp/...) and rejected
   it because the env entry was the un-realpathed form.

Fix both: walk four segments up to the workspace root, and run every
candidate through fs.realpathSync before joining. The lazy
canonicalization inside resolveAllowedRoots() still works as a backstop
but the env var is now correct on its face, which is easier to reason
about.

Also improve assertSuccess() in session-test-utils.ts so a failing
result surfaces error.code and error.message in the assertion failure
instead of "expected true received false". The previous form wasted
~10 minutes per occurrence whenever PATH_NOT_ALLOWED came back from
openDocument.

Add a path_policy regression test that builds a directory + sibling
symlink and verifies access is allowed in both directions, pinning
the /tmp ↔ /private/tmp behavior on Linux too.

Fixes: #104
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
site Ready Ready Preview, Comment Apr 27, 2026 2:49pm

Request Review

@github-actions github-actions Bot added the test label Apr 27, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Peer review flagged that the comment overclaimed: runtime path policy
already canonicalizes every SAFE_DOCX_ALLOWED_ROOTS entry inside
resolveAllowedRoots, so setup-side realpath is hardening (and a
log-readability nicety), not the primary fix. The primary fix is the
workspace-root segment count correction one block above.

Ref: #104
@stevenobiajulu stevenobiajulu enabled auto-merge (squash) April 27, 2026 14:49
@stevenobiajulu stevenobiajulu merged commit 33c11b1 into main Apr 27, 2026
22 checks passed
@stevenobiajulu stevenobiajulu deleted the 104-harden-test-path-allowlist-20260427 branch April 27, 2026 14:55
stevenobiajulu added a commit that referenced this pull request Apr 28, 2026
* chore(release): bump workspace versions to 0.9.1

Patch release covering bug fixes accumulated since v0.9.0:

- fix(docx-core): merge auxiliary parts in rebuild mode (#94, #101)
- fix(docx-core): emit paragraph-level markers outside <w:r> on rebuild (#109)
- fix(docx-core): preserve reply comments in rebuild mode (#108, #112)
- test(docx-mcp): harden vitest path allowlist against /tmp symlink topology (#105)
- ci: SHA-pin all GitHub Actions, add dependency review and Dependabot
- ci: fix PR title check for acronyms and fork PRs (#88)
- chore(skills): sync docx-editing to 0.3.0 (#95)

Generated via scripts/bump_version.mjs.

* fix(docx-mcp): declare @xmldom/xmldom dependency explicitly

The bumped package-lock.json regenerated cleanly and exposed a latent
bug: docx-mcp directly imports @xmldom/xmldom in tag_parser.ts but did
not declare it as a dependency. It was previously masked by npm
hoisting from docx-core's transitive dep — the workspace happened to
work because the lockfile carried a stale hoisted xmldom@0.8.11.

Pinning to ^0.8.11 (matching what was already running in production via
the silent hoist) preserves observable behavior. tag_parser depends on
xmldom 0.8's lenient parsing of cross-nested tags
(<b><i>text</b></i>) — xmldom 0.9 throws on such input. Migrating
docx-mcp's tag_parser to xmldom 0.9 is its own concern; this commit
keeps the 0.9.1 release a pure version bump.

docx-core continues to use xmldom ^0.9.9 (PR #75); the two versions
coexist under their respective package's node_modules. No DOM nodes
cross the docx-core/docx-mcp boundary, so the version split is safe.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docx-mcp: harden vitest setup against env-only PATH_NOT_ALLOWED failures

1 participant