openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
263 lines (262 loc) • 11.2 kB
JavaScript
import { c as normalizeOptionalLowercaseString } from "./string-coerce-CIXf7egm.js";
import { d as normalizeStringEntries } from "./string-normalization-DsCfAx8q.js";
import { r as isPathInside } from "./path-guards-Cp-mGr3-.js";
import { g as shortenPathWithHome } from "./utils-P__uGsPB.js";
import { c as splitSandboxBindSpec, s as resolveSandboxHostPathViaExistingAncestor } from "./network-mode-BcCA7r3o.js";
import { a as resolveSandboxInputPath, o as resolveSandboxPath } from "./sandbox-paths-hdOwYXJD.js";
import { g as SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constants-DE2EIeEP.js";
import { n as normalizeContainerPathCore, r as relativePathEscapesContainerRoot, t as isPathInsideContainerRoot } from "./path-utils-Drbu0ZHc.js";
import { o as resolveProtectedSkillMountContainerPaths, s as resolveReadOnlyWorkspaceSkillMounts } from "./workspace-mounts-CowGDwFc.js";
import path from "node:path";
import os from "node:os";
//#region src/agents/sandbox/fs-paths.ts
/**
* Sandbox filesystem mount and path resolution helpers.
*
* Builds the container-to-host mount table and maps requested sandbox paths to writable/read-only host targets.
*/
function parseSandboxBindMount(spec) {
const trimmed = spec.trim();
if (!trimmed) return null;
const parsed = splitSandboxBindSpec(trimmed);
if (!parsed) return null;
const hostToken = parsed.host.trim();
const containerToken = parsed.container.trim();
if (!hostToken || !containerToken || !path.posix.isAbsolute(containerToken)) return null;
const optionsToken = normalizeOptionalLowercaseString(parsed.options) ?? "";
const writable = !(optionsToken ? normalizeStringEntries(optionsToken.split(",")) : []).includes("ro");
return {
hostRoot: path.resolve(hostToken),
containerRoot: normalizeContainerPathCore(containerToken),
writable
};
}
function buildSandboxFsMounts(sandbox) {
const mounts = [{
hostRoot: path.resolve(sandbox.workspaceDir),
containerRoot: normalizeContainerPathCore(sandbox.containerWorkdir),
writable: sandbox.workspaceAccess !== "ro",
source: "workspace"
}];
if (sandbox.workspaceAccess !== "none" && path.resolve(sandbox.agentWorkspaceDir) !== path.resolve(sandbox.workspaceDir)) mounts.push({
hostRoot: path.resolve(sandbox.agentWorkspaceDir),
containerRoot: SANDBOX_AGENT_WORKSPACE_MOUNT,
writable: sandbox.workspaceAccess === "rw",
source: "agent"
});
const protectedSkillMounts = resolveReadOnlyWorkspaceSkillMounts({
workspaceDir: sandbox.workspaceDir,
agentWorkspaceDir: sandbox.agentWorkspaceDir,
skillsWorkspaceDir: sandbox.skillsWorkspaceDir,
workdir: sandbox.containerWorkdir,
workspaceAccess: sandbox.workspaceAccess
});
for (const mount of protectedSkillMounts) mounts.push({
hostRoot: path.resolve(mount.hostPath),
containerRoot: normalizeContainerPathCore(mount.containerPath),
writable: false,
source: "protectedSkill"
});
const protectedPaths = resolveProtectedSkillMountContainerPaths(protectedSkillMounts);
for (const bind of sandbox.docker.binds ?? []) {
const parsed = parseSandboxBindMount(bind);
if (!parsed) continue;
if (protectedPaths.has(parsed.containerRoot)) continue;
mounts.push({
hostRoot: parsed.hostRoot,
containerRoot: parsed.containerRoot,
writable: parsed.writable,
source: "bind"
});
}
for (const resource of sandbox.readOnlyResourceMounts ?? []) mounts.push({
hostRoot: resource.hostPath,
containerRoot: resource.containerPath,
writable: false,
source: "protectedSkill"
});
return dedupeMounts(mounts);
}
function resolveWritableSandboxBindHostRoots(binds) {
const parsedBinds = parseSandboxBindMounts(binds);
const readonlyRoots = parsedBinds.filter((bind) => !bind.writable).map((bind) => bind.hostRoot);
const roots = [];
const seen = /* @__PURE__ */ new Set();
for (const parsed of parsedBinds) {
if (!parsed.writable || seen.has(parsed.hostRoot) || readonlyRoots.some((root) => isPathInside(parsed.hostRoot, root))) continue;
seen.add(parsed.hostRoot);
roots.push(parsed.hostRoot);
}
return roots;
}
function hasSandboxBindContainerPathAliases(binds) {
for (const parsed of parseSandboxBindMounts(binds)) if (parsed.hostRoot !== parsed.containerRoot) return true;
return false;
}
function hasSandboxBindReadonlyHostShadows(binds) {
const parsedBinds = parseSandboxBindMounts(binds);
const writableRoots = parsedBinds.filter((bind) => bind.writable).map((bind) => bind.hostRoot);
const readonlyRoots = parsedBinds.filter((bind) => !bind.writable).map((bind) => bind.hostRoot);
return writableRoots.some((writableRoot) => readonlyRoots.some((readonlyRoot) => isPathInside(writableRoot, readonlyRoot)));
}
function parseSandboxBindMounts(binds) {
const parsed = [];
for (const bind of binds ?? []) {
const mount = parseSandboxBindMount(bind);
if (mount) parsed.push(mount);
}
return parsed;
}
function resolveSandboxFsPathWithMounts(params) {
const mountsByContainer = [...params.mounts].toSorted(compareMountsByContainerPath);
const mountsByHost = [...params.mounts].toSorted(compareMountsByHostPath);
const input = params.filePath;
const inputPosix = normalizePosixInput(input);
if (path.posix.isAbsolute(inputPosix)) {
const containerMount = findMountByContainerPath(mountsByContainer, inputPosix);
if (containerMount) return resolveMountedContainerPath({
mount: containerMount,
containerPath: inputPosix,
defaultContainerRoot: params.defaultContainerRoot
});
}
if (!path.posix.isAbsolute(inputPosix)) {
const protectedContainerCandidate = resolveRelativeContainerCandidate({
inputPosix,
cwd: params.cwd,
defaultContainerRoot: params.defaultContainerRoot,
mountsByHost
});
const protectedContainerMount = findMountByContainerPath(mountsByContainer, protectedContainerCandidate);
if (protectedContainerMount?.source === "protectedSkill") return resolveMountedContainerPath({
mount: protectedContainerMount,
containerPath: protectedContainerCandidate,
defaultContainerRoot: params.defaultContainerRoot
});
}
const hostResolved = resolveSandboxInputPath(input, params.cwd);
const hostMount = findMountByHostPath(mountsByHost, hostResolved);
if (hostMount) {
const relHost = path.relative(hostMount.hostRoot, hostResolved);
const relPosix = relHost ? relHost.split(path.sep).join(path.posix.sep) : "";
const containerPath = relPosix ? path.posix.join(hostMount.containerRoot, relPosix) : hostMount.containerRoot;
return {
hostPath: hostResolved,
containerPath,
relativePath: toDisplayRelative({
containerPath,
defaultContainerRoot: params.defaultContainerRoot
}),
writable: hostMount.writable
};
}
const escapeMessage = formatSandboxRootEscapeMessage({
input,
defaultWorkspaceRoot: params.defaultWorkspaceRoot,
defaultContainerRoot: params.defaultContainerRoot
});
try {
resolveSandboxPath({
filePath: input,
cwd: params.cwd,
root: params.defaultWorkspaceRoot
});
} catch {
throw new Error(escapeMessage);
}
throw new Error(escapeMessage);
}
function resolveMountedContainerPath(params) {
const rel = path.posix.relative(params.mount.containerRoot, params.containerPath);
const hostPath = rel ? path.resolve(params.mount.hostRoot, ...toHostSegments(rel)) : params.mount.hostRoot;
const containerPath = rel ? path.posix.join(params.mount.containerRoot, rel) : params.mount.containerRoot;
return {
hostPath,
containerPath,
relativePath: toDisplayRelative({
containerPath,
defaultContainerRoot: params.defaultContainerRoot
}),
writable: params.mount.writable
};
}
function resolveRelativeContainerCandidate(params) {
const cwdMount = findMountByHostPath(params.mountsByHost, path.resolve(params.cwd));
if (cwdMount) {
const relHost = path.relative(cwdMount.hostRoot, path.resolve(params.cwd));
const relPosix = relHost ? relHost.split(path.sep).join(path.posix.sep) : "";
const containerCwd = relPosix ? path.posix.join(cwdMount.containerRoot, relPosix) : cwdMount.containerRoot;
return normalizeContainerPathCore(path.posix.resolve(containerCwd, params.inputPosix));
}
const cwdPosix = normalizePosixInput(params.cwd);
if (path.posix.isAbsolute(cwdPosix)) return normalizeContainerPathCore(path.posix.resolve(cwdPosix, params.inputPosix));
return normalizeContainerPathCore(path.posix.resolve(params.defaultContainerRoot, params.inputPosix));
}
function formatSandboxRootEscapeMessage(params) {
const containerRoot = normalizeContainerPathCore(params.defaultContainerRoot);
let workspaceRoot = shortenHomePath(path.resolve(params.defaultWorkspaceRoot));
if (workspaceRoot.startsWith(`~${path.sep}`)) workspaceRoot = workspaceRoot.replaceAll(path.sep, path.posix.sep);
return `Path escapes sandbox root (${workspaceRoot}; container root ${containerRoot}): ${params.input}. Use a path under ${containerRoot}/ instead.`;
}
function shortenHomePath(value) {
return shortenPathWithHome(value, {
home: os.homedir(),
prefix: "~"
});
}
function compareMountsByContainerPath(a, b) {
const byLength = b.containerRoot.length - a.containerRoot.length;
if (byLength !== 0) return byLength;
return mountSourcePriority(b.source) - mountSourcePriority(a.source);
}
function compareMountsByHostPath(a, b) {
const byLength = b.hostRoot.length - a.hostRoot.length;
if (byLength !== 0) return byLength;
return mountSourcePriority(b.source) - mountSourcePriority(a.source);
}
function mountSourcePriority(source) {
if (source === "protectedSkill") return 3;
if (source === "bind") return 2;
if (source === "agent") return 1;
return 0;
}
function dedupeMounts(mounts) {
const seen = /* @__PURE__ */ new Set();
const deduped = [];
for (const mount of mounts) {
const key = `${mount.hostRoot}=>${mount.containerRoot}`;
if (seen.has(key)) continue;
seen.add(key);
deduped.push(mount);
}
return deduped;
}
function findMountByContainerPath(mounts, target) {
for (const mount of mounts) if (isPathInsideContainerRoot(mount.containerRoot, target)) return mount;
return null;
}
function findMountByHostPath(mounts, target) {
for (const mount of mounts) if (isPathInsideHost(mount.hostRoot, target)) return mount;
return null;
}
function isPathInsideHost(root, target) {
const canonicalRoot = resolveSandboxHostPathViaExistingAncestor(path.resolve(root));
const resolvedTarget = path.resolve(target);
const canonicalTargetParent = resolveSandboxHostPathViaExistingAncestor(path.dirname(resolvedTarget));
const canonicalTarget = path.resolve(canonicalTargetParent, path.basename(resolvedTarget));
return isPathInside(canonicalRoot, canonicalTarget);
}
function toHostSegments(relativePosix) {
return relativePosix.split("/").filter(Boolean);
}
function toDisplayRelative(params) {
const rel = path.posix.relative(params.defaultContainerRoot, params.containerPath);
if (!rel) return "";
if (!relativePathEscapesContainerRoot(rel)) return rel;
return params.containerPath;
}
function normalizePosixInput(value) {
return value.replace(/\\/g, "/").trim();
}
//#endregion
export { resolveWritableSandboxBindHostRoots as a, resolveSandboxFsPathWithMounts as i, hasSandboxBindContainerPathAliases as n, hasSandboxBindReadonlyHostShadows as r, buildSandboxFsMounts as t };