diff --git a/src/client/api.ts b/src/client/api.ts index e02cd1b..49b264d 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -256,8 +256,9 @@ export class WordPressClient implements IWordPressClient { throw new Error("Only HTTP and HTTPS protocols are allowed"); } - // Prevent localhost/private IP access in production - if (config().app.isProduction) { + // Prevent localhost/private IP access unless explicitly allowed + // Set ALLOW_PRIVATE_URLS=true for local development with a local WordPress + if (process.env.ALLOW_PRIVATE_URLS !== "true") { const hostname = parsed.hostname.toLowerCase(); if ( hostname === "localhost" || @@ -267,7 +268,7 @@ export class WordPressClient implements IWordPressClient { hostname.match(/^172\.(1[6-9]|2[0-9]|3[01])\./) || hostname.match(/^192\.168\./) ) { - throw new Error("Private/localhost URLs not allowed in production"); + throw new Error("Private/localhost URLs not allowed. Set ALLOW_PRIVATE_URLS=true for local development."); } } diff --git a/src/tools/media.ts b/src/tools/media.ts index cf8a898..1b6a572 100644 --- a/src/tools/media.ts +++ b/src/tools/media.ts @@ -3,6 +3,7 @@ import { WordPressClient } from "@/client/api.js"; import type { MCPToolSchema } from "@/types/mcp.js"; import { MediaQueryParams, UpdateMediaRequest, UploadMediaRequest } from "@/types/wordpress.js"; import { getErrorMessage } from "@/utils/error.js"; +import { validateFilePath } from "@/utils/validation/security.js"; import { toolParams } from "./params.js"; /** @@ -223,10 +224,16 @@ export class MediaTools { public async handleUploadMedia(client: WordPressClient, params: Record): Promise { const uploadParams = toolParams(params); try { + // Validate file path to prevent path traversal attacks + // Set MCP_UPLOAD_BASE_DIR to restrict uploads to a specific directory (recommended in Docker) + const allowedBasePath = process.env.MCP_UPLOAD_BASE_DIR || "/"; + const safePath = validateFilePath(uploadParams.file_path, allowedBasePath); + uploadParams.file_path = safePath; + try { - await fs.promises.access(uploadParams.file_path); + await fs.promises.access(safePath); } catch (_error) { - throw new Error(`File not found at path: ${uploadParams.file_path}`); + throw new Error(`File not found at path: ${safePath}`); } const media = await client.uploadMedia(uploadParams);