diff --git a/server/helpers.mjs b/server/helpers.mjs index 654e072..d890088 100644 --- a/server/helpers.mjs +++ b/server/helpers.mjs @@ -1,15 +1,34 @@ import { spawnSync } from "child_process"; import path from "path"; -const WORKSPACE_ROOT = process.env.CONDUIT_ROOT || "/workspace"; +const CONTAINER_PATH = "/workspace"; -// Resolve a path param relative to WORKSPACE_ROOT, preventing traversal. +// Maps container path prefixes to host paths. +// Derived from docker-compose.yml volumes: +// ./workspace -> /workspace +// ./claude-home -> /home/claude +// Override host paths via env vars when running outside the default layout. +const VOLUME_MAP = { + "/workspace": process.env.CONDUIT_HOST_WORKSPACE || CONTAINER_PATH, + "/home/claude": process.env.CONDUIT_HOST_HOME || "/home/claude", +}; + +// Translate a container-side path to its host-side equivalent using VOLUME_MAP. +// Relative paths are resolved against CONTAINER_PATH first. +// Throws if the path escapes all known volumes. export function resolve_path(user_path) { - const resolved = path.resolve(WORKSPACE_ROOT, user_path.replace(/^\//, "")); - if (!resolved.startsWith(WORKSPACE_ROOT)) { - throw new Error(`Path escapes workspace root: ${user_path}`); + const abs = path.isAbsolute(user_path) + ? user_path + : path.join(CONTAINER_PATH, user_path); + + for (const [container_prefix, host_prefix] of Object.entries(VOLUME_MAP)) { + if (abs === container_prefix || abs.startsWith(container_prefix + "/")) { + const relative = abs.slice(container_prefix.length); + return host_prefix + relative; + } } - return resolved; + + throw new Error(`Path is outside all known volumes: ${user_path}`); } // Execute a binary with an argument list — no shell interpolation.