jumpserver/jumpserver · error · ValidationError
Invalid zip file: {}
Error message
Invalid zip file: {} What it means
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>`.
Source
Thrown at apps/terminal/api/virtualapp/virtualapp.py:49
if extract_to and os.path.exists(extract_to):
shutil.rmtree(extract_to)
def extract_zip_pkg(self):
serializer = self.get_serializer(data=self.request.data)
serializer.is_valid(raise_exception=True)
file = serializer.validated_data['file']
save_to = 'virtual_apps/{}'.format(file.name + '.tmp.zip')
if default_storage.exists(save_to):
default_storage.delete(save_to)
rel_path = default_storage.save(save_to, file)
path = default_storage.path(rel_path)
extract_to = default_storage.path('virtual_apps/{}.tmp'.format(file.name))
if os.path.exists(extract_to):
shutil.rmtree(extract_to)
try:
safe_extract_zip(path, extract_to)
except RuntimeError as e:
raise ValidationError({'error': _('Invalid zip file') + ': {}'.format(e)})
tmp_dir = VirtualApp.locate_pkg_root(extract_to, file.name)
return tmp_dir, rel_path, extract_to
@action(detail=False, methods=['post'], serializer_class=FileSerializer)
def upload(self, request, *args, **kwargs):
rel_path = None
extract_to = None
try:
tmp_dir, rel_path, extract_to = self.extract_zip_pkg()
manifest = VirtualApp.validate_pkg(tmp_dir)
name = manifest['name']
instance = VirtualApp.objects.filter(name=name).first()
if instance:
return Response({'error': 'virtual app already exists: {}'.format(name)}, status=400)
app, serializer = VirtualApp.install_from_dir(tmp_dir)
return Response(serializer.data, status=201)
finally:View on GitHub (pinned to 6ec464fabd)
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
Example fix
# before: zipping including parent dirs / absolute entries zip -r /tmp/myapp.zip /home/user/myapp/ # after: build the archive from inside the package root cd myapp && zip -r ../myapp.zip . && curl -F 'file=@../myapp.zip' .../virtual-apps/upload/
Defensive patterns
Strategy: validation
Validate before calling
import zipfile
with zipfile.ZipFile(path) as z:
assert z.testzip() is None, 'corrupt zip'
bad = [n for n in z.namelist() if n.startswith('/') or '..' in n.split('/')]
assert not bad, f'unsafe entries: {bad}' Type guard
def is_safe_zip(path: str) -> bool:
import zipfile
try:
with zipfile.ZipFile(path) as z:
return z.testzip() is None and not any(
n.startswith('/') or '..' in n.split('/') for n in z.namelist())
except zipfile.BadZipFile:
return False Try / catch
try:
resp = client.post('.../virtual-apps/upload/', files={'file': f})
except HTTPError as e:
detail = e.response.json().get('error', '')
if detail.startswith('Invalid zip file'):
repack_archive_and_retry()
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Applet pkg not valid, Missing file {}
- Request user has no terminal
- Applet pkg not valid, Missing file {}
- Missing name in manifest.yml
- {initError}: {ctorName}
AI-assisted analysis of jumpserver/jumpserver@6ec464fabd (2026-08-28).
Data as JSON: /api/errors/f5a19b02fc197c01.
Report an issue: GitHub.