{"record":{"id":"d2a3a41a1be02253","repo":"abhigyanpatwari/GitNexus","slug":"dependency-symlink-must-be-a-bounded-relative-link","errorCode":null,"errorMessage":"dependency symlink must be a bounded relative link: {relative}","messagePattern":"dependency symlink must be a bounded relative link: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/task_assets.py","lineNumber":474,"sourceCode":"                sha256=digest.hexdigest(),\n                mode=captured_mode,\n            )\n        )\n\n    def _copy_symlink(\n        self,\n        parent_descriptor: int,\n        name: str,\n        relative: PurePosixPath,\n        before: os.stat_result,\n    ) -> None:\n        try:\n            target = os.readlink(name, dir_fd=parent_descriptor)\n            target_bytes = target.encode(\"utf-8\")\n        except (OSError, UnicodeEncodeError) as exc:\n            raise SandboxError(f\"dependency symlink is unreadable or not UTF-8: {relative}\") from exc\n        if not target or PurePosixPath(target).is_absolute() or \"\\x00\" in target:\n            raise SandboxError(f\"dependency symlink must be a bounded relative link: {relative}\")\n        if len(target_bytes) > MAX_TASK_ASSET_PATH_BYTES:\n            raise SandboxError(f\"dependency symlink target exceeds the path limit: {relative}\")\n        if self.budget.total_bytes + len(target_bytes) > MAX_TASK_ASSET_BYTES:\n            raise SandboxError(\"sandbox_copy exceeds the total byte limit\")\n        destination = self.destination / Path(*relative.parts)\n        os.symlink(target, destination)\n        after = os.stat(name, dir_fd=parent_descriptor, follow_symlinks=False)\n        if (\n            _mutation_identity(before) != _mutation_identity(after)\n            or os.readlink(\n                name,\n                dir_fd=parent_descriptor,\n            )\n            != target\n        ):\n            raise SandboxError(f\"dependency symlink changed while snapshotting: {relative}\")\n        self.total_bytes += len(target_bytes)\n        self.budget.total_bytes += len(target_bytes)","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/task_assets.py#L456-L492","documentation":"Raised by _copy_symlink when the readlink target is empty, is an absolute path (starts with '/'), or contains a NUL byte. The snapshot only stores bounded relative links because absolute links would escape the sandbox workspace at mount time and the snapshot must be reproducible independent of the host's absolute layout. This is a security-containment guard as much as a consistency one.","triggerScenarios":"A sandbox_dependency source tree contains a symlink pointing to an absolute path (e.g. `/usr/lib/node_modules/foo`), a symlink created with `ln -s '' ...` (empty target), or a malformed link whose target string embeds a NUL byte. Common with system-installed npm packages that symlink into global directories.","commonSituations":"node_modules created by a system package manager (apt/brew) that links into /usr/lib. A developer hand-created symlink with an absolute target for convenience. Symlinks pointing to /tmp or /home during local dev that got committed or vendored.","solutions":["Recreate offending symlinks as relative links: `ln -sfn ../<package> node_modules/<link>` using a path relative to the link's own directory.","If using npm/pnpm/yarn, reinstall dependencies inside the repo so the package manager writes relative links: `rm -rf node_modules && npm install`.","Audit with: `find <dep-source> -type l -exec sh -c 'readlink \"$1\" | grep -q ^/ && echo \"$1\"' _ {} \\;` to list absolute-target links.","For NUL-byte or empty targets, delete and recreate the link cleanly."],"exampleFix":"# before\nnode_modules/.foo -> /usr/lib/node_modules/foo\n\n# after\nrm node_modules/.foo\nln -s ../foo node_modules/.foo   # relative, bounded","handlingStrategy":"validation","validationCode":"from pathlib import Path, PurePosixPath\nimport os\n\ndef validate_symlinks_bounded_relative(dep_source: Path) -> None:\n    for current, dirs, files in os.walk(dep_source, followlinks=False):\n        for name in dirs + files:\n            p = Path(current) / name\n            if p.is_symlink():\n                target = os.readlink(p)\n                if not target or PurePosixPath(target).is_absolute() or \"\\x00\" in target:\n                    raise ValueError(f\"symlink must be bounded relative: {p} -> {target!r}\")\n\nfor d in task.get(\"sandbox_dependencies\", []):\n    validate_symlinks_bounded_relative(repo_path / d[\"source\"])","typeGuard":"from pathlib import PurePosixPath\nimport os\nfrom pathlib import Path\n\ndef symlink_target_is_bounded_relative(p: Path) -> bool:\n    try:\n        t = os.readlink(p)\n    except OSError:\n        return False\n    return bool(t) and not PurePosixPath(t).is_absolute() and \"\\x00\" not in t","tryCatchPattern":"from eval.workflow_bench.propposer_sandbox import SandboxError\n\ntry:\n    snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)\nexcept SandboxError as exc:\n    if \"bounded relative link\" in str(exc):\n        # recreate offending links as relative, then re-run\n        raise\n    raise","preventionTips":["Install dependencies inside the repo so package managers write relative links.","Audit with `find <dep-source> -type l -exec sh -c 'readlink \"$1\" | grep -q ^/ && echo \"$1\"' _ {} \\;`.","Never hand-create absolute symlinks inside dependency sources."],"tags":["sandbox","symlink","dependencies","security","path-traversal"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}