{"record":{"id":"3ac4d5bde2b5f0db","repo":"invoke-ai/InvokeAI","slug":"only-relative-download-paths-accepted","errorCode":null,"errorMessage":"only relative download paths accepted","messagePattern":"only relative download paths accepted","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"invokeai/app/services/download/download_default.py","lineNumber":218,"sourceCode":"        on_complete: Optional[DownloadEventHandler] = None,\n        on_cancelled: Optional[DownloadEventHandler] = None,\n        on_error: Optional[DownloadExceptionHandler] = None,\n    ) -> MultiFileDownloadJob:\n        dest_root = dest.resolve()\n        mfdj = MultiFileDownloadJob(dest=dest_root, id=self._next_id())\n        mfdj.set_callbacks(\n            on_start=on_start,\n            on_progress=on_progress,\n            on_complete=on_complete,\n            on_cancelled=on_cancelled,\n            on_error=on_error,\n        )\n\n        for part in parts:\n            url = part.url\n            path = (dest_root / part.path).resolve()\n            if not path.is_relative_to(dest_root):\n                raise ValueError(\"only relative download paths accepted\")\n            job = DownloadJob(\n                source=url,\n                dest=path,\n                access_token=access_token or self._lookup_access_token(url),\n            )\n            job.id = self._next_id()  # pre-assign ID so _download_part2parent can be keyed by ID\n            if part.size and part.size > 0:\n                job.total_bytes = part.size\n                job.expected_total_bytes = part.size\n            job.canonical_url = str(url)\n            mfdj.download_parts.add(job)\n            self._download_part2parent[job.id] = mfdj\n        if submit_job:\n            self.submit_multifile_download(mfdj)\n        return mfdj\n\n    def submit_multifile_download(self, job: MultiFileDownloadJob) -> None:\n        pending = sorted(job.download_parts, key=lambda j: str(j.source))","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/services/download/download_default.py#L200-L236","documentation":"multifile_download() resolves each part path against dest_root and rejects any resolved path that escapes dest_root via is_relative_to(). This prevents path traversal — a malicious or malformed part.path like '../../etc/passwd' from writing outside the intended destination.","triggerScenarios":"A multifile download whose part.path contains '..' segments or an absolute path that resolves outside dest_root, e.g. part.path = '/abs/dir' or 'a/../../b'.","commonSituations":"Downloading from untrusted model indexes/manifests with hostile relative paths, misconfigured dest roots, or symlinked destinations that resolve outside dest_root.","solutions":["Inspect part.url/part.path entries and remove '..' or absolute path components from the manifest","Ensure every part.path is a plain relative subpath under the intended destination","Check for symlinks in dest_root causing resolve() to land outside it; use a canonical dest_root","Only download multifile sets from trusted sources"],"exampleFix":"// before (manifest part)\n{\"url\": \"https://x/f\", \"path\": \"../../../etc/evil\"}\n// after\n\n{\"url\": \"https://x/f\", \"path\": \"models/evil.bin\"}","handlingStrategy":"validation","validationCode":"from pathlib import Path\ndef part_path_is_safe(dest_root: Path, part_path: str) -> bool:\n    root = dest_root.resolve()\n    return (root / part_path).resolve().is_relative_to(root)\n# pre-check every part before calling multifile_download","typeGuard":"def is_safe_relative_path(p: str) -> bool:\n    return not p.startswith(\"/\") and \"..\" not in Path(p).parts","tryCatchPattern":"try:\n    service.multifile_download(parts, dest_root)\nexcept ValueError as e:\n    if \"relative download paths\" in str(e):\n        logger.error(\"manifest contains unsafe part path — refusing download\")\n    else:\n        raise","preventionTips":["Reject manifests with '..' or absolute part paths before download","Only download multifile sets from trusted sources","Sanitize part paths with a normalization function up front","Keep dest_root canonical (no symlinks pointing outside)"],"tags":["security","path-traversal","download"],"backgroundTag":"path-traversal-blocked","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}