{"record":{"id":"e0bb1c0529471c7e","repo":"home-assistant/core","slug":"invalid-filename-suggested-filename","errorCode":null,"errorMessage":"Invalid filename: {suggested_filename}","messagePattern":"Invalid filename: (.+?)","errorType":"exception","errorClass":"InvalidBackupFilename","httpStatus":null,"severity":"error","filePath":"homeassistant/components/backup/manager.py","lineNumber":1015,"sourceCode":"        finally:\n            self.async_on_backup_event(IdleEvent())\n\n    async def _async_receive_backup(\n        self,\n        *,\n        agent_ids: list[str],\n        contents: aiohttp.BodyPartReader,\n    ) -> str:\n        \"\"\"Receive and store a backup file from upload.\"\"\"\n        contents.chunk_size = BUF_SIZE\n        suggested_filename = contents.filename or \"backup.tar\"\n        safe_filename = PureWindowsPath(suggested_filename).name\n        if (\n            not safe_filename\n            or safe_filename != suggested_filename\n            or safe_filename == \"..\"\n        ):\n            raise InvalidBackupFilename(f\"Invalid filename: {suggested_filename}\")\n        self.async_on_backup_event(\n            ReceiveBackupEvent(\n                reason=None,\n                stage=ReceiveBackupStage.RECEIVE_FILE,\n                state=ReceiveBackupState.IN_PROGRESS,\n            )\n        )\n        written_backup = await self._reader_writer.async_receive_backup(\n            agent_ids=agent_ids,\n            stream=contents,\n            suggested_filename=suggested_filename,\n        )\n        self.async_on_backup_event(\n            ReceiveBackupEvent(\n                reason=None,\n                stage=ReceiveBackupStage.UPLOAD_TO_AGENTS,\n                state=ReceiveBackupState.IN_PROGRESS,\n            )","sourceCodeStart":997,"sourceCodeEnd":1033,"githubUrl":"https://github.com/home-assistant/core/blob/58a3fdb3ea0538617f0a07efcfba6294de64fd59/homeassistant/components/backup/manager.py#L997-L1033","documentation":"Raised by BackupManager._async_receive_backup while validating the uploaded multipart file's filename. suggested_filename defaults to 'backup.tar' when absent; it is sanitized through PureWindowsPath(...).name and rejected if that is empty, differs from the original (meaning it contained path separators like '/' or '\\\\' or a drive component), or equals '..'. This blocks path-traversal via the upload filename.","triggerScenarios":"POSTing a multipart upload whose filename field contains 'sub/dir/backup.tar', '..\\\\..\\\\evil.tar', 'C:\\\\backup.tar', is empty-but-not-None, or resolves to '..' after PureWindowsPath normalization.","commonSituations":"Custom API clients sending full paths as filename; curl/scripts reusing a file path string as filename; deliberately malicious requests probing the upload endpoint.","solutions":["Send a plain base filename in the multipart Content-Disposition: filename=\"backup.tar\" with no directories or drive letters.","In client code, use Path(filepath).name (or basename) when constructing the multipart field from a full path.","Retry the upload with the corrected filename; nothing else about the request needs to change."],"exampleFix":"# before (python client)\nfiles = {\"file\": (str(full_path), fh)}  # sends '/home/user/backups/x.tar'\n\n# after\nfrom pathlib import Path\nfiles = {\"file\": (Path(full_path).name, fh)}  # sends 'x.tar'","handlingStrategy":"validation","validationCode":"from pathlib import PurePath\n\ndef valid_upload_filename(name: str | None) -> bool:\n    if name is None:\n        return True  # defaults to backup.tar\n    return bool(name) and PurePath(name).name == name and name not in (\"..\", \".\")","typeGuard":null,"tryCatchPattern":"from homeassistant.components.backup.manager import InvalidBackupFilename\n\ntry:\n    backup_id = await manager.async_receive_backup(\n        agent_ids=agent_ids, contents=contents\n    )\nexcept InvalidBackupFilename as err:\n    return web.Response(status=400, text=str(err))","preventionTips":["Always send Path(full_path).name as the multipart filename.","Reject/normalize filenames containing '/', '\\\\' or '..' client-side.","Default to a plain 'backup.tar' name when the source name is untrusted."],"tags":["backup","upload","validation","path-traversal"],"backgroundTag":null,"analyzedSha":"58a3fdb3ea0538617f0a07efcfba6294de64fd59","analyzedAt":"2026-08-14T20:54:38.818Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}