mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-26 08:31:55 +00:00
OpenShell: constrain mirror sync roots (#58515)
* fix(openshell): constrain mirror sync roots * fix(openshell): restore config test types * fix(openshell): simplify managed root sync
This commit is contained in:
72
extensions/openshell/src/config.test.ts
Normal file
72
extensions/openshell/src/config.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import fsSync from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createOpenShellPluginConfigSchema, resolveOpenShellPluginConfig } from "./config.js";
|
||||
|
||||
describe("openshell plugin config", () => {
|
||||
it("applies defaults", () => {
|
||||
expect(resolveOpenShellPluginConfig(undefined)).toEqual({
|
||||
mode: "mirror",
|
||||
command: "openshell",
|
||||
gateway: undefined,
|
||||
gatewayEndpoint: undefined,
|
||||
from: "openclaw",
|
||||
policy: undefined,
|
||||
providers: [],
|
||||
gpu: false,
|
||||
autoProviders: true,
|
||||
remoteWorkspaceDir: "/sandbox",
|
||||
remoteAgentWorkspaceDir: "/agent",
|
||||
timeoutMs: 120_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts remote mode", () => {
|
||||
expect(resolveOpenShellPluginConfig({ mode: "remote" }).mode).toBe("remote");
|
||||
});
|
||||
|
||||
it("rejects relative remote paths", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
remoteWorkspaceDir: "sandbox",
|
||||
}),
|
||||
).toThrow("OpenShell remoteWorkspaceDir must be absolute");
|
||||
});
|
||||
|
||||
it("rejects remote paths outside managed sandbox roots", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
remoteWorkspaceDir: "/tmp/victim",
|
||||
}),
|
||||
).toThrow("OpenShell remoteWorkspaceDir must stay under /sandbox or /agent");
|
||||
});
|
||||
|
||||
it("normalizes managed sandbox subpaths", () => {
|
||||
expect(
|
||||
resolveOpenShellPluginConfig({
|
||||
remoteWorkspaceDir: "/sandbox/../sandbox/project",
|
||||
remoteAgentWorkspaceDir: "/agent/./session",
|
||||
}),
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
remoteWorkspaceDir: "/sandbox/project",
|
||||
remoteAgentWorkspaceDir: "/agent/session",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects unknown mode", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
mode: "bogus",
|
||||
}),
|
||||
).toThrow("mode must be one of mirror, remote");
|
||||
});
|
||||
|
||||
it("keeps the runtime json schema in sync with the manifest config schema", () => {
|
||||
const manifest = JSON.parse(
|
||||
fsSync.readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf8"),
|
||||
) as { configSchema?: unknown };
|
||||
|
||||
expect(createOpenShellPluginConfigSchema().jsonSchema).toEqual(manifest.configSchema);
|
||||
});
|
||||
});
|
||||
@@ -38,6 +38,10 @@ const DEFAULT_SOURCE = "openclaw";
|
||||
const DEFAULT_REMOTE_WORKSPACE_DIR = "/sandbox";
|
||||
const DEFAULT_REMOTE_AGENT_WORKSPACE_DIR = "/agent";
|
||||
const DEFAULT_TIMEOUT_MS = 120_000;
|
||||
const OPEN_SHELL_MANAGED_REMOTE_ROOTS = [
|
||||
DEFAULT_REMOTE_WORKSPACE_DIR,
|
||||
DEFAULT_REMOTE_AGENT_WORKSPACE_DIR,
|
||||
] as const;
|
||||
|
||||
function normalizeProviders(value: string[] | undefined): string[] {
|
||||
const seen = new Set<string>();
|
||||
@@ -100,11 +104,26 @@ function formatOpenShellConfigIssue(issue: z.ZodIssue | undefined): string {
|
||||
return issue.message;
|
||||
}
|
||||
|
||||
function normalizeRemotePath(value: string | undefined, fallback: string): string {
|
||||
function isManagedOpenShellRemotePath(value: string): boolean {
|
||||
return OPEN_SHELL_MANAGED_REMOTE_ROOTS.some(
|
||||
(root) => value === root || value.startsWith(`${root}/`),
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeOpenShellRemotePath(
|
||||
value: string | undefined,
|
||||
fallback: string,
|
||||
fieldName = "remote path",
|
||||
): string {
|
||||
const candidate = value ?? fallback;
|
||||
const normalized = path.posix.normalize(candidate.trim() || fallback);
|
||||
if (!normalized.startsWith("/")) {
|
||||
throw new Error(`OpenShell remote path must be absolute: ${candidate}`);
|
||||
throw new Error(`OpenShell ${fieldName} must be absolute: ${candidate}`);
|
||||
}
|
||||
if (!isManagedOpenShellRemotePath(normalized)) {
|
||||
throw new Error(
|
||||
`OpenShell ${fieldName} must stay under ${OPEN_SHELL_MANAGED_REMOTE_ROOTS.join(" or ")}: ${candidate}`,
|
||||
);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
@@ -137,6 +156,8 @@ export function createOpenShellPluginConfigSchema(): OpenClawPluginConfigSchema
|
||||
|
||||
export function resolveOpenShellPluginConfig(value: unknown): ResolvedOpenShellPluginConfig {
|
||||
if (value === undefined) {
|
||||
// The built-in defaults are managed OpenShell roots, so they do not need to
|
||||
// flow back through normalizeOpenShellRemotePath.
|
||||
return {
|
||||
mode: DEFAULT_MODE,
|
||||
command: DEFAULT_COMMAND,
|
||||
@@ -170,10 +191,15 @@ export function resolveOpenShellPluginConfig(value: unknown): ResolvedOpenShellP
|
||||
providers: normalizeProviders(cfg.providers),
|
||||
gpu: cfg.gpu ?? false,
|
||||
autoProviders: cfg.autoProviders ?? true,
|
||||
remoteWorkspaceDir: normalizeRemotePath(cfg.remoteWorkspaceDir, DEFAULT_REMOTE_WORKSPACE_DIR),
|
||||
remoteAgentWorkspaceDir: normalizeRemotePath(
|
||||
remoteWorkspaceDir: normalizeOpenShellRemotePath(
|
||||
cfg.remoteWorkspaceDir,
|
||||
DEFAULT_REMOTE_WORKSPACE_DIR,
|
||||
"remoteWorkspaceDir",
|
||||
),
|
||||
remoteAgentWorkspaceDir: normalizeOpenShellRemotePath(
|
||||
cfg.remoteAgentWorkspaceDir,
|
||||
DEFAULT_REMOTE_AGENT_WORKSPACE_DIR,
|
||||
"remoteAgentWorkspaceDir",
|
||||
),
|
||||
timeoutMs:
|
||||
typeof cfg.timeoutSeconds === "number"
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
setBundledOpenShellCommandResolverForTest,
|
||||
shellEscape,
|
||||
} from "./cli.js";
|
||||
import { createOpenShellPluginConfigSchema, resolveOpenShellPluginConfig } from "./config.js";
|
||||
import { resolveOpenShellPluginConfig } from "./config.js";
|
||||
|
||||
const cliMocks = vi.hoisted(() => ({
|
||||
runOpenShellCli: vi.fn(),
|
||||
@@ -20,53 +20,6 @@ const cliMocks = vi.hoisted(() => ({
|
||||
|
||||
let createOpenShellSandboxBackendManager: typeof import("./backend.js").createOpenShellSandboxBackendManager;
|
||||
|
||||
describe("openshell plugin config", () => {
|
||||
it("applies defaults", () => {
|
||||
expect(resolveOpenShellPluginConfig(undefined)).toEqual({
|
||||
mode: "mirror",
|
||||
command: "openshell",
|
||||
gateway: undefined,
|
||||
gatewayEndpoint: undefined,
|
||||
from: "openclaw",
|
||||
policy: undefined,
|
||||
providers: [],
|
||||
gpu: false,
|
||||
autoProviders: true,
|
||||
remoteWorkspaceDir: "/sandbox",
|
||||
remoteAgentWorkspaceDir: "/agent",
|
||||
timeoutMs: 120_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts remote mode", () => {
|
||||
expect(resolveOpenShellPluginConfig({ mode: "remote" }).mode).toBe("remote");
|
||||
});
|
||||
|
||||
it("rejects relative remote paths", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
remoteWorkspaceDir: "sandbox",
|
||||
}),
|
||||
).toThrow("OpenShell remote path must be absolute");
|
||||
});
|
||||
|
||||
it("rejects unknown mode", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
mode: "bogus",
|
||||
}),
|
||||
).toThrow("mode must be one of mirror, remote");
|
||||
});
|
||||
|
||||
it("keeps the runtime json schema in sync with the manifest config schema", () => {
|
||||
const manifest = JSON.parse(
|
||||
fsSync.readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf8"),
|
||||
) as { configSchema?: unknown };
|
||||
|
||||
expect(createOpenShellPluginConfigSchema().jsonSchema).toEqual(manifest.configSchema);
|
||||
});
|
||||
});
|
||||
|
||||
describe("openshell cli helpers", () => {
|
||||
afterEach(() => {
|
||||
setBundledOpenShellCommandResolverForTest();
|
||||
|
||||
Reference in New Issue
Block a user