{"record":{"id":"150ae1ee7ac1a3ce","repo":"abhigyanpatwari/GitNexus","slug":"label-changed-while-opening-path","errorCode":null,"errorMessage":"{label} changed while opening: {path}","messagePattern":"(.+?) changed while opening: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/evolution.py","lineNumber":102,"sourceCode":"\n\ndef _bounded_regular_bytes(path: Path, *, limit: int, label: str) -> bytes:\n    \"\"\"Read one bounded regular file without following its leaf link.\"\"\"\n\n    try:\n        before = path.lstat()\n    except OSError as exc:\n        raise ValueError(f\"{label} is unreadable: {path}: {exc}\") from exc\n    if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):\n        raise ValueError(f\"{label} must be a regular non-symlink file: {path}\")\n    if before.st_size > limit:\n        raise ValueError(f\"{label} exceeds the bounded evidence limit\")\n\n    descriptor = os.open(path, os.O_RDONLY | getattr(os, \"O_NOFOLLOW\", 0))\n    try:\n        opened = os.fstat(descriptor)\n        if not stat.S_ISREG(opened.st_mode) or opened.st_dev != before.st_dev or opened.st_ino != before.st_ino:\n            raise ValueError(f\"{label} changed while opening: {path}\")\n        chunks: list[bytes] = []\n        remaining = limit + 1\n        while remaining > 0:\n            chunk = os.read(descriptor, min(64 * 1024, remaining))\n            if not chunk:\n                break\n            chunks.append(chunk)\n            remaining -= len(chunk)\n        content = b\"\".join(chunks)\n        if len(content) > limit:\n            raise ValueError(f\"{label} exceeds the bounded evidence limit\")\n        after = os.fstat(descriptor)\n        if (\n            opened.st_dev,\n            opened.st_ino,\n            opened.st_size,\n            opened.st_mtime_ns,\n        ) != (","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/evolution.py#L84-L120","documentation":"Thrown by _bounded_regular_bytes() in eval/workflow_bench/evolution.py as a TOCTOU defense: after lstat() it opens the file with O_NOFOLLOW, then fstat()s the descriptor and compares st_dev and st_ino against the lstat result. If they differ, someone replaced the file (symlink swap, rename race) between lstat and open, and the reader refuses to return bytes that may now point somewhere unexpected.","triggerScenarios":"An attacker or buggy concurrent writer replaces the file at the path between the lstat() check and the os.open() call (classic TOCTOU): e.g. swap a regular file for a symlink to /etc/passwd, or rename another file over the path. The descriptor's fstat dev/ino no longer match the pre-open lstat.","commonSituations":"Untrusted candidate directories trying to escape the sandbox via a race; a concurrent build process rewriting files mid-read; CI artifact directory being actively written when the bench starts; malicious overlay attempting to substitute privileged content.","solutions":["Freeze the overlay before reading: copy with `cp -rL` to a private location and `chmod -R a-w` so nothing can mutate it.","Run the bench in an isolated sandbox with no other writers having access to the path.","Treat this error as a security signal — investigate provenance and reject the candidate.","Retry against a stable snapshot; persistent races indicate a buggy generator that must be fixed."],"exampleFix":"# before: overlay mutated between lstat and open\n# ValueError: candidate overlay file changed while opening: /tmp/ov/x.yaml\n\n# after: stage an immutable snapshot first\ncp -rL /volatile/overlay /tmp/ov && chmod -R a-w /tmp/ov\ncandidate_overlay_payload(Path('/tmp/ov'))","handlingStrategy":"validation","validationCode":"# Prevent the race: stage an immutable snapshot before the bench reads it.\nimport subprocess, shutil\ndef freeze_overlay(src: Path, dst: Path) -> Path:\n    if dst.exists(): shutil.rmtree(dst)\n    subprocess.run(['cp', '-rL', str(src), str(dst)], check=True)\n    subprocess.run(['chmod', '-R', 'a-w', str(dst)], check=True)\n    return dst\n# overlay = freeze_overlay(original, Path('/tmp/ov-frozen'))","typeGuard":"def is_toctu_error(exc: ValueError) -> bool:\n    return 'changed while opening' in str(exc)","tryCatchPattern":"try:\n    candidate_overlay_payload(overlay)\nexcept ValueError as e:\n    if is_toctu_error(e):\n        overlay = freeze_overlay(original_overlay, Path('/tmp/ov-frozen'))\n        candidate_overlay_payload(overlay)  # retry against immutable copy\n    else:\n        raise","preventionTips":["Stage overlays as immutable copies (cp -rL then chmod -R a-w) before the run.","Run the bench against a private directory no other process can write.","Treat this error as a security signal when the source is untrusted — investigate provenance."],"tags":["workflow-bench","validation","security","toctou","sandbox","evolution"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}