openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
81 lines (80 loc) • 3.99 kB
JavaScript
import "./fs-safe-defaults-B7hUN42l.js";
import { i as isPathInside } from "./path-BlG8lhgR.js";
import path from "node:path";
import fs from "node:fs/promises";
import { createHash } from "node:crypto";
//#region node_modules/@openclaw/fs-safe/dist/install-path.js
function safeDirName(input) {
const trimmed = input.trim();
if (!trimmed) return trimmed;
return trimmed.replaceAll("/", "__").replaceAll("\\", "__");
}
function safePathSegmentHashed(input) {
const trimmed = input.trim();
const base = trimmed.replaceAll(/[\\/]/g, "-").replaceAll(/[^a-zA-Z0-9._-]/g, "-").replaceAll(/-+/g, "-").replaceAll(/^-+/g, "").replaceAll(/-+$/g, "");
const normalized = base.length > 0 ? base : "skill";
const safe = normalized === "." || normalized === ".." ? "skill" : normalized;
const hash = createHash("sha256").update(trimmed).digest("hex").slice(0, 10);
if (safe !== trimmed) return `${safe.length > 50 ? safe.slice(0, 50) : safe}-${hash}`;
if (safe.length > 60) return `${safe.slice(0, 50)}-${hash}`;
return safe;
}
function resolveSafeInstallDir(params) {
const encodedName = (params.nameEncoder ?? safeDirName)(params.id);
const targetDir = path.join(params.baseDir, encodedName);
const resolvedBase = path.resolve(params.baseDir);
const resolvedTarget = path.resolve(targetDir);
const relative = path.relative(resolvedBase, resolvedTarget);
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) return {
ok: false,
error: params.invalidNameMessage
};
return {
ok: true,
path: targetDir
};
}
async function assertCanonicalPathWithinBase(params) {
const baseDir = path.resolve(params.baseDir);
const candidatePath = path.resolve(params.candidatePath);
if (!isPathInside(baseDir, candidatePath)) throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
const baseLstat = await fs.lstat(baseDir);
if (baseLstat.isSymbolicLink()) {
if (!(await fs.stat(baseDir)).isDirectory()) throw new Error(`Invalid ${params.boundaryLabel}: base directory must resolve to a directory`);
} else if (!baseLstat.isDirectory()) throw new Error(`Invalid ${params.boundaryLabel}: base directory must be a directory`);
const baseRealPath = await fs.realpath(baseDir);
const validateDirectory = async (dirPath) => {
const resolvedDirPath = path.resolve(dirPath);
const dirLstat = await fs.lstat(dirPath);
if (dirLstat.isSymbolicLink()) {
if (resolvedDirPath !== baseDir) throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
if (!(await fs.stat(dirPath)).isDirectory()) throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
} else if (!dirLstat.isDirectory()) throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
if (!isPathInside(baseRealPath, await fs.realpath(dirPath))) throw new Error(`Invalid path: must stay within ${params.boundaryLabel}`);
};
try {
await validateDirectory(candidatePath);
return;
} catch (err) {
if (err.code !== "ENOENT") throw err;
}
await validateDirectory(path.dirname(candidatePath));
}
//#endregion
//#region src/infra/install-safe-path.ts
/** Returns the package basename for scoped npm names while preserving plain ids. */
function unscopedPackageName(name) {
const trimmed = name.trim();
if (!trimmed) return trimmed;
return trimmed.includes("/") ? trimmed.split("/").pop() ?? trimmed : trimmed;
}
/** Matches a requested install id against either the full package name or unscoped basename. */
function packageNameMatchesId(packageName, id) {
const trimmedId = id.trim();
if (!trimmedId) return false;
const trimmedPackageName = packageName.trim();
if (!trimmedPackageName) return false;
return trimmedId === trimmedPackageName || trimmedId === unscopedPackageName(trimmedPackageName);
}
//#endregion
export { safeDirName as a, resolveSafeInstallDir as i, unscopedPackageName as n, safePathSegmentHashed as o, assertCanonicalPathWithinBase as r, packageNameMatchesId as t };