{"record":{"id":"da9b02a02e2b15f5","repo":"abhigyanpatwari/GitNexus","slug":"label-must-not-traverse-symlinks-lexical","errorCode":null,"errorMessage":"{label} must not traverse symlinks: {lexical}","messagePattern":"(.+?) must not traverse symlinks: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/proposer_sandbox.py","lineNumber":557,"sourceCode":"    _resolve_executable(None, \"socat\")\n\n\ndef _real_directory(path: Path, *, label: str) -> Path:\n    \"\"\"Return an absolute directory path without accepting any symlink hop.\"\"\"\n\n    lexical = path.expanduser().absolute()\n    try:\n        mode = lexical.lstat().st_mode\n    except OSError as exc:\n        raise SandboxError(f\"{label} must be a real directory: {lexical}: {exc}\") from exc\n    if stat.S_ISLNK(mode) or not stat.S_ISDIR(mode):\n        raise SandboxError(f\"{label} must be a real directory: {lexical}\")\n    try:\n        resolved = lexical.resolve(strict=True)\n    except OSError as exc:\n        raise SandboxError(f\"{label} must be a real directory: {lexical}: {exc}\") from exc\n    if resolved != lexical:\n        raise SandboxError(f\"{label} must not traverse symlinks: {lexical}\")\n    return lexical\n\n\ndef _safe_repo_source(repo: Path, relative: str, *, label: str) -> tuple[Path, Path]:\n    candidate = PurePosixPath(relative)\n    if candidate.is_absolute() or \"..\" in candidate.parts or not candidate.parts:\n        raise SandboxError(f\"{label} must be a repository-relative path: {relative!r}\")\n    lexical = repo / Path(*candidate.parts)\n    resolved = lexical.resolve()\n    try:\n        resolved.relative_to(repo)\n    except ValueError as exc:\n        raise SandboxError(f\"{label} escapes its allowed repository root: {relative}\") from exc\n    if not resolved.exists():\n        raise SandboxError(f\"{label} does not exist: {relative}\")\n    return lexical, resolved\n\n","sourceCodeStart":539,"sourceCodeEnd":575,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/proposer_sandbox.py#L539-L575","documentation":"Raised by _real_directory when strict resolution succeeds but resolved != lexical — meaning a symlink somewhere in the path caused the resolved absolute path to differ from the lexical absolute path. The contract requires the path itself be canonical (no symlink hops), so even a valid target reached through a symlink is rejected.","triggerScenarios":"The lexical path is absolute and a real directory, but one of its components (or the leaf) is a symlink, so Path.resolve(strict=True) returns a different absolute path. e.g. lexical /opt/claude where /opt/claude -> /mnt/x, resolved becomes /mnt/x.","commonSituations":"Operator symlinked a directory to another mount for space (/var/lib/foo -> /data/foo); a 'convenience' symlink in /opt or /home/agent; a distro packaged the dir as a symlink to a versioned path; a CI image layered symlinks to share assets; user pointed HOME-ish paths through /usr/local equivalence.","solutions":["Replace the symlink with the real directory at the lexical path: remove the symlink, then move/bind-mount the target into place, or update the config to the resolved canonical path.","If the contract is too strict for your legit setup, point the config value at the resolved canonical path so lexical == resolved.","Use a bind mount (mount --bind) instead of a symlink so the lexical path is the real directory.","Repackage the asset without the symlink indirection."],"exampleFix":"// before\n# /opt/claude -> /mnt/claude-1.2\nreal = _real_directory(Path('/opt/claude'), label='claude root')\n// after\nreal = _real_directory(Path('/mnt/claude-1.2'), label='claude root')","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef is_canonical_dir(p: Path) -> bool:\n    lexical = p.expanduser().absolute()\n    try:\n        resolved = lexical.resolve(strict=True)\n    except OSError:\n        return False\n    return resolved == lexical\n\nassert is_canonical_dir(candidate)","typeGuard":"from pathlib import Path\n\ndef is_canonical_real_directory(value: object) -> bool:\n    if not isinstance(value, Path):\n        return False\n    lexical = value.expanduser().absolute()\n    try:\n        resolved = lexical.resolve(strict=True)\n    except OSError:\n        return False\n    return resolved == lexical","tryCatchPattern":"try:\n    real = _real_directory(path, label=label)\nexcept SandboxError as exc:\n    if 'must not traverse symlinks' in str(exc):\n        canonical = path.resolve(strict=True)\n        raise SystemExit(f'point config at canonical path: {canonical}')\n    raise","preventionTips":["Point configs at canonical paths, not symlinks.","Use bind mounts instead of symlinks.","Periodically scan key directories for symlink components.","Document the no-symlink-hop contract for operators."],"tags":["filesystem","symlink","validation","sandbox","security"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}