abhigyanpatwari/GitNexus · error · SandboxError

Compound Engineering plugin entries must not be symlinks: {e

Error message

Compound Engineering plugin entries must not be symlinks: {entry.path}

What it means

walk() rejects any entry whose lstat mode is a symlink (stat.S_ISLNK). Symlinks anywhere under skills/, scripts/, or assets/ are forbidden because they can escape the bounded source and create TOCTOU windows during the snapshot copy.

Source

Thrown at eval/workflow_bench/runtime_mounts.py:327

    if not skills.exists():
        raise SandboxError(f"Compound Engineering plugin skills directory is missing: {skills}")

    def walk(directory: Path, relative_dir: PurePosixPath) -> Iterator[tuple[PurePosixPath, Path]]:
        try:
            with os.scandir(directory) as scanned:
                entries = sorted(scanned, key=lambda entry: entry.name)
        except OSError as exc:
            raise SandboxError(f"Compound Engineering plugin directory is unreadable: {directory}: {exc}") from exc
        for entry in entries:
            relative = relative_dir / entry.name
            if _is_forbidden_plugin_path(relative):
                continue
            try:
                metadata = entry.stat(follow_symlinks=False)
            except OSError as exc:
                raise SandboxError(f"Compound Engineering plugin entry is unreadable: {entry.path}: {exc}") from exc
            if stat.S_ISLNK(metadata.st_mode):
                raise SandboxError(f"Compound Engineering plugin entries must not be symlinks: {entry.path}")
            if stat.S_ISDIR(metadata.st_mode):
                yield from walk(Path(entry.path), relative)
            elif stat.S_ISREG(metadata.st_mode):
                yield relative, Path(entry.path)
            else:
                raise SandboxError(f"Compound Engineering plugin entries must be regular files: {entry.path}")

    for name in _ALLOWED_PLUGIN_DIRS:
        directory = source / name
        if not directory.exists():
            continue
        try:
            metadata = directory.lstat()
        except OSError as exc:
            raise SandboxError(f"Compound Engineering plugin component is unreadable: {directory}: {exc}") from exc
        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
            raise SandboxError(f"Compound Engineering plugin component must be a real directory: {directory}")
        yield from walk(directory, PurePosixPath(name))

View on GitHub (pinned to d540b00184)

Solutions

  1. Find symlinks: `find <plugin_dir>/{skills,scripts,assets} -type l`.
  2. Dereference them in place: `cp -rL --remove-destination <plugin_dir> <plugin_dir_real>` (then validate).
  3. Re-tar the plugin with `tar -czh` (dereference at archive time) and re-extract.
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def no_symlinks_in_plugin(source: Path) -> bool:
    for name in ("skills", "scripts", "assets"):
        d = source / name
        if not d.exists():
            continue
        for p in d.rglob("*"):
            if p.is_symlink():
                return False
    return True

Try / catch

try:
    list(_plugin_files(source))
except SandboxError as exc:
    if "must not be symlinks" in str(exc):
        # dereference: cp -rL into a clean directory
        ...
    raise

Prevention

When it happens

Trigger: Any file (or directory) inside skills/, scripts/, or assets/ is a symbolic link — e.g., scripts/postinstall.sh -> /usr/local/bin/foo, or skills/ce-plan -> ../shared/ce-plan.

Common situations: Plugin shipped from a monorepo that used symlinks for shared code; operator symlinked for convenience; build tool that created symlinks for assets.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/8b3a183b529e2bfb. Report an issue: GitHub.