Panniantong/Agent-Reach · error · PrivatePathError
{label}不能经过符号链接:{current}
Error message
{label}不能经过符号链接:{current} What it means
Raised by ensure_no_symlink_path when any existing component of the path being checked is a symbolic link. Agent Reach hardened its private-directory and file-read paths against symlink traversal attacks (TOCTOU / link-swapping), so private data directories and read targets must not traverse symlinks. The message is in Chinese: '{label} cannot pass through a symlink: {current}'.
Source
Thrown at agent_reach/utils/paths.py:46
expanded = os.path.expanduser("~")
if expanded and expanded != "~":
return Path(expanded)
return Path.home()
def ensure_no_symlink_path(path: str | Path, label: str = "路径") -> Path:
"""Reject any existing symlink component without resolving the path."""
target = Path(path)
absolute = Path(os.path.abspath(os.fspath(target)))
current = Path(absolute.anchor)
for part in absolute.parts[1:]:
current /= part
try:
mode = os.lstat(current).st_mode
except FileNotFoundError:
continue
if stat.S_ISLNK(mode):
raise PrivatePathError(f"{label}不能经过符号链接:{current}")
return target
def make_private_dir(path: str | Path) -> Path:
"""Create a directory restricted to the current user where supported."""
target = ensure_no_symlink_path(path, "私密目录")
target.mkdir(mode=0o700, parents=True, exist_ok=True)
ensure_no_symlink_path(target, "私密目录")
if sys.platform != "win32":
flags = (
os.O_RDONLY
| getattr(os, "O_DIRECTORY", 0)
| getattr(os, "O_NOFOLLOW", 0)
)
dir_fd = os.open(target, flags)
try:
ensure_no_symlink_path(target, "私密目录")
if hasattr(os, "fchmod"):View on GitHub (pinned to 93ae1d18c3)
Solutions
- Resolve symlinks before passing the path: pass Path(path).resolve() so the checked path contains real components
- Move the real directory into place instead of symlinking it (e.g. point the dotfiles tool the other way)
- Set XDG_CONFIG_HOME to a real (non-symlinked) directory
- On macOS, prefer real paths like /private/tmp over /tmp aliases when constructing paths
Example fix
# before
from agent_reach.utils.paths import make_private_dir
d = make_private_dir("~/.config/agent-reach") # ~/.config is a symlink
# after
from pathlib import Path
d = make_private_dir(Path("~/.config/agent-reach").expanduser().resolve()) Defensive patterns
Strategy: validation
Validate before calling
import os, stat
from pathlib import Path
def path_has_symlink(path: str | Path) -> bool:
absolute = Path(os.path.abspath(os.fspath(path)))
current = Path(absolute.anchor)
for part in absolute.parts[1:]:
current /= part
try:
if stat.S_ISLNK(os.lstat(current).st_mode):
return True
except FileNotFoundError:
return False
return False
# usage: assert not path_has_symlink(my_dir) Try / catch
from agent_reach.utils.paths import PrivatePathError, make_private_dir
try:
d = make_private_dir(target)
except PrivatePathError:
d = make_private_dir(Path(target).resolve()) # retry on the resolved path Prevention
- Pre-resolve paths with Path(...).resolve() before passing them into private-dir APIs
- Never symlink config dirs; let dotfiles tools create real files or symlinks in the other direction
- Set XDG_CONFIG_HOME to a real directory, not a link
When it happens
Trigger: Calling make_private_dir() or the private read helper with a path where an intermediate component (e.g. ~/.config being a symlink, common on macOS where ~/Library/... is linked, or a dotfiles-managed symlink) is a link. The final component being a symlink also triggers it since every part is lstat'ed.
Common situations: Users who symlink ~/.config to a dotfiles repo; nix/homebrew setups where config dirs are links; XDG_CONFIG_HOME pointing at a symlinked path; test fixtures that create temp dirs via symlinks; macOS where /tmp is a symlink to /private/tmp (use realpath before calling).
Related errors
- 读取目标不是常规文件:{target}
- gh hosts.yml 无法安全读取
- mcporter 配置文件无法安全读取:{exc}
- mcporter 配置文件不存在
- {label}不能经过符号链接:{current}
AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14).
Data as JSON: /api/errors/0166851c459cf6ed.
Report an issue: GitHub.