From f528304dfc555576acb5d0154e1a7b44a44883f4 Mon Sep 17 00:00:00 2001 From: Kastriot Salihu Date: Fri, 6 Mar 2026 11:37:18 +0100 Subject: [PATCH] TA-4861: check zip file before passing to axios --- .../batch-import-export.service.ts | 16 +++++++++-- .../config-import.spec.ts | 27 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/commands/configuration-management/batch-import-export.service.ts b/src/commands/configuration-management/batch-import-export.service.ts index 8b45b09..82512ca 100644 --- a/src/commands/configuration-management/batch-import-export.service.ts +++ b/src/commands/configuration-management/batch-import-export.service.ts @@ -142,7 +142,7 @@ export class BatchImportExportService { configs = await this.studioService.mapSpaces(configs, studioManifests); const existingStudioPackages = await this.studioPackageApi.findAllPackages(); - const formData = this.buildBodyForImport(configs, variablesManifests); + const formData = this.buildBodyForImport(configs, sourceToBeImported, variablesManifests); const postPackageImportData = await this.batchImportExportApi.importPackages(formData, overwrite); await this.studioService.processImportedPackages(configs, existingStudioPackages, studioManifests); @@ -205,8 +205,10 @@ export class BatchImportExportService { logger.info(FileService.fileDownloadedMessage + filename); } - private buildBodyForImport(configs: AdmZip, variablesManifests: VariableManifestTransport[]): FormData { + private buildBodyForImport(configs: AdmZip, sourcePath: string, variablesManifests: VariableManifestTransport[]): FormData { const formData = new FormData(); + + this.assertUncompressedSizeWithinLimit(configs, sourcePath); const readableStream = this.getReadableStream(configs); formData.append("file", readableStream, {filename: "configs.zip"}); @@ -220,6 +222,16 @@ export class BatchImportExportService { return formData; } + private assertUncompressedSizeWithinLimit(configs: AdmZip, sourcePath: string): void { + const MAX_ZIP_SIZE = 4 * 1024 * 1024 * 1024; + const totalUncompressedBytes = configs.getEntries().reduce((sum, entry) => sum + entry.header.size, 0); + if (totalUncompressedBytes > MAX_ZIP_SIZE) { + throw new Error( + `Failed to handle zip file "${sourcePath}": uncompressed size ${(totalUncompressedBytes / (1024 ** 3)).toFixed(2)} GB exceeds the 4 GB limit.` + ); + } + } + private getReadableStream(configs: AdmZip): Readable { return new Readable({ read(): void { diff --git a/tests/commands/configuration-management/config-import.spec.ts b/tests/commands/configuration-management/config-import.spec.ts index 0a6717f..6f83327 100644 --- a/tests/commands/configuration-management/config-import.spec.ts +++ b/tests/commands/configuration-management/config-import.spec.ts @@ -1,5 +1,11 @@ import * as path from "path"; +import AdmZip = require("adm-zip"); import { mockCreateReadStream, mockExistsSync, mockReadDirSync, mockReadFileSync } from "../../utls/fs-mock-utils"; + +jest.mock("adm-zip", () => { + const realAdmZip = jest.requireActual("adm-zip"); + return jest.fn((...args: any[]) => realAdmZip(...args)); +}); import { PackageManifestTransport, PostPackageImportData, StudioPackageManifest, } from "../../../src/commands/configuration-management/interfaces/package-export.interfaces"; @@ -335,6 +341,27 @@ describe("Config import", () => { expect(mockedAxiosInstance.put).not.toHaveBeenCalledWith("https://myTeam.celonis.cloud/package-manager/api/spaces", expect.anything(), expect.anything()); }) + it("Should throw an error when zip bomb causes uncompressed size to exceed 4 GB limit", async () => { + const manifest: PackageManifestTransport[] = []; + manifest.push(ConfigUtils.buildManifestForKeyAndFlavor("key-1", "TEST")); + const exportedPackagesZip = ConfigUtils.buildBatchExportZip(manifest, []); + + mockReadFileSync(exportedPackagesZip.toBuffer()); + mockCreateReadStream(exportedPackagesZip.toBuffer()); + mockAxiosGet("https://myTeam.celonis.cloud/package-manager/api/packages", []); + + const FIVE_GB = 5 * 1024 * 1024 * 1024; + (AdmZip as unknown as jest.Mock).mockImplementationOnce((...args: any[]) => { + const instance = jest.requireActual("adm-zip")(...args); + instance.getEntries = () => [{ header: { size: FIVE_GB } }]; + return instance; + }); + + await expect( + new ConfigCommandService(testContext).batchImportPackages("./export_file.zip", null, false, null) + ).rejects.toThrow('Failed to handle zip file "./export_file.zip": uncompressed size 5.00 GB exceeds the 4 GB limit.'); + }) + it("Should assign studio runtime variable values after import", async () => { const manifest: PackageManifestTransport[] = []; manifest.push(ConfigUtils.buildManifestForKeyAndFlavor("key-1", "STUDIO"));