From 08b34f4f7604248460e8e1dfdce7bc94beaff0df Mon Sep 17 00:00:00 2001 From: David Dworken Date: Fri, 18 Jul 2025 09:35:32 -0700 Subject: [PATCH] fix: prevent zip slip attacks in DXT unpack function (#74) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: prevent zip slip attacks in DXT unpack function Add path validation to prevent directory traversal attacks when unpacking DXT files. The fix validates that extracted file paths remain within the intended output directory before writing files to disk. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * fix: make zip slip protection cross-platform compatible Use path.sep instead of hardcoded '/' separator to ensure path validation works correctly on both Windows and Unix systems. This fixes failing tests on Windows while maintaining security protection against directory traversal. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: Claude --- CLI.md | 2 ++ MANIFEST.md | 2 ++ src/cli/unpack.ts | 13 ++++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CLI.md b/CLI.md index 904b86b..bc21b8d 100644 --- a/CLI.md +++ b/CLI.md @@ -157,10 +157,12 @@ dxt unsign my-extension.dxt For signing extensions, you need: 1. **Certificate**: X.509 certificate in PEM format + - Should have Code Signing extended key usage - Can be self-signed (for development) or CA-issued (for production) 2. **Private Key**: Corresponding private key in PEM format + - Must match the certificate's public key 3. **Intermediate Certificates** (optional): For CA-issued certificates diff --git a/MANIFEST.md b/MANIFEST.md index 68d27c0..d1dab68 100644 --- a/MANIFEST.md +++ b/MANIFEST.md @@ -290,12 +290,14 @@ The `server` object defines how to run the MCP server: ### Server Types 1. **Python**: `server.type = "python"` + - Requires `entry_point` to Python file - All dependencies must be bundled in the DXT - Can use `server/lib` for packages or `server/venv` for full virtual environment - Python runtime version specified in `compatibility.runtimes.python` 2. **Node.js**: `server.type = "node"` + - Requires `entry_point` to JavaScript file - All dependencies must be bundled in `node_modules` - Node.js runtime version specified in `compatibility.runtimes.node` diff --git a/src/cli/unpack.ts b/src/cli/unpack.ts index d7f3afe..774ed6e 100644 --- a/src/cli/unpack.ts +++ b/src/cli/unpack.ts @@ -1,6 +1,6 @@ import { unzipSync } from "fflate"; import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; -import { join, resolve } from "path"; +import { join, resolve, sep } from "path"; import { extractSignatureBlock } from "../node/sign.js"; import { getLogger } from "../shared/log.js"; @@ -85,6 +85,17 @@ export async function unpackExtension({ if (Object.prototype.hasOwnProperty.call(decompressed, relativePath)) { const data = decompressed[relativePath]; const fullPath = join(finalOutputDir, relativePath); + + // Prevent zip slip attacks by validating the resolved path + const normalizedPath = resolve(fullPath); + const normalizedOutputDir = resolve(finalOutputDir); + if ( + !normalizedPath.startsWith(normalizedOutputDir + sep) && + normalizedPath !== normalizedOutputDir + ) { + throw new Error(`Path traversal attempt detected: ${relativePath}`); + } + const dir = join(fullPath, ".."); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true });