{"record":{"id":"4702f1a8471fee6f","repo":"abhigyanpatwari/GitNexus","slug":"sandbox-copy-must-be-a-repository-relative-path","errorCode":null,"errorMessage":"sandbox_copy must be a repository-relative path: {raw!r}","messagePattern":"sandbox_copy must be a repository-relative path: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/task_assets.py","lineNumber":551,"sourceCode":"\n        self._record_directory(relative)\n\n    def finished_entries(self) -> tuple[AssetManifestEntry, ...]:\n        return tuple(sorted(self.entries.values(), key=lambda entry: entry.path.as_posix()))\n\n\ndef _sandbox_copy_declarations(\n    task: Mapping[str, Any],\n) -> tuple[tuple[str, ...], tuple[PurePosixPath, ...]]:\n    raw_declarations = task.get(\"sandbox_copy\", [])\n    if not isinstance(raw_declarations, list) or not all(isinstance(item, str) and item for item in raw_declarations):\n        raise SandboxError(\"sandbox_copy must be a list of nonblank repository-relative paths\")\n    declarations = tuple(raw_declarations)\n    paths: list[PurePosixPath] = []\n    for raw in declarations:\n        relative = PurePosixPath(raw)\n        if relative.is_absolute() or not relative.parts or \"..\" in relative.parts:\n            raise SandboxError(f\"sandbox_copy must be a repository-relative path: {raw!r}\")\n        _validate_manifest_path(relative)\n        paths.append(relative)\n    for index, path in enumerate(paths):\n        for other in paths[index + 1 :]:\n            if path == other or path in other.parents or other in path.parents:\n                raise SandboxError(f\"sandbox_copy declarations overlap: {path} and {other}\")\n    return declarations, tuple(paths)\n\n\ndef _sandbox_dependency_declarations(\n    task: Mapping[str, Any],\n) -> tuple[_DependencyDeclaration, ...]:\n    raw_declarations = task.get(\"sandbox_dependencies\", [])\n    if not isinstance(raw_declarations, list):\n        raise SandboxError(\"sandbox_dependencies must be a list\")\n    declarations: list[_DependencyDeclaration] = []\n    for item in raw_declarations:\n        if (","sourceCodeStart":533,"sourceCodeEnd":569,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/task_assets.py#L533-L569","documentation":"Raised by _sandbox_copy_declarations for each declared path string that is absolute (starts with '/'), has no parts (empty after PurePosixPath parsing), or contains a '..' component anywhere in its parts. The harness only accepts repository-relative paths that stay within the repo root — this is both a path-traversal security guard and a determinism guard (absolute or escaping paths would capture bytes outside the declared repo identity).","triggerScenarios":"A declaration like `/abs/path`, `../sibling`, `repo/../outside`, `./` (no parts), or `''` that produced an empty PurePosixPath. Also a Windows-style path with a drive letter parsed unexpectedly. The check is per-element: `relative.is_absolute() or not relative.parts or '..' in relative.parts`.","commonSituations":"Author copies an absolute path from an IDE. Relative path that walks up with `..`. A templating bug that prefixes '/'. A path normalization step that collapses `a/../..` to an escape.","solutions":["Rewrite each sandbox_copy entry as a clean repo-relative path with no '..': `src/lib`, `tests/fixtures`, not `/home/user/repo/src` or `../shared/lib`.","Normalize at authoring time with `os.path.relpath(path, repo_root)` and reject results starting with '..'.","Validate with the provided path guard before calling prepare() (see typeGuard).","If a path outside the repo is genuinely needed, copy it into the repo first or declare it as a sandbox_dependency with a proper source."],"exampleFix":"// before\n{\"sandbox_copy\": [\"/home/user/repo/src\", \"../shared/include\"]}\n\n// after\n{\"sandbox_copy\": [\"src\", \"include\"]}","handlingStrategy":"type-guard","validationCode":"from pathlib import PurePosixPath\n\ndef validate_copy_paths_relative(task: dict) -> None:\n    for raw in task.get(\"sandbox_copy\", []):\n        p = PurePosixPath(raw)\n        if p.is_absolute() or not p.parts or \"..\" in p.parts:\n            raise ValueError(f\"sandbox_copy must be repository-relative, got {raw!r}\")\n\nvalidate_copy_paths_relative(task)","typeGuard":"from pathlib import PurePosixPath\n\ndef is_repo_relative_bounded(raw: str) -> bool:\n    p = PurePosixPath(raw)\n    return not p.is_absolute() and bool(p.parts) and \"..\" not in p.parts","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 \"repository-relative path\" in str(exc):\n        # rewrite the offending path to a clean repo-relative form\n        raise\n    raise","preventionTips":["Author paths relative to the repo root with no '..' components.","Normalize with os.path.relpath(path, repo_root) and reject results starting with '..'.","Never paste absolute paths from outside the repo into sandbox_copy."],"tags":["sandbox","config","validation","path-traversal","security","task-declaration"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}