{"record":{"id":"09348a269ae6c355","repo":"abhigyanpatwari/GitNexus","slug":"dependency-source-must-stay-inside-the-repository","errorCode":null,"errorMessage":"dependency source must stay inside the repository: {source_path}","messagePattern":"dependency source must stay inside the repository: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/task_assets.py","lineNumber":580,"sourceCode":"    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 (\n            not isinstance(item, Mapping)\n            or set(item) != {\"source\", \"target\"}\n            or not all(isinstance(item[field], str) and item[field] for field in (\"source\", \"target\"))\n        ):\n            raise SandboxError(\"sandbox_dependencies entries require only nonblank source and target\")\n        source = str(item[\"source\"])\n        target = str(item[\"target\"])\n        source_path = PurePosixPath(source)\n        target_path = PurePosixPath(target)\n        if source_path.is_absolute() or \"..\" in source_path.parts or not source_path.parts:\n            raise SandboxError(f\"dependency source must stay inside the repository: {source_path}\")\n        if target_path.is_absolute() or \"..\" in target_path.parts or not target_path.parts:\n            raise SandboxError(f\"dependency target must stay inside the clone: {target_path}\")\n        _validate_manifest_path(source_path)\n        _validate_manifest_path(target_path)\n        declarations.append(\n            _DependencyDeclaration(\n                source=source,\n                target=target,\n                source_path=source_path,\n                target_path=target_path,\n            )\n        )\n    for index, declaration in enumerate(declarations):\n        for other in declarations[index + 1 :]:\n            if (\n                declaration.target_path == other.target_path\n                or declaration.target_path in other.target_path.parents\n                or other.target_path in declaration.target_path.parents","sourceCodeStart":562,"sourceCodeEnd":598,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/task_assets.py#L562-L598","documentation":"Raised by _sandbox_dependency_declarations when a dependency's `source` path is absolute (starts with '/'), contains a '..' component, or is empty (no parts after PurePosixPath parsing). The source must stay inside the repository because it is opened relative to the repo descriptor via _open_relative — an escaping source would read bytes outside the declared repo identity, breaking snapshot determinism and the security boundary. This is the dependency analogue of the sandbox_copy repository-relative guard.","triggerScenarios":"A dependency source like `/abs/path`, `../outside`, `node_modules/../../etc`, or `''`. The check is `source_path.is_absolute() or '..' in source_path.parts or not source_path.parts`.","commonSituations":"Author pastes an absolute path from outside the repo. A templating layer prefixes '/'. A relative source that walks above the repo root with '..'. Confusing source (repo-relative) with target (clone-relative) and using an absolute path for source.","solutions":["Rewrite each dependency source as a clean repo-relative path with no '..': `node_modules`, `vendor/lib`, not `/usr/lib/node_modules` or `../shared`.","Compute sources via `os.path.relpath(path, repo_root)` at authoring time and reject results starting with '..'.","If bytes outside the repo are genuinely needed, copy them into the repo (or a worktree) first so the source is inside.","Validate with the provided path guard before prepare() (see validationCode)."],"exampleFix":"// before\n{\"sandbox_dependencies\": [\n  {\"source\": \"/usr/local/lib/node_modules\", \"target\": \"node_modules\"}\n]}\n\n// after\n{\"sandbox_dependencies\": [\n  {\"source\": \"node_modules\", \"target\": \"node_modules\"}\n]}","handlingStrategy":"type-guard","validationCode":"from pathlib import PurePosixPath\n\ndef validate_dependency_sources_relative(task: dict) -> None:\n    for d in task.get(\"sandbox_dependencies\", []):\n        sp = PurePosixPath(d[\"source\"])\n        if sp.is_absolute() or \"..\" in sp.parts or not sp.parts:\n            raise ValueError(f\"dependency source must stay inside repo: {sp}\")\n\nvalidate_dependency_sources_relative(task)","typeGuard":"from pathlib import PurePosixPath\n\ndef dependency_source_is_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 \"source must stay inside the repository\" in str(exc):\n        # rewrite the source as a repo-relative path\n        raise\n    raise","preventionTips":["Author dependency sources relative to the repo root with no '..'.","Remember source is repo-relative, target is clone-relative — do not mix them.","If bytes outside the repo are needed, copy them into the repo first."],"tags":["sandbox","config","validation","dependencies","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"}