{"record":{"id":"f817863b088e6793","repo":"abhigyanpatwari/GitNexus","slug":"overlay-destination-escapes-repository-target","errorCode":null,"errorMessage":"overlay destination escapes repository: {target}","messagePattern":"overlay destination escapes repository: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"critical","filePath":"eval/workflow_bench/promotion_apply.py","lineNumber":193,"sourceCode":"            stat.S_ISLNK(final.st_mode)\n            or not stat.S_ISDIR(opened.st_mode)\n            or not stat.S_ISDIR(final.st_mode)\n            or final_resolved != root\n            or not (identity(metadata) == identity(opened) == identity(final))\n        ):\n            raise ValueError(f\"repository root changed while opening: {root}\")\n    except OSError as exc:\n        os.close(descriptor)\n        raise ValueError(f\"repository root changed while opening: {root}\") from exc\n    except BaseException:\n        os.close(descriptor)\n        raise\n    return root, descriptor\n\n\ndef _open_target_parent(root_descriptor: int, target: PurePosixPath) -> int:\n    if target.is_absolute() or not target.parts or \"..\" in target.parts:\n        raise ValueError(f\"overlay destination escapes repository: {target}\")\n    flags = os.O_RDONLY | os.O_DIRECTORY | getattr(os, \"O_CLOEXEC\", 0) | getattr(os, \"O_NOFOLLOW\", 0)\n    current = os.dup(root_descriptor)\n    try:\n        for part in target.parts[:-1]:\n            try:\n                metadata = os.stat(part, dir_fd=current, follow_symlinks=False)\n            except OSError as exc:\n                raise ValueError(f\"overlay destination parent is unavailable: {target}\") from exc\n            if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):\n                raise ValueError(f\"overlay destination parent must not be a symlink: {target}\")\n            try:\n                child = os.open(part, flags, dir_fd=current)\n            except OSError as exc:\n                raise ValueError(f\"overlay destination parent changed while opening: {target}\") from exc\n            opened = os.fstat(child)\n            if (\n                opened.st_dev,\n                opened.st_ino,","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/promotion_apply.py#L175-L211","documentation":"All errors below are raised by internal helpers of `eval/workflow_bench/promotion_apply.py` and propagate to the caller of the public entry points: `apply_promoted_overlay(overlay, repo_root, *, expected_digest, expected_target_bases)`, `destination_base_digests(overlay, repo_root)`, `committed_destination_base_digests(overlay, repo_root, *, ref)` and `freeze_overlay(overlay, destination)`. The module applies promoted skill overlays across the canonical skill tree plus its shipped mirrors (`gitnexus/skills`, `gitnexus-claude-plugin/skills`) in a TOCTOU-hardened, symlink-rejecting, descriptor-bound transaction. `_open_target_parent` rejects a target immediately when `target.is_absolute() or not target.parts or '..' in target.parts`. Targets derive from `mirror_targets(relative)` built off the overlay payload's relative paths (see `candidate_overlay_payload`). Absolute, empty, or `..`-bearing paths would escape the held repository-root descriptor, so they are refused before any directory walk.","triggerScenarios":"The overlay payload contains an absolute path, an empty relative path, or a path with `..` segments; equivalently the candidate overlay ZIP/tar declared a traversal entry.","commonSituations":"A hand-crafted or corrupted overlay file includes entries like `/etc/passwd`, `../../sibling`, or a bare empty name; a packaging tool wrote absolute member names.","solutions":["Inspect the overlay payload paths: `for r,_ in candidate_overlay_payload(overlay)[1]: print(r)`.","Strip/normalize member names when building the overlay; reject absolute and `..` entries at overlay-authoring time.","Re-create the overlay from a clean source tree using only repo-relative names."],"exampleFix":"// before\n# overlay contains an entry like b'../escape/SKILL.md'\napply_promoted_overlay(overlay, repo_root=root)\n// after\n_, payload = candidate_overlay_payload(overlay)\nbad = [str(r) for r,_ in payload if r.is_absolute() or not r.parts or '..' in r.parts]\nassert not bad, f'overlay has escaping entries: {bad}'\napply_promoted_overlay(overlay, repo_root=root)","handlingStrategy":"validation","validationCode":"def assert_no_escape(payload):\n    for rel, _ in payload:\n        assert not rel.is_absolute() and rel.parts and '..' not in rel.parts, f'escaping path: {rel}'\n_, payload = candidate_overlay_payload(overlay)\nassert_no_escape(payload)\napply_promoted_overlay(overlay, repo_root=root)","typeGuard":"from pathlib import PurePosixPath\ndef overlay_paths_are_safe(payload) -> bool:\n    return all(\n        (not r.is_absolute()) and r.parts and ('..' not in r.parts)\n        for r, _ in payload\n    )","tryCatchPattern":"except ValueError as exc:\n    if 'escapes repository' in str(exc):\n        raise SystemExit(f'refusing overlay with traversal entry: {exc}')","preventionTips":["Author overlays with strictly repo-relative, normalized member names only.","Validate the payload with `overlay_paths_are_safe` before calling the API.","Treat any escaping entry as a security incident, not a retryable error."],"tags":["security","path-traversal","validation","overlay"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}