{"record":{"id":"530bec536307b13e","repo":"abhigyanpatwari/GitNexus","slug":"task-asset-snapshot-contains-an-unexpected-symlink","errorCode":null,"errorMessage":"task asset snapshot contains an unexpected symlink: {path}","messagePattern":"task asset snapshot contains an unexpected symlink: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/task_assets.py","lineNumber":993,"sourceCode":"        \"dependency_content_digest\": dependency_content_digest,\n        \"dependency_manifest_digest\": dependency_manifest_digest,\n        \"manifest_digest\": manifest_digest,\n        \"repo_identity\": str(repo_identity),\n        \"resolved_sha\": resolved_sha,\n        \"schema_version\": 2,\n    }\n    return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(\",\", \":\")).encode()).hexdigest()\n\n\ndef _freeze_snapshot(root: Path) -> None:\n    for current, directories, files in os.walk(root, topdown=False, followlinks=False):\n        for name in files:\n            path = Path(current) / name\n            mode = path.lstat().st_mode\n            relative = path.relative_to(root)\n            if stat.S_ISLNK(mode):\n                if not relative.parts or relative.parts[0] != \"dependencies\":\n                    raise SandboxError(f\"task asset snapshot contains an unexpected symlink: {path}\")\n                continue\n            if not stat.S_ISREG(mode):\n                raise SandboxError(f\"task asset snapshot contains a special file: {path}\")\n            path.chmod(0o400 | (0o100 if stat.S_IMODE(mode) & 0o111 else 0))\n        for name in directories:\n            path = Path(current) / name\n            mode = path.lstat().st_mode\n            relative = path.relative_to(root)\n            if stat.S_ISLNK(mode):\n                if not relative.parts or relative.parts[0] != \"dependencies\":\n                    raise SandboxError(f\"task asset snapshot contains an unexpected symlink: {path}\")\n                continue\n            if not stat.S_ISDIR(mode):\n                raise SandboxError(f\"task asset snapshot contains a special directory: {path}\")\n            path.chmod(0o500)\n        Path(current).chmod(0o500)\n\n","sourceCodeStart":975,"sourceCodeEnd":1011,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/task_assets.py#L975-L1011","documentation":"Raised by _freeze_snapshot during the files walk when a symlink is found whose first relative path component is not 'dependencies'. Symlinks are permitted only inside the dependencies/ subtree of the snapshot; anywhere else they are a containment violation because they could escape the snapshot root or alias mutable state.","triggerScenarios":"A sandbox_copy declaration captured a tree that contains a symlink (file or symlinked regular file) outside the dependencies/ directory, and _freeze_snapshot walked it. E.g. a repo symlink like include/foo -> ../shared/foo, or a vendored library with convenience symlinks.","commonSituations":"Capturing a repo with version-manager (asdf/nvm/direnv) symlinks, broken convenience symlinks, or symlinked vendored dependencies not declared via sandbox_dependencies; switching sandbox_copy to a broader root that now includes previously-excluded symlinks.","solutions":["Move the dependency under the dependencies/ subtree and declare it via sandbox_dependencies, which is the only symlink-allowed location.","Resolve or remove the symlink from the captured tree (replace with the real file or a copy) before capture.","Narrow the sandbox_copy declaration so it does not include the offending symlink."],"exampleFix":"# before: symlink lives in a sandbox_copy'd directory\nrepo/include/version.h -> ../shared/version.h   # captured -> freeze fails\n\n# after: declare it as a dependency (symlinks allowed under dependencies/)\nsandbox_dependencies = [{'source': 'shared/version.h', 'target': 'include/version.h'}]","handlingStrategy":"validation","validationCode":"import os, stat\nfrom pathlib import Path\n\ndef assert_no_external_symlinks(root: Path) -> None:\n    for current, dirs, files in os.walk(root, followlinks=False):\n        rel = Path(current).relative_to(root)\n        for name in files + dirs:\n            p = Path(current) / name\n            if stat.S_ISLNK(p.lstat().st_mode):\n                first = rel.parts[0] if rel.parts else (name,)\n                if not rel.parts or rel.parts[0] != 'dependencies':\n                    raise ValueError(f'external symlink would be rejected by freeze: {p}')\n\n# Run before cache.prepare; move offending symlinks under dependencies/ or remove them.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep all symlinked content under the dependencies/ subtree and declare it via sandbox_dependencies.","Resolve or remove convenience symlinks (version managers, vendored aliases) from sandbox_copy'd trees.","Pre-scan captured roots with the validator above so freeze never surprises you."],"tags":["sandbox","symlinks","filesystem","security"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}