{"record":{"id":"1b9267d9a1694bef","repo":"abhigyanpatwari/GitNexus","slug":"unsafe-results-artifact-path-relative-value-r","errorCode":null,"errorMessage":"unsafe results artifact path: {relative_value!r}","messagePattern":"unsafe results artifact path: (.+?)","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/evolve.py","lineNumber":310,"sourceCode":"    except OSError as exc:\n        raise SandboxError(f\"results directory is unavailable: {root}: {exc}\") from exc\n    if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):\n        raise SandboxError(f\"results directory must be a real non-symlink directory: {root}\")\n    if root.resolve(strict=True) != root:\n        raise SandboxError(f\"results directory must not traverse symlinks: {root}\")\n    return root\n\n\ndef _results_artifact_path(root: Path, relative_value: str, *, transcript: bool) -> Path:\n    relative = PurePosixPath(relative_value)\n    expected_parts = 2 if transcript else 1\n    if (\n        relative.is_absolute()\n        or len(relative.parts) != expected_parts\n        or any(part in {\"\", \".\", \"..\"} for part in relative.parts)\n        or (transcript and relative.parts[0] != \"transcripts\")\n    ):\n        raise SandboxError(f\"unsafe results artifact path: {relative_value!r}\")\n    current = root\n    for part in relative.parts[:-1]:\n        current /= part\n        try:\n            metadata = current.lstat()\n        except OSError as exc:\n            raise SandboxError(f\"results artifact parent is unavailable: {current}: {exc}\") from exc\n        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):\n            raise SandboxError(f\"results artifact parent must be a real directory: {current}\")\n        if transcript and stat.S_IMODE(metadata.st_mode) & 0o077:\n            raise SandboxError(f\"transcript artifact parent must be owner-only: {current}\")\n    return root / Path(*relative.parts)\n\n\ndef _transcript_artifact_metadata(metadata: Any) -> tuple[str, str, int]:\n    \"\"\"Validate transcript metadata without touching any host path.\"\"\"\n\n    if not isinstance(metadata, dict) or set(metadata) != {\"path\", \"sha256\", \"bytes\", \"source\"}:","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/evolve.py#L292-L328","documentation":"_results_artifact_path enforces a strict shape on each artifact's relative path before any filesystem touch: not absolute, exactly expected_parts (1 for a plain artifact, 2 for a transcript), no empty/./.. components, and transcripts must start with 'transcripts/'. This is the primary path-traversal and layout guard for results.jsonl entries.","triggerScenarios":"A results row carries a path like '../secret', '/etc/passwd', 'a/b/c' (too many parts), '' or '.', or a transcript path that does not start with 'transcripts/'.","commonSituations":"A custom runner emits absolute paths or nested layouts; a hand-edited results.jsonl; a transcript path recorded without the transcripts/ prefix; path-escape attempts in crafted evidence.","solutions":["Inspect the offending row: the message prints relative_value!r; find that exact string in results.jsonl.","Rewrite the row's path to a single component (plain artifact) or 'transcripts/<name>' (transcript).","Ensure the runner that writes results.jsonl normalizes paths to the contract."],"exampleFix":"# before (results.jsonl row)\n{\"path\": \"../hidden/secret\", \"sha256\": \"...\", ...}\n\n# after\n{\"path\": \"secret\", \"sha256\": \"...\", ...}\n# and for transcripts\n{\"path\": \"transcripts/run-42.json\", \"source\": \"parent-captured-stream-json\", ...}","handlingStrategy":"validation","validationCode":"from pathlib import PurePosixPath\n\ndef artifact_path_ok(relative_value: str, *, transcript: bool) -> bool:\n    p = PurePosixPath(relative_value)\n    expected = 2 if transcript else 1\n    return (\n        not p.is_absolute()\n        and len(p.parts) == expected\n        and not any(part in {\"\", \".\", \"..\"} for part in p.parts)\n        and (not transcript or p.parts[0] == \"transcripts\")\n    )","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Normalize every results.jsonl path to the contract before writing.","Never store absolute paths or nested layouts in artifact rows.","Add a unit test that fuzzes traversal strings against the writer."],"tags":["workflow-bench","sandbox","security","path-traversal","validation","evidence"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}