{"record":{"id":"00954a1a5e2a5a61","repo":"abhigyanpatwari/GitNexus","slug":"sandbox-copy-must-not-traverse-a-symlink-relativ","errorCode":null,"errorMessage":"sandbox_copy must not traverse a symlink: {relative}","messagePattern":"sandbox_copy must not traverse a symlink: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"critical","filePath":"eval/workflow_bench/task_assets.py","lineNumber":630,"sourceCode":"        return current\n    except BaseException:\n        os.close(current)\n        raise\n\n\ndef _open_child(\n    parent_descriptor: int,\n    name: str,\n    relative: PurePosixPath,\n    *,\n    require_directory: bool = False,\n) -> int:\n    try:\n        metadata = os.stat(name, dir_fd=parent_descriptor, follow_symlinks=False)\n    except OSError as exc:\n        raise SandboxError(f\"sandbox_copy path is unavailable: {relative}: {exc}\") from exc\n    if stat.S_ISLNK(metadata.st_mode):\n        raise SandboxError(f\"sandbox_copy must not traverse a symlink: {relative}\")\n    if require_directory and not stat.S_ISDIR(metadata.st_mode):\n        raise SandboxError(f\"sandbox_copy parent must be a directory: {relative}\")\n    if not (stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode)):\n        raise SandboxError(f\"sandbox_copy accepts only regular files and directories: {relative}\")\n    flags = os.O_RDONLY | getattr(os, \"O_CLOEXEC\", 0) | getattr(os, \"O_NOFOLLOW\", 0)\n    if stat.S_ISDIR(metadata.st_mode):\n        flags |= os.O_DIRECTORY\n    else:\n        flags |= getattr(os, \"O_NONBLOCK\", 0)\n    try:\n        descriptor = os.open(name, flags, dir_fd=parent_descriptor)\n    except OSError as exc:\n        raise SandboxError(f\"sandbox_copy path changed or is unreadable: {relative}: {exc}\") from exc\n    opened = os.fstat(descriptor)\n    if not (stat.S_ISDIR(opened.st_mode) or stat.S_ISREG(opened.st_mode)):\n        os.close(descriptor)\n        raise SandboxError(f\"sandbox_copy accepts only regular files and directories: {relative}\")\n    if (","sourceCodeStart":612,"sourceCodeEnd":648,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/task_assets.py#L612-L648","documentation":"Raised by _open_child when the pre-open os.stat shows S_ISLNK on any path component of a declared sandbox_copy path. The snapshot walker never crosses symlinks because a symlink could redirect capture outside the repo root, breaking the containment contract that SandboxError guards.","triggerScenarios":"A sandbox_copy declaration (or one of its parent directories) is a symlink in the worktree at the resolved SHA; e.g. a top-level convenience link like build -> dist/build, or an installed package alias.","commonSituations":"Monorepos with cross-package symlinks; packages that create relative symlinks during install; a task pin captured after npm install wrote symlinks into the copied tree.","solutions":["Resolve the symlink target and declare the real path it points to","Remove the symlink from the worktree at the captured SHA, or exclude that subtree from the declaration","If the linked content is genuinely needed, materialize it as a regular file/dir before capture"],"exampleFix":"// before (declaration points at a symlink)\n//   sandbox_copy:\n//     - src/build        # 'build' is a symlink -> 'dist/build'\n// after\n//   sandbox_copy:\n//     - src/dist/build   # the real directory","handlingStrategy":"validation","validationCode":"import os, stat\nfrom pathlib import Path\n\ndef find_symlinks_under(root: Path, declared: list[str]) -> list[str]:\n    hits = []\n    for decl in declared:\n        base = root / decl\n        if base.is_symlink():\n            hits.append(str(decl)); continue\n        cur = base.parent\n        parts = list(Path(decl).parts)\n        walker = root\n        for part in parts:\n            try:\n                if os.path.islink(walker / part):\n                    hits.append(f\"{decl} (symlink at {walker/part})\"); break\n            except OSError:\n                break\n            walker = walker / part\n    return hits\n# abort prepare if hits is non-empty","typeGuard":null,"tryCatchPattern":"from eval.workflow_bench.proposer_sandbox import SandboxError\n\ntry:\n    snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)\nexcept SandboxError as exc:\n    if \"must not traverse a symlink\" in str(exc):\n        raise SystemExit(f\"declaration crosses a symlink; resolve the real path: {exc}\") from exc\n    raise","preventionTips":["Resolve all declaration paths to their real (non-symlink) targets before capture","Avoid declaring installed-package directories that contain symlinks (e.g. node_modules/.bin)","Run `find <repo> -type l` over each declared root before preparing the snapshot"],"tags":["sandbox","sandbox-copy","symlinks","security"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}