From d18e5f9c8337d8eda0b3455dfc2d93ccbcc382ca Mon Sep 17 00:00:00 2001 From: Sag Date: Thu, 25 Jun 2026 14:40:09 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20theme=20zip=20extraction?= =?UTF-8?q?=20permissions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit no issue GScan validates and installs theme zips from its extracted temp directory. Some customer archives preserve read-only directory modes, which can block validation or later cleanup. Use the new @tryghost/zip owner-permission normalization option so uploaded themes are extracted with writable owner permissions while keeping the source archive unchanged. --- lib/read-zip.js | 4 +++- test/read-zip.test.js | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/read-zip.js b/lib/read-zip.js index a181575c..67ea9d10 100644 --- a/lib/read-zip.js +++ b/lib/read-zip.js @@ -32,7 +32,9 @@ const resolveBaseDir = async (zipPath) => { const readZip = (zip, options = {}) => { const tempUuid = randomUUID(); const tempPath = os.tmpdir() + '/' + tempUuid; - const extractOptions = {}; + const extractOptions = { + ensureOwnerPermissions: true + }; if (options.limits) { extractOptions.limits = options.limits; diff --git a/test/read-zip.test.js b/test/read-zip.test.js index 211fa68b..51af148f 100644 --- a/test/read-zip.test.js +++ b/test/read-zip.test.js @@ -140,7 +140,10 @@ describe('Zip file handler can read zip files', function () { const zip = await mocked.readZip({path: '/tmp/theme.zip', name: 'theme.zip'}, {limits}); tempDirs.push(zip.origPath); - expect(extract).toHaveBeenCalledWith('/tmp/theme.zip', expect.any(String), {limits}); + expect(extract).toHaveBeenCalledWith('/tmp/theme.zip', expect.any(String), { + ensureOwnerPermissions: true, + limits + }); } finally { mocked.restore(); } @@ -162,7 +165,9 @@ describe('Zip file handler can read zip files', function () { errorDetails: extractError.message }); - expect(extract).toHaveBeenCalledWith('/tmp/theme.zip', expect.any(String), {}); + expect(extract).toHaveBeenCalledWith('/tmp/theme.zip', expect.any(String), { + ensureOwnerPermissions: true + }); } finally { mocked.restore(); }