Background
The docx-mcp test suite uses openDocument, which gates file reads through enforceReadPathPolicy against SAFE_DOCX_ALLOWED_ROOTS. When the env var is unset, the runtime defaults to [$HOME, os.tmpdir()]. A vitest setupFiles entry — packages/docx-mcp/src/testing/setup-path-roots.ts — is already responsible for adding cwd and the repo path so tests work outside of $HOME.
In practice we still hit env-only test failures when the suite runs from a worktree under /tmp (which is a symlink to /private/tmp on macOS). Two contributing factors:
- The setup currently resolves "repo root" as
path.resolve(__dirname, '..', '..', '..') from src/testing/, which lands at packages/ rather than the actual workspace root.
- Setup adds raw paths (e.g.
/tmp/worktree-foo) but never realpaths them. The policy realpaths the requested file (/tmp/x → /private/tmp/x), and resolveAllowedRoots() realpaths the env entries lazily, but a single canonicalization at setup makes the behavior obvious and avoids relying on lazy resolution timing.
We also lose ~10 minutes every time we hit a PATH_NOT_ALLOWED because assertSuccess(result) only does expect(result.success).toBe(true) — the assertion failure shows "expected true received false" and discards the structured result.error.code/hint.
Reproduction
git worktree add /tmp/repro main
cd /tmp/repro
npm run test:run --workspace=packages/docx-mcp
# Expect: assertions like `open` fail without surfacing `PATH_NOT_ALLOWED`.
Proposed Fixes
Three layered fixes, in increasing depth:
-
Test setup self-configures the allowlist robustly. In setup-path-roots.ts:
- Resolve the workspace root with the correct number of segments (
packages/docx-mcp/src/testing → up 4 to the workspace root, not 3).
- Run each candidate through
fs.realpathSync before joining into SAFE_DOCX_ALLOWED_ROOTS, so /tmp/x and /private/tmp/x collapse to the same canonical form. This matches what enforceReadPathPolicy does at request time.
- Keep
process.cwd(), os.tmpdir(), and $HOME as candidates.
-
Verify symlink-equivalent root handling end-to-end. path_policy.ts already realpaths both sides, but we currently lack a regression test that pins this on macOS-style /tmp ↔ /private/tmp semantics. Add one so we don't regress.
-
Better assertion fingerprint. Update assertSuccess in packages/docx-mcp/src/testing/session-test-utils.ts to include result.error.code and result.error.message in both the expect() message and the thrown Error. A failing openDocument should immediately tell us PATH_NOT_ALLOWED (or FILE_NOT_FOUND, etc.) instead of "expected true received false".
These are low-risk and self-contained.
Acceptance
Background
The docx-mcp test suite uses
openDocument, which gates file reads throughenforceReadPathPolicyagainstSAFE_DOCX_ALLOWED_ROOTS. When the env var is unset, the runtime defaults to[$HOME, os.tmpdir()]. A vitestsetupFilesentry —packages/docx-mcp/src/testing/setup-path-roots.ts— is already responsible for addingcwdand the repo path so tests work outside of$HOME.In practice we still hit env-only test failures when the suite runs from a worktree under
/tmp(which is a symlink to/private/tmpon macOS). Two contributing factors:path.resolve(__dirname, '..', '..', '..')fromsrc/testing/, which lands atpackages/rather than the actual workspace root./tmp/worktree-foo) but never realpaths them. The policy realpaths the requested file (/tmp/x→/private/tmp/x), andresolveAllowedRoots()realpaths the env entries lazily, but a single canonicalization at setup makes the behavior obvious and avoids relying on lazy resolution timing.We also lose ~10 minutes every time we hit a
PATH_NOT_ALLOWEDbecauseassertSuccess(result)only doesexpect(result.success).toBe(true)— the assertion failure shows "expected true received false" and discards the structuredresult.error.code/hint.Reproduction
Proposed Fixes
Three layered fixes, in increasing depth:
Test setup self-configures the allowlist robustly. In
setup-path-roots.ts:packages/docx-mcp/src/testing→ up 4 to the workspace root, not 3).fs.realpathSyncbefore joining intoSAFE_DOCX_ALLOWED_ROOTS, so/tmp/xand/private/tmp/xcollapse to the same canonical form. This matches whatenforceReadPathPolicydoes at request time.process.cwd(),os.tmpdir(), and$HOMEas candidates.Verify symlink-equivalent root handling end-to-end.
path_policy.tsalready realpaths both sides, but we currently lack a regression test that pins this on macOS-style/tmp↔/private/tmpsemantics. Add one so we don't regress.Better assertion fingerprint. Update
assertSuccessinpackages/docx-mcp/src/testing/session-test-utils.tsto includeresult.error.codeandresult.error.messagein both theexpect()message and the thrownError. A failingopenDocumentshould immediately tell usPATH_NOT_ALLOWED(orFILE_NOT_FOUND, etc.) instead of "expected true received false".These are low-risk and self-contained.
Acceptance
setup-path-roots.tsrealpaths each candidate root and uses the actual workspace root./tmpand/private/tmpas equivalent on macOS-style filesystems.assertSuccessincludeserror.codeanderror.messagein failure output.npm run test:run --workspace=packages/docx-mcppasses from a/tmpworktree without settingSAFE_DOCX_ALLOWED_ROOTSmanually.