Implementation Guide

    How to Detect Filesystem Model Context Protocol Tool Calls That Reach Outside Configured Roots

    Resolve the target of every filesystem tool call to a canonical path, follow symlinks, and compare it to each approved root by path component before the call executes. Record the requested path, resolved path, matched root, decision, caller and timestamp. Evaluate this in the execution path so an out-of-root call can be blocked, not just found later.

    Detection logic for each tool call

    1. 1

      1. Capture the raw request

      Store the tool name, every path argument exactly as received, and the caller context before any transformation.

    2. 2

      2. Decode and normalize

      Reject or flag undecodable input, null characters and mixed separators. Collapse dot segments and apply platform-appropriate case and Unicode rules.

    3. 3

      3. Resolve to a canonical path

      Make relative paths absolute against a fixed base, then follow symlinks to the real target. For new files, resolve the parent.

    4. 4

      4. Test containment per root

      Compare path components against every approved root. With multiple roots, record which one matched. No match means out of scope.

    5. 5

      5. Bind the check to the use

      Tie the decision to the file actually opened, since a path valid at check time may change before use. This is the check/use gap NIST SAMATE describes with lstat and readdir.

    6. 6

      6. Emit a decision

      Produce allow, redact or block with the reason, and write the evidence record whatever the outcome.

    Validating the detection before relying on it

    • Fuzz path arguments with traversal, encoded and mixed-separator variants and confirm each is blocked or flagged.
    • Add regression cases for symlinks inside allowed directories, based on CVE-2025-53109.
    • Confirm deployed Filesystem server versions are at or above 0.6.4 or 2025.7.01.
    • Include a sibling-directory case to catch prefix-matching errors.
    • Verify every blocked and allowed call produces a complete record with both paths and the matched root.
    • Keep CI checks that flag changes to files outside the requested scope as a backstop only.

    How roots work and where enforcement can fail

    In MCP, roots describe the directories a filesystem server is expected to operate within. They are a statement of intended scope. A root does not restrict anything on its own. The boundary holds only if the code that handles each read, write or directory operation actually checks the target against it.

    That is why the boundary should be verified rather than assumed from server configuration. MCP servers run with delegated user permissions and often take part in chained tool calls, so a single flaw in path handling can have wide reach. CVE-2025-53109 is a concrete example: versions of the MCP Filesystem server before 0.6.4 or 2025.7.01 could allow access to unintended files through symlinks within allowed directories. A path can look in scope as a string and still resolve somewhere else.

    Tool annotations do not close this gap. They are hints, not enforcement, and monitoring tool definitions reveals metadata changes, not changed behavior behind an unchanged definition. Root decisions need to be made on the actual call and its actual arguments.

    Signals that a call reaches outside its roots

    Start with the resolved path, because every other signal is a way of changing it. A call is out of scope when the canonical target, after all resolution, is not contained in any approved root.

    Traversal sequences such as parent-directory segments are the obvious signal, but they are rarely the only one. Encoding variants can hide them: percent-encoded separators, double encoding, alternate Unicode forms and embedded null characters. Relative paths depend on a working directory that the agent may not control. Symlinks inside a root can point outside it, so a lexical check passes while the real target fails. On Windows, drive letters, UNC paths and backslash separators add forms that a POSIX-minded check may miss. Case-insensitive filesystems mean two spellings can name the same file.

    Prefix-matching errors deserve explicit attention. A string prefix test treats a sibling directory whose name merely begins with the root's name as inside the root. Compare whole path components, never raw string prefixes. For operations with two paths, such as move or copy, evaluate both source and destination. For writes to files that do not yet exist, resolve the parent directory.

    Telemetry fields that make a call reconstructable

    Server-side logs alone rarely carry enough context to reconstruct an out-of-scope attempt. OWASP guidance calls for logging every tool invocation with full parameters, user context and timestamps, feeding those records into a SIEM, and alerting on unusual patterns such as new tools being called or abnormal call frequency.

    For filesystem calls, that translates into a minimum record: timestamp, caller identity, tool name, requested path, resolved path, matched root (or none), the decision and its reason, and the server and version handling the call. The pair of requested and resolved paths is the most useful detail, because a divergence between them is itself a detection signal. Keep the records in append-only storage, consistent with OWASP's recommendation of immutable audit trails.

    Full parameters can contain credentials, file contents or environment variables that agents pass without regard to data classification. Decide in policy what is redacted before storage, and apply that rule consistently so evidence stays useful without becoming a new exposure.

    Detection versus enforcement at the tool-call layer

    Detection after the fact tells you a file was read. Enforcement in the execution path decides whether it is read. If the check runs only in server logs or in later review, an out-of-root call has already completed by the time anyone sees it. Evaluating arguments before execution is consistent with OWASP advice to validate tool arguments and apply least privilege to what an agent can invoke.

    There is a design choice between checking inside each server and checking at the call layer in front of all of them. A server-side check depends on every server implementing it correctly, and CVE-2025-53109 shows that implementations can fail. A call-layer check applies one policy across servers, though it must resolve paths the way the server will, or it risks disagreeing with the server about the target.

    Trussed enforces policy on tool calls and MCP servers in the execution path, with allow, redact and block outcomes enforced at runtime, not reviewed after the fact. Each enforcement decision generates audit-ready evidence, mapped to frameworks including NIST. A team can run the same logic with its own components, but the requirement is the same: the decision and the record come from one point.

    Root boundary check per tool call

    Requested path

    The string the agent supplied, kept exactly as received.

    Resolved path

    Canonical target after normalization, decoding and symlink resolution.

    Root match

    Component-wise containment test against every approved root.

    Decision and evidence

    Allow, redact or block, recorded with caller identity and timestamp.

    Verify the boundary on every call

    See how policy enforced in the execution path allows, redacts or blocks filesystem tool calls and produces audit-ready evidence for a regulated enterprise.

    See How Evidence Is Produced