{"record":{"id":"f5a19b02fc197c01","repo":"jumpserver/jumpserver","slug":"invalid-zip-file-f5a19b","errorCode":null,"errorMessage":"Invalid zip file: {}","messagePattern":"Invalid zip file: (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"apps/terminal/api/virtualapp/virtualapp.py","lineNumber":49,"sourceCode":"        if extract_to and os.path.exists(extract_to):\n            shutil.rmtree(extract_to)\n\n    def extract_zip_pkg(self):\n        serializer = self.get_serializer(data=self.request.data)\n        serializer.is_valid(raise_exception=True)\n        file = serializer.validated_data['file']\n        save_to = 'virtual_apps/{}'.format(file.name + '.tmp.zip')\n        if default_storage.exists(save_to):\n            default_storage.delete(save_to)\n        rel_path = default_storage.save(save_to, file)\n        path = default_storage.path(rel_path)\n        extract_to = default_storage.path('virtual_apps/{}.tmp'.format(file.name))\n        if os.path.exists(extract_to):\n            shutil.rmtree(extract_to)\n        try:\n            safe_extract_zip(path, extract_to)\n        except RuntimeError as e:\n            raise ValidationError({'error': _('Invalid zip file') + ': {}'.format(e)})\n        tmp_dir = VirtualApp.locate_pkg_root(extract_to, file.name)\n        return tmp_dir, rel_path, extract_to\n\n    @action(detail=False, methods=['post'], serializer_class=FileSerializer)\n    def upload(self, request, *args, **kwargs):\n        rel_path = None\n        extract_to = None\n        try:\n            tmp_dir, rel_path, extract_to = self.extract_zip_pkg()\n            manifest = VirtualApp.validate_pkg(tmp_dir)\n            name = manifest['name']\n            instance = VirtualApp.objects.filter(name=name).first()\n            if instance:\n                return Response({'error': 'virtual app already exists: {}'.format(name)}, status=400)\n\n            app, serializer = VirtualApp.install_from_dir(tmp_dir)\n            return Response(serializer.data, status=201)\n        finally:","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/jumpserver/jumpserver/blob/6ec464fabd61b95912d539455a3a5f15f5c59fe0/apps/terminal/api/virtualapp/virtualapp.py#L31-L67","documentation":"Thrown by VirtualApp extract_zip_pkg when safe_extract_zip fails while unpacking an uploaded .zip (bad archive, unsupported compression, or a path-traversal attempt caught by the safe-extract guard). The RuntimeError is converted into DRF ValidationError so the API returns 400 with 'Invalid zip file: <reason>`. ","triggerScenarios":"POST upload to the virtual-app upload action with a corrupted zip, a zip containing absolute paths or '../' entries (rejected by safe_extract_zip), or a file that is not actually a zip. Also triggered by disk/permission errors surfaced as RuntimeError during extraction.","commonSituations":"Repacking an applet on Windows producing archives with backslash entries; truncated uploads; zips built with unusual tools; deliberately malicious archives blocked by the safe-extraction check.","solutions":["Re-create the zip with standard tooling (zip -r app.zip . from inside the package dir) and re-upload","Inspect the archive for absolute paths or .. entries and repack with relative paths","Verify integrity locally first: unzip -t app.zip","If it persists, check server storage permissions/paths used by default_storage"],"exampleFix":"# before: zipping including parent dirs / absolute entries\nzip -r /tmp/myapp.zip /home/user/myapp/\n\n# after: build the archive from inside the package root\ncd myapp && zip -r ../myapp.zip . && curl -F 'file=@../myapp.zip' .../virtual-apps/upload/","handlingStrategy":"validation","validationCode":"import zipfile\nwith zipfile.ZipFile(path) as z:\n    assert z.testzip() is None, 'corrupt zip'\n    bad = [n for n in z.namelist() if n.startswith('/') or '..' in n.split('/')]\n    assert not bad, f'unsafe entries: {bad}'","typeGuard":"def is_safe_zip(path: str) -> bool:\n    import zipfile\n    try:\n        with zipfile.ZipFile(path) as z:\n            return z.testzip() is None and not any(\n                n.startswith('/') or '..' in n.split('/') for n in z.namelist())\n    except zipfile.BadZipFile:\n        return False","tryCatchPattern":"try:\n    resp = client.post('.../virtual-apps/upload/', files={'file': f})\nexcept HTTPError as e:\n    detail = e.response.json().get('error', '')\n    if detail.startswith('Invalid zip file'):\n        repack_archive_and_retry()\n    raise","preventionTips":["Always run unzip -t before uploading","Create archives from inside the package root with relative paths","Reject zips containing absolute paths or .. entries in CI before deploy"],"tags":["jumpserver","zip","upload","validation","virtual-app"],"backgroundTag":"invalid-archive-upload","analyzedSha":"6ec464fabd61b95912d539455a3a5f15f5c59fe0","analyzedAt":"2026-08-28T11:33:00.925Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}