path_security
Filesystem path safety primitives for resource handlers.
These functions help MCP servers reject paths that would resolve
outside the served root when extracted URI template parameters are
used in filesystem operations. They are standalone utilities usable from both the
high-level :class:~mcp.server.mcpserver.MCPServer and lowlevel server
implementations.
The canonical safe pattern::
from mcp.shared.path_security import safe_join
@mcp.resource("file://docs/{+path}")
def read_doc(path: str) -> str:
return safe_join("/data/docs", path).read_text()
PathEscapeError
Bases: ValueError
Raised by :func:safe_join when the resolved path escapes the base.
Source code in src/mcp/shared/path_security.py
24 25 | |
contains_path_traversal
Check whether a value, treated as a relative path, escapes its origin.
This is a base-free check: it does not know the sandbox root, so
it detects only whether .. components would move above the
starting point. Use :func:safe_join when you know the root — it
additionally catches symlink escapes and absolute-path injection.
Note
This is a string-level check on the value as supplied. It does
not model platform-specific filesystem normalisation (e.g. Win32
stripping of trailing dots and spaces from the final path
component). For filesystem access, use :func:safe_join, which
resolves through the OS and verifies containment.
The check is component-based: .. is dangerous only as a
standalone path segment, not as a substring. Both / and \
are treated as separators.
Example::
>>> contains_path_traversal("a/b/c")
False
>>> contains_path_traversal("../etc")
True
>>> contains_path_traversal("a/../../b")
True
>>> contains_path_traversal("a/../b")
False
>>> contains_path_traversal("1.0..2.0")
False
>>> contains_path_traversal("..")
True
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
A string that may be used as a filesystem path. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/mcp/shared/path_security.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | |
is_absolute_path
Check whether a value is an absolute filesystem path.
Absolute paths are dangerous when joined onto a base: in Python,
Path("/data") / "/etc/passwd" yields /etc/passwd — the
absolute right-hand side silently discards the base.
Detects POSIX absolute (/foo), Windows drive-absolute
(C:\foo) and drive-relative (C:foo), and Windows
UNC/root-relative (\\server\share, \foo).
Example::
>>> is_absolute_path("relative/path")
False
>>> is_absolute_path("/etc/passwd")
True
>>> is_absolute_path("C:\\Windows")
True
>>> is_absolute_path("")
False
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
A string that may be used as a filesystem path. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/mcp/shared/path_security.py
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
safe_join
Join path components onto a base, rejecting escapes.
Resolves the joined path and verifies it remains within base.
This is the gold-standard check: it catches .. traversal,
absolute-path injection, and symlink escapes that the base-free
checks cannot.
The symlink check is point-in-time: a directory swapped for a
symlink between this call and the caller's subsequent open would not
be re-checked. Handlers serving a tree that may be modified
concurrently should additionally open with O_NOFOLLOW or use
platform path-confinement primitives.
Example::
>>> safe_join("/data/docs", "readme.txt")
PosixPath('/data/docs/readme.txt')
>>> safe_join("/data/docs", "../../../etc/passwd")
Traceback (most recent call last):
...
PathEscapeError: ...
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base
|
str | Path
|
The sandbox root. May be relative; it will be resolved. |
required |
parts
|
str
|
Path components to join. Each is checked for null bytes and absolute form before joining. |
()
|
Returns:
| Type | Description |
|---|---|
Path
|
The resolved path, verified to be within |
Path
|
time. |
Raises:
| Type | Description |
|---|---|
PathEscapeError
|
If any part contains a null byte, any part is absolute, or the resolved path is not contained within the resolved base. |
Source code in src/mcp/shared/path_security.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |