UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

194 lines (193 loc) 7.58 kB
import { t as CONFIG_DIR } from "./utils-P__uGsPB.js"; import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-DnyL0lW9.js"; import { n as resolveExistingPathsWithinRoot, r as resolveStrictExistingPathsWithinRoot } from "./security-runtime-Ckf0kc0h.js"; import "./sdk-security-runtime-De1urFYo.js"; import "./tmp-openclaw-dir-rXePUIsE.js"; import "./sdk-config-DCcWkGeD.js"; import path from "node:path"; import fs from "node:fs/promises"; //#region extensions/browser/src/browser/paths.ts /** * Browser filesystem path helpers. * * Defines browser output roots and resolves upload/media references while * enforcing root-scoped path access for Browser tool file inputs. */ const DEFAULT_FALLBACK_BROWSER_TMP_DIR = "/tmp/openclaw"; function canUseNodeFs() { const getBuiltinModule = process.getBuiltinModule; if (typeof getBuiltinModule !== "function") return false; try { return getBuiltinModule("fs") !== void 0; } catch { return false; } } const DEFAULT_BROWSER_TMP_DIR = canUseNodeFs() ? resolvePreferredOpenClawTmpDir() : DEFAULT_FALLBACK_BROWSER_TMP_DIR; /** Default root directory for browser trace files. */ const DEFAULT_TRACE_DIR = DEFAULT_BROWSER_TMP_DIR; /** Default root directory for browser downloads. */ const DEFAULT_DOWNLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "downloads"); /** Default root directory for browser upload inputs. */ const DEFAULT_UPLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "uploads"); /** Default root directory for managed inbound media references. */ const DEFAULT_INBOUND_MEDIA_DIR = path.join(CONFIG_DIR, "media", "inbound"); function normalizeUploadPathSource(source) { const trimmed = source.trim(); if (/^media:\/\//i.test(trimmed)) return trimmed; return trimmed.replace(/^\s*MEDIA\s*:\s*/i, "").trim(); } function decodeInboundMediaId(value, source) { let id; try { id = decodeURIComponent(value); } catch { return { ok: false, error: `Invalid media reference: ${source}` }; } if (!id || id === "." || id === ".." || id.includes("/") || id.includes("\\") || id.includes("\0")) return { ok: false, error: `Invalid media reference: ${source}` }; return { ok: true, path: id }; } function resolveManagedInboundMediaRef(source, inboundMediaDir) { const normalizedSource = normalizeUploadPathSource(source); if (!normalizedSource) return null; if (/^media:\/\//i.test(normalizedSource)) { const rawPath = /^media:\/\/[^/?#]*([^?#]*)/iu.exec(normalizedSource)?.[1] ?? ""; let parsed; try { parsed = new URL(normalizedSource); } catch { return { ok: false, error: `Invalid media reference: ${normalizedSource}` }; } if (parsed.hostname !== "inbound") return { ok: false, error: `Unsupported media reference location: ${parsed.hostname || "(missing)"}` }; if (!rawPath.startsWith("/") || rawPath.slice(1).includes("/") || rawPath.includes("\\")) return { ok: false, error: `Invalid media reference: ${normalizedSource}` }; const decoded = decodeInboundMediaId(rawPath.slice(1), normalizedSource); return decoded?.ok ? { ok: true, path: path.join(inboundMediaDir, decoded.path), uploadRootPrecedence: false } : decoded; } const relativeMatch = /^(?:\.\/)?media\/inbound\/([^/\\]+)$/u.exec(normalizedSource); if (!relativeMatch?.[1]) return null; const decoded = decodeInboundMediaId(relativeMatch[1], normalizedSource); return decoded?.ok ? { ok: true, path: path.join(inboundMediaDir, decoded.path), uploadRootPrecedence: true } : decoded; } async function isDirectInboundMediaFile(params) { let inboundRoot; try { inboundRoot = await fs.realpath(params.inboundMediaDir); } catch { inboundRoot = path.resolve(params.inboundMediaDir); } const relativePath = path.relative(inboundRoot, params.resolvedPath); return Boolean(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(relativePath) && !relativePath.includes("/") && !relativePath.includes("\\"); } async function resolveDirectInboundMediaPath(params) { const inboundPathsResult = params.strict ? await resolveStrictExistingPathsWithinRoot({ rootDir: params.inboundMediaDir, requestedPaths: [params.requestedPath], scopeLabel: `inbound media directory (${params.inboundMediaDir})` }) : await resolveExistingPathsWithinRoot({ rootDir: params.inboundMediaDir, requestedPaths: [params.requestedPath], scopeLabel: `inbound media directory (${params.inboundMediaDir})` }); if (!inboundPathsResult.ok) return inboundPathsResult; const resolvedPath = inboundPathsResult.paths[0] ?? params.requestedPath; if (!await isDirectInboundMediaFile({ inboundMediaDir: params.inboundMediaDir, resolvedPath })) return { ok: false, error: `Invalid media reference: must be a direct child of inbound media directory (${params.inboundMediaDir})` }; return inboundPathsResult; } /** Resolve upload paths and managed media references into existing file paths. */ async function resolveExistingUploadPaths({ requestedPaths, uploadDir = DEFAULT_UPLOAD_DIR, inboundMediaDir = DEFAULT_INBOUND_MEDIA_DIR }) { const paths = []; for (const requestedPath of requestedPaths) { const managedMediaPathResult = resolveManagedInboundMediaRef(requestedPath, inboundMediaDir); if (managedMediaPathResult?.ok === false) return managedMediaPathResult; if (managedMediaPathResult?.uploadRootPrecedence !== false) { const uploadPathsResult = managedMediaPathResult?.uploadRootPrecedence === true ? await resolveStrictExistingPathsWithinRoot({ rootDir: uploadDir, requestedPaths: [requestedPath], scopeLabel: `uploads directory (${uploadDir})` }) : await resolveExistingPathsWithinRoot({ rootDir: uploadDir, requestedPaths: [requestedPath], scopeLabel: `uploads directory (${uploadDir})` }); if (uploadPathsResult.ok) { paths.push(uploadPathsResult.paths[0] ?? requestedPath); continue; } } const inboundPathsResult = await resolveDirectInboundMediaPath({ inboundMediaDir, requestedPath: managedMediaPathResult?.path ?? requestedPath, strict: false }); if (!inboundPathsResult.ok) return inboundPathsResult; paths.push(inboundPathsResult.paths[0] ?? requestedPath); } return { ok: true, paths }; } /** Strictly resolve upload paths under the upload root only. */ async function resolveStrictExistingUploadPaths({ requestedPaths, uploadDir = DEFAULT_UPLOAD_DIR, inboundMediaDir = DEFAULT_INBOUND_MEDIA_DIR }) { const paths = []; for (const requestedPath of requestedPaths) { const managedMediaPathResult = resolveManagedInboundMediaRef(requestedPath, inboundMediaDir); if (managedMediaPathResult?.ok === false) return managedMediaPathResult; if (managedMediaPathResult?.uploadRootPrecedence !== false) { const uploadPathsResult = await resolveStrictExistingPathsWithinRoot({ rootDir: uploadDir, requestedPaths: [requestedPath], scopeLabel: `uploads directory (${uploadDir})` }); if (uploadPathsResult.ok) { paths.push(uploadPathsResult.paths[0] ?? requestedPath); continue; } } const inboundPathsResult = await resolveDirectInboundMediaPath({ inboundMediaDir, requestedPath: managedMediaPathResult?.path ?? requestedPath, strict: true }); if (!inboundPathsResult.ok) return inboundPathsResult; paths.push(inboundPathsResult.paths[0] ?? requestedPath); } return { ok: true, paths }; } //#endregion export { resolveStrictExistingUploadPaths as a, resolveExistingUploadPaths as i, DEFAULT_TRACE_DIR as n, DEFAULT_UPLOAD_DIR as r, DEFAULT_DOWNLOAD_DIR as t };