{"record":{"id":"0c954a866dcd9a8f","repo":"nexu-io/open-design","slug":"path-traversal-detected-in-input-images-for-job-j","errorCode":null,"errorMessage":"path traversal detected in input_images for job {job.get('id')}","messagePattern":"path traversal detected in input_images for job (.+?)","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/hatch-pet/scripts/generate_pet_images.py","lineNumber":219,"sourceCode":"    manifest[\"canonical_identity_reference\"] = reference\n    request_path = run_dir / \"pet_request.json\"\n    if request_path.exists():\n        request = json.loads(request_path.read_text(encoding=\"utf-8\"))\n        request[\"canonical_identity_reference\"] = reference\n        request_path.write_text(json.dumps(request, indent=2) + \"\\n\", encoding=\"utf-8\")\n\n\ndef path_list(run_dir: Path, job: dict[str, object]) -> list[Path]:\n    inputs = job.get(\"input_images\")\n    if not isinstance(inputs, list):\n        raise SystemExit(f\"job {job.get('id')} has invalid input_images\")\n    paths = []\n    for item in inputs:\n        if not isinstance(item, dict) or not isinstance(item.get(\"path\"), str):\n            raise SystemExit(f\"job {job.get('id')} has invalid input image entry\")\n        path = (run_dir / item[\"path\"]).resolve()\n        if not path.is_relative_to(run_dir):\n            raise SystemExit(f\"path traversal detected in input_images for job {job.get('id')}\")\n        if not path.is_file():\n            raise SystemExit(f\"input image for job {job.get('id')} not found: {path}\")\n        paths.append(path)\n    return paths\n\n\ndef main() -> None:\n    parser = argparse.ArgumentParser(description=__doc__)\n    parser.add_argument(\"--run-dir\", required=True)\n    parser.add_argument(\"--model\", default=\"gpt-image-2\")\n    parser.add_argument(\"--size\", default=\"1024x1024\")\n    parser.add_argument(\"--states\", default=\"all\")\n    parser.add_argument(\"--job-id\", action=\"append\", default=[])\n    parser.add_argument(\"--skip-base\", action=\"store_true\")\n    args = parser.parse_args()\n\n    api_key = os.environ.get(\"OPENAI_API_KEY\")\n    if not api_key:","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/hatch-pet/scripts/generate_pet_images.py#L201-L237","documentation":"Raised by path_list after resolving an input_images path: the resolved absolute path does not stay inside run_dir (Path.is_relative_to(run_dir) is False). This is a deliberate path-traversal guard so a manifest cannot reference files outside the run directory.","triggerScenarios":"An input_images entry's path contains ../ that escapes run_dir, or is an absolute path pointing elsewhere on disk (e.g. /etc/passwd or /home/user/secret.png).","commonSituations":"Hand-editing input_images to point at convenience absolute paths; symlinks inside run_dir that resolve outside it; a manifest copied from another machine whose relative paths now resolve differently.","solutions":["Keep every input_images path relative to run_dir and free of ../ escapes.","Copy any needed external image into run_dir/references first, then reference it by relative path.","Avoid symlinks inside run_dir that point outside it; replace them with real files.","Regenerate the run dir with prepare_pet_run.py, which only writes in-run relative paths."],"exampleFix":"// before (manifest)\n\"input_images\": [\n  {\"path\": \"/home/user/photos/secret.png\", \"role\": \"reference\"}\n]\n\n// after - copy the file into the run dir and reference relatively\n\"input_images\": [\n  {\"path\": \"references/reference-01.png\", \"role\": \"pet reference\"}\n]","handlingStrategy":"validation","validationCode":"def safe_join(run_dir: Path, relative: str) -> Path:\n    candidate = (run_dir / relative).resolve()\n    if not candidate.is_relative_to(run_dir):\n        raise SystemExit(f\"path traversal detected: {relative}\")\n    return candidate","typeGuard":"def is_in_run_dir(run_dir: Path, relative: str) -> bool:\n    return (run_dir / relative).resolve().is_relative_to(run_dir)","tryCatchPattern":null,"preventionTips":["Never put absolute paths or ../ in manifest path fields; keep everything relative to run_dir.","Avoid symlinks inside run_dir that point outside it.","Regenerate the manifest rather than editing paths by hand."],"tags":["security","path-traversal","manifest","validation","system-exit"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}