From 1302d705e753af59c49cdfea2e9d6b0381a00a48 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Tue, 17 Feb 2026 14:42:52 -0600 Subject: [PATCH] test(drop): add security tests for traversal prevention in unzipDrop function - Introduced a new test suite to validate that the unzipDrop function prevents writing outside the application directory, specifically addressing potential sandbox escape vulnerabilities. - Implemented setup and teardown logic to ensure a clean test environment for each test run. --- apps/dokploy/__test__/drop/drop.test.ts | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/apps/dokploy/__test__/drop/drop.test.ts b/apps/dokploy/__test__/drop/drop.test.ts index e0277cfed..6e9940d6d 100644 --- a/apps/dokploy/__test__/drop/drop.test.ts +++ b/apps/dokploy/__test__/drop/drop.test.ts @@ -285,6 +285,45 @@ describe("unzipDrop path under output (no traversal)", () => { }); }); +describe("security: traversal inside BASE_PATH (sandbox escape)", () => { + beforeAll(async () => { + await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true }); + }); + + afterAll(async () => { + await fs.rm(APPLICATIONS_PATH, { recursive: true, force: true }); + }); + + it("should NOT allow writing outside application directory but inside BASE_PATH", async () => { + const appName = "sandbox-escape"; + + const base = APPLICATIONS_PATH.replace("/applications", ""); + const output = path.join(APPLICATIONS_PATH, appName, "code"); + + await fs.mkdir(output, { recursive: true }); + + // attacker writes into traefik config inside base + const zip = new AdmZip(); + zip.addFile( + "../../../traefik/dynamic/evil.yml", + Buffer.from("pwned: true"), + ); + + const file = new File([zip.toBuffer() as any], "exploit.zip"); + + await unzipDrop(file, { ...baseApp, appName }); + + const escapedPath = path.join(base, "traefik/dynamic/evil.yml"); + + const exists = await fs + .readFile(escapedPath) + .then(() => true) + .catch(() => false); + + expect(exists).toBe(false); + }); +}); + describe("unzipDrop using real zip files", () => { // const { APPLICATIONS_PATH } = paths(); beforeAll(async () => {