Panniantong/Agent-Reach · critical · ConfigSecurityError

{label}不能经过符号链接:{current}

Error message

{label}不能经过符号链接:{current}

What it means

Config writes are hardened against symlink redirection: before atomically writing the YAML (in _atomic_write_yaml, agent_reach/config.py:43→53), the target config file path is checked with ensure_no_symlink_path. If the path to ~/.agent-reach/config.yaml traverses or ends at a symlink, a ConfigSecurityError (subclass of ConfigError/RuntimeError) is raised with the Chinese message '<label>不能经过符号链接:<path>' ('the config file path must not pass through a symlink'). This prevents an attacker from redirecting credential writes.

Source

Thrown at agent_reach/config.py:43


class ConfigError(RuntimeError):
    """Base class for configuration errors safe to show to the user."""


class ConfigReadOnlyError(ConfigError):
    """Raised when code tries to mutate an explicitly read-only config."""


class ConfigSecurityError(ConfigError):
    """Raised when a config path could redirect credential reads or writes."""


def _reject_symlink(path: Path, label: str) -> None:
    try:
        ensure_no_symlink_path(path, label)
    except PrivatePathError as exc:
        raise ConfigSecurityError(str(exc)) from exc


def _atomic_write_yaml(target: Path, data: dict) -> None:
    """Atomically replace ``target`` with owner-only YAML.

    The temporary file lives beside the target so ``os.replace`` remains an
    atomic same-filesystem operation. Existing symlinks are rejected rather
    than followed or silently replaced.
    """
    _reject_symlink(target, "配置文件")
    fd, tmp_name = tempfile.mkstemp(
        dir=str(target.parent),
        prefix=f".{target.name}.",
        suffix=".tmp",
    )
    tmp_path = Path(tmp_name)
    try:
        if os.name != "nt" and hasattr(os, "fchmod"):

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Replace the symlink with the real file/dir: move the actual config to ~/.agent-reach/config.yaml and remove the symlink
  2. If using a dotfile manager, exclude ~/.agent-reach from stow/mackup management, or have the manager copy instead of link
  3. Check every path component: ls -la ~/.agent-reach/ and readlink -f ~/.agent-reach/config.yaml to find the offending link
  4. If sharing across machines is the goal, copy the file explicitly (scp/rsync) rather than linking

Example fix

# before: symlink-managed config
~/.agent-reach/config.yaml -> ~/dotfiles/agent-reach/config.yaml

# after: real file (sync by copying, not linking)
rm ~/.agent-reach/config.yaml
cp ~/dotfiles/agent-reach/config.yaml ~/.agent-reach/config.yaml
chmod 600 ~/.agent-reach/config.yaml
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def config_path_is_safe(base: Path) -> bool:
    """True when no component of the config file path is a symlink."""
    cur = base
    while True:
        if cur.is_symlink():
            return False
        if cur == cur.parent:
            return True
        cur = cur.parent

assert config_path_is_safe(Path.home() / ".agent-reach" / "config.yaml")

Try / catch

from agent_reach.config import Config, ConfigSecurityError

try:
    cfg = Config(); cfg.save()
except ConfigSecurityError as exc:
    if "符号链接" in str(exc):
        repair_symlinked_config()  # convert link to real file, then retry
    else:
        raise

Prevention

When it happens

Trigger: Config.save() (or any `agent-reach configure` write) when ~/.agent-reach/config.yaml is a symlink to another location, or when ~/.agent-reach itself is a symlink (that case is labeled 配置目录 instead). Dotfile managers (stow, chezmoi, mackup) commonly create such symlinks.

Common situations: Users managing dotfiles with GNU stow or mackup symlinking ~/.agent-reach into a repo; multi-user setups sharing one config via symlink; sandboxed environments that symlink home subdirectories.

Related errors


AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14). Data as JSON: /api/errors/90db115f4894f72a. Report an issue: GitHub.