nexu-io/open-design · error · SystemExit

path traversal detected in input_images for job {job.get('id

Error message

path traversal detected in input_images for job {job.get('id')}

What it means

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.

Source

Thrown at skills/hatch-pet/scripts/generate_pet_images.py:219

    manifest["canonical_identity_reference"] = reference
    request_path = run_dir / "pet_request.json"
    if request_path.exists():
        request = json.loads(request_path.read_text(encoding="utf-8"))
        request["canonical_identity_reference"] = reference
        request_path.write_text(json.dumps(request, indent=2) + "\n", encoding="utf-8")


def path_list(run_dir: Path, job: dict[str, object]) -> list[Path]:
    inputs = job.get("input_images")
    if not isinstance(inputs, list):
        raise SystemExit(f"job {job.get('id')} has invalid input_images")
    paths = []
    for item in inputs:
        if not isinstance(item, dict) or not isinstance(item.get("path"), str):
            raise SystemExit(f"job {job.get('id')} has invalid input image entry")
        path = (run_dir / item["path"]).resolve()
        if not path.is_relative_to(run_dir):
            raise SystemExit(f"path traversal detected in input_images for job {job.get('id')}")
        if not path.is_file():
            raise SystemExit(f"input image for job {job.get('id')} not found: {path}")
        paths.append(path)
    return paths


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--run-dir", required=True)
    parser.add_argument("--model", default="gpt-image-2")
    parser.add_argument("--size", default="1024x1024")
    parser.add_argument("--states", default="all")
    parser.add_argument("--job-id", action="append", default=[])
    parser.add_argument("--skip-base", action="store_true")
    args = parser.parse_args()

    api_key = os.environ.get("OPENAI_API_KEY")
    if not api_key:

View on GitHub (pinned to 5be4028344)

Solutions

  1. Keep every input_images path relative to run_dir and free of ../ escapes.
  2. Copy any needed external image into run_dir/references first, then reference it by relative path.
  3. Avoid symlinks inside run_dir that point outside it; replace them with real files.
  4. Regenerate the run dir with prepare_pet_run.py, which only writes in-run relative paths.

Example fix

// before (manifest)
"input_images": [
  {"path": "/home/user/photos/secret.png", "role": "reference"}
]

// after - copy the file into the run dir and reference relatively
"input_images": [
  {"path": "references/reference-01.png", "role": "pet reference"}
]
Defensive patterns

Strategy: validation

Validate before calling

def safe_join(run_dir: Path, relative: str) -> Path:
    candidate = (run_dir / relative).resolve()
    if not candidate.is_relative_to(run_dir):
        raise SystemExit(f"path traversal detected: {relative}")
    return candidate

Type guard

def is_in_run_dir(run_dir: Path, relative: str) -> bool:
    return (run_dir / relative).resolve().is_relative_to(run_dir)

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/0c954a866dcd9a8f. Report an issue: GitHub.