diff --git a/src/app/api/open-file/route.ts b/src/app/api/open-file/route.ts index 06d3d9a6..597a6c7e 100644 --- a/src/app/api/open-file/route.ts +++ b/src/app/api/open-file/route.ts @@ -7,7 +7,33 @@ import os from "os"; const execFileAsync = promisify(execFile); +function isLocalhostRequest(req: NextRequest): boolean { + const forwarded = req.headers.get("x-forwarded-for"); + if (forwarded) { + const firstIp = forwarded.split(",")[0].trim(); + if (firstIp !== "127.0.0.1" && firstIp !== "::1" && firstIp !== "::ffff:127.0.0.1") { + return false; + } + } + + const host = req.headers.get("host") || ""; + const hostname = host.split(":")[0]; + if (hostname && hostname !== "localhost" && hostname !== "127.0.0.1" && hostname !== "::1") { + return false; + } + + return true; +} + export async function POST(req: NextRequest) { + // Only allow requests from localhost + if (!isLocalhostRequest(req)) { + return NextResponse.json( + { success: false, error: "Forbidden: localhost only" }, + { status: 403 } + ); + } + try { const body = await req.json(); const { filePath: inputPath } = body; @@ -22,6 +48,15 @@ export async function POST(req: NextRequest) { // Normalize and resolve the path to prevent traversal attacks const normalizedPath = path.resolve(inputPath); + // Restrict to user's home directory + const homeDir = os.homedir(); + if (!normalizedPath.startsWith(homeDir + path.sep) && normalizedPath !== homeDir) { + return NextResponse.json( + { success: false, error: "Path is outside allowed directory" }, + { status: 403 } + ); + } + // Validate that the path exists and is a file try { const stats = await stat(normalizedPath); diff --git a/src/app/api/save-generation/route.ts b/src/app/api/save-generation/route.ts index 52460966..317f5783 100644 --- a/src/app/api/save-generation/route.ts +++ b/src/app/api/save-generation/route.ts @@ -49,11 +49,11 @@ function isHttpUrl(str: string): boolean { } // Known file extensions for 3D models and common media -const KNOWN_3D_EXTENSIONS = new Set(["glb", "gltf", "obj", "fbx", "usdz", "stl", "ply", "zip"]); +const KNOWN_3D_EXTENSIONS = new Set(["glb", "gltf", "obj", "fbx", "usdz", "stl", "ply"]); const KNOWN_MEDIA_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "mp4", "webm", "mov"]); // Helper to extract a recognized file extension from a URL pathname -function getExtensionFromUrl(url: string): string | null { +export function getExtensionFromUrl(url: string): string | null { try { const urlObj = new URL(url); const pathname = urlObj.pathname; diff --git a/src/types/nodes.ts b/src/types/nodes.ts index ab92a652..61b3c60b 100644 --- a/src/types/nodes.ts +++ b/src/types/nodes.ts @@ -183,8 +183,8 @@ export interface Generate3DNodeData extends BaseNodeData { inputImageRefs?: string[]; inputPrompt: string | null; output3dUrl: string | null; - savedFilename?: string | null; - savedFilePath?: string | null; + savedFilename: string | null; + savedFilePath: string | null; selectedModel?: SelectedModel; parameters?: Record; inputSchema?: ModelInputDef[];