{"record":{"id":"5220454c0ff0ebd9","repo":"Leantime/leantime","slug":"notification-plugin-zip-not-zip","errorCode":null,"errorMessage":"notification.plugin_zip_not_zip","messagePattern":"notification\\.plugin_zip_not_zip","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"app/Domain/Plugins/Services/Plugins.php","lineNumber":740,"sourceCode":"            is_dir($pluginDir = \"{$this->pluginDirectory}{$foldername}\")\n            && ! File::deleteDirectory($pluginDir)\n        ) {\n            throw new \\Exception(__('notification.plugin_cant_remove'));\n        }\n\n        if (! mkdir($pluginDir) && ! is_dir($pluginDir)) {\n            throw new \\RuntimeException(sprintf('Directory \"%s\" was not created', $pluginDir));\n        }\n\n        $zip = new \\ZipArchive;\n\n        match ($zip->open($temporaryFile)) {\n            \\ZipArchive::ER_EXISTS => throw new \\Exception(__('notification.plugin_zip_exists')),\n            \\ZipArchive::ER_INCONS => throw new \\Exception(__('notification.plugin_zip_inconsistent')),\n            \\ZipArchive::ER_INVAL => throw new \\Exception(__('notification.plugin_zip_invalid_arg')),\n            \\ZipArchive::ER_MEMORY => throw new \\Exception(__('notification.plugin_zip_malloc')),\n            \\ZipArchive::ER_NOENT => throw new \\Exception(__('notification.plugin_zip_no_file')),\n            \\ZipArchive::ER_NOZIP => throw new \\Exception(__('notification.plugin_zip_not_zip')),\n            \\ZipArchive::ER_OPEN => throw new \\Exception(__('notification.plugin_zip_cant_open')),\n            \\ZipArchive::ER_READ => throw new \\Exception(__('notification.plugin_zip_read_err')),\n            \\ZipArchive::ER_SEEK => throw new \\Exception(__('notification.plugin_zip_seek_err')),\n            default => throw new \\Exception(__('notification.plugin_zip_unknown_err')),\n            true => null,\n        };\n\n        if (! $zip->extractTo($pluginDir)) {\n            throw new \\Exception(__('notification.plugin_zip_cant_extract'));\n        }\n\n        $zip->close();\n\n        unlink($temporaryFile);\n\n        // read the composer.json content from the plugin phar file\n        $pluginModel = $this->createPluginFromComposer($foldername, $plugin->license);\n","sourceCodeStart":722,"sourceCodeEnd":758,"githubUrl":"https://github.com/Leantime/leantime/blob/9a9f49f1008f4782b30f6723c54228f4f992e636/app/Domain/Plugins/Services/Plugins.php#L722-L758","documentation":"The match maps ZipArchive::ER_NOZIP to 'Zip: Not a zip archive'. The temp file exists and opened, but it has no zip magic - libzip could not find a zip structure at all. The earlier Content-Type === 'application/zip' check (Plugins.php:703) only validates a header, so a server can still send a non-zip body with that header.","triggerScenarios":"installMarketplacePlugin() where the 200/zip-labeled body is actually HTML or JSON: a marketplace soft-error page (expired license, rate limit) returned with HTTP 200; a proxy/captive portal injecting an HTML page; the identifier/version hitting an error route that still sets application/zip.","commonSituations":"Invalid or exhausted license key producing an HTML error page with a 200 status; marketplace API changes; transparent proxies on corporate networks rewriting responses; the marketplace serving a JSON error object.","solutions":["Inspect the temp file: head -c 200 /tmp/{Folder}.zip - if it starts with '<' or '{', the body is an error page, not a zip.","Validate the plugin license key and marketplace account status, then retry.","Bypass/verify any corporate proxy for marketplace.leantime.io.","Report to Leantime if the artifact endpoint serves non-zip bodies with 200 + application/zip."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"$body = $response->body();\nif (strncmp($body, \"PK\\x03\\x04\", 4) !== 0) {\n    throw new RuntimeException('Marketplace returned a non-zip body (license/proxy error page?)');\n}","typeGuard":"/** True when $path points at a file starting with the zip magic bytes. */\nfunction looksLikeZip(string $path): bool\n{\n    $fh = @fopen($path, 'rb');\n    if (! $fh) {\n        return false;\n    }\n    $magic = fread($fh, 4);\n    fclose($fh);\n\n    return $magic === \"PK\\x03\\x04\" || $magic === \"PK\\x05\\x06\" || $magic === \"PK\\x07\\x08\";\n}","tryCatchPattern":null,"preventionTips":["Check the zip magic bytes before opening - a Content-Type header alone proves nothing.","Keep the marketplace license key valid; soft-error pages arrive with HTTP 200.","Route marketplace traffic without interfering proxies."],"tags":["plugins","ziparchive","marketplace","invalid-file"],"backgroundTag":"invalid-zip-file","analyzedSha":"9a9f49f1008f4782b30f6723c54228f4f992e636","analyzedAt":"2026-08-21T02:37:38.966Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}