mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 07:40:21 +00:00
92 lines
2.6 KiB
TypeScript
92 lines
2.6 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { assertNoWindowsNetworkPath } from "../infra/local-file-access.js";
|
|
import { getDefaultMediaLocalRoots } from "./local-roots.js";
|
|
|
|
export type LocalMediaAccessErrorCode =
|
|
| "path-not-allowed"
|
|
| "invalid-root"
|
|
| "invalid-file-url"
|
|
| "network-path-not-allowed"
|
|
| "unsafe-bypass"
|
|
| "not-found"
|
|
| "invalid-path"
|
|
| "not-file";
|
|
|
|
export class LocalMediaAccessError extends Error {
|
|
code: LocalMediaAccessErrorCode;
|
|
|
|
constructor(code: LocalMediaAccessErrorCode, message: string, options?: ErrorOptions) {
|
|
super(message, options);
|
|
this.code = code;
|
|
this.name = "LocalMediaAccessError";
|
|
}
|
|
}
|
|
|
|
export function getDefaultLocalRoots(): readonly string[] {
|
|
return getDefaultMediaLocalRoots();
|
|
}
|
|
|
|
export async function assertLocalMediaAllowed(
|
|
mediaPath: string,
|
|
localRoots: readonly string[] | "any" | undefined,
|
|
): Promise<void> {
|
|
if (localRoots === "any") {
|
|
return;
|
|
}
|
|
try {
|
|
assertNoWindowsNetworkPath(mediaPath, "Local media path");
|
|
} catch (err) {
|
|
throw new LocalMediaAccessError("network-path-not-allowed", (err as Error).message, {
|
|
cause: err,
|
|
});
|
|
}
|
|
const roots = localRoots ?? getDefaultLocalRoots();
|
|
let resolved: string;
|
|
try {
|
|
resolved = await fs.realpath(mediaPath);
|
|
} catch {
|
|
resolved = path.resolve(mediaPath);
|
|
}
|
|
|
|
if (localRoots === undefined) {
|
|
const workspaceRoot = roots.find((root) => path.basename(root) === "workspace");
|
|
if (workspaceRoot) {
|
|
const stateDir = path.dirname(workspaceRoot);
|
|
const rel = path.relative(stateDir, resolved);
|
|
if (rel && !rel.startsWith("..") && !path.isAbsolute(rel)) {
|
|
const firstSegment = rel.split(path.sep)[0] ?? "";
|
|
if (firstSegment.startsWith("workspace-")) {
|
|
throw new LocalMediaAccessError(
|
|
"path-not-allowed",
|
|
`Local media path is not under an allowed directory: ${mediaPath}`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const root of roots) {
|
|
let resolvedRoot: string;
|
|
try {
|
|
resolvedRoot = await fs.realpath(root);
|
|
} catch {
|
|
resolvedRoot = path.resolve(root);
|
|
}
|
|
if (resolvedRoot === path.parse(resolvedRoot).root) {
|
|
throw new LocalMediaAccessError(
|
|
"invalid-root",
|
|
`Invalid localRoots entry (refuses filesystem root): ${root}. Pass a narrower directory.`,
|
|
);
|
|
}
|
|
if (resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path.sep)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new LocalMediaAccessError(
|
|
"path-not-allowed",
|
|
`Local media path is not under an allowed directory: ${mediaPath}`,
|
|
);
|
|
}
|