VFX-E-1001¶
Title¶
PathOutsideWorkspace
Explanation¶
A path argument was refused before any filesystem call was made against it, for one of two reasons.
It named a network/UNC location (e.g. \\host\share\...) rather than a local file. This is
rejected always, on every path-taking tool, whether or not a workspace is configured, and defends
against a forced-authentication attack: on Windows, merely opening a UNC path can make the OS
silently attempt an SMB/NTLM authentication handshake against the remote host, leaking credentials to
an attacker-controlled server, without the file needing to exist at all. PathSafetyGuard performs
this check purely by inspecting the path string — it never touches the network.
It resolved outside the configured workspace root. This applies only when the server was started
with --workspace <path>. A relative path is first resolved against the workspace root; the result is
made absolute, its .. segments collapsed, and every symlink/junction on the way resolved to its
final target — repeatedly, until nothing more resolves, so a link whose target itself sits under
another link cannot smuggle a path back inside. If the result is not the workspace root or inside it,
the call is refused. Started without --workspace, the server has no root to contain against and
this half never fires — a relative path resolves against the server process's current directory and a
local path with ../ traversal is accepted, exactly as they always have been.
Or containment could not be established at all. The containment half refuses in two distinguishable situations, and the message says which. "Path resolves outside the configured workspace root" means the path was resolved completely and genuinely lands outside. "Path could not be verified as inside the configured workspace root" means the check never reached a verdict — the segment-resolution budget ran out (a very deeply nested path, or a symlink cycle), or a permission or I/O error interrupted the walk. The refusal and the code are the same in both cases, because a containment check that cannot demonstrate containment must fail closed; only the claim differs, since the server has no basis for asserting where an unresolvable path lands.
Both apply to path on validate_suite/normalize_suite/run_suite/plan_coverage, to
plan_coverage's eventsPath, and to a caller-supplied eventsPath on
explain_run/diagnose_run. Nothing is exempt from the containment half — it applies
uniformly to a path a caller named and to explain_run/diagnose_run's default events
path (the events file of the most recent finished run recorded in the run registry) alike. The
documented run_suite → explain_run round trip is unaffected, because run artefacts live inside
the workspace: with --workspace, run_suite writes its events file under <root>/.vouchfx/runs/,
so handing the returned eventsFilePath straight back to explain_run is contained on its merits
rather than waved through. Without --workspace there is no root to contain against and this half
never fires at all.
plan_coverage's UNC rejection is a behaviour change for hosts with no workspace configured.
Both of its path arguments previously bypassed this guard entirely and were spliced straight into the
vouchfx plan command line, so the engine subprocess — not this server — performed the SMB/NTLM
handshake described above. They now go through the same guard as every other path argument: the
containment half only when --workspace is set, the network/UNC half always. A plan_coverage call
naming a share therefore now returns this code where it previously ran.
The workspace root itself is checked too: --workspace \\host\share is refused at startup, before any
filesystem call is made against it.
Common causes¶
- A suite or events path pasted from a mapped network drive shown in its
\\server\share\...form instead of a local drive letter. - A path accidentally gaining a leading
\\from string concatenation in the calling code. - Genuinely testing against a file that lives on a network share.
- A relative path with enough
../segments to climb out of the workspace root it is resolved against. - A
../segment (or a symlink whose target sits outside the root) escaping the workspace the host configured. - A suite kept outside the workspace the host configured — a sibling checkout, or a scratch file in the OS temp directory.
- An
eventsPathfrom a run recorded by a server started with a different workspace root, or with no workspace at all (a temp-directory path): server-produced paths are contained like any other, so one produced under a different root is outside this one. - A
plan_coveragecall naming a UNC share as itspathoreventsPath— it used to be handed tovouchfx planunchecked and is now refused in both workspace modes. - A
plan_coverageeventsPathpointing at a history directory kept outside the workspace root (a shared CI artefact drop, say) while the suites themselves are inside it.
Fixes¶
- Use a local path — copy the file to local disk, or reference it via a mapped drive letter (e.g.
Z:\...) rather than its UNC form. - Check for accidental doubled backslashes introduced when building the path programmatically.
- If the file must live on a network share, copy it locally first; this guard does not have a network-path allowlist.
- Move the file inside the workspace root, or pass a path that is already inside it — the root the server is using is reported as
meta.workspaceRooton every successful tool result. - With a workspace configured, prefer a path relative to the workspace root (e.g.
e2e/orders.e2e.yaml); it no longer depends on whichever directory your MCP client launched the server from. - If containment is not what you wanted, start the server without
--workspace; the flag is what turns it on. - For
explain_run/diagnose_run, pass theeventsFilePaththis server returned fromrun_suitein the same workspace — it is written under<root>/.vouchfx/runs/and is therefore inside the root — or omiteventsPathentirely to use the most recent finished run. - For
plan_coverage, keep both the analysed suite tree and the event history inside the workspace root, or start the server without--workspace; a UNC location for either is refused in both modes. - If the message says the path could not be verified, the check ran out of road rather than finding an escape — check the path is not absurdly deep (over 256 segments) or reached through a symlink cycle, and that every directory on the way to it is readable by the account running the server.