mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
refactor(codex): require PathUri sandbox inputs (#108772)
* refactor(codex): require PathUri sandbox inputs * test(codex): use PathUri lifecycle fixture
This commit is contained in:
committed by
GitHub
parent
f771aa54e8
commit
30f8146b38
@@ -71,7 +71,7 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/writeFile", {
|
||||
path: "/workspace/missing/note.txt",
|
||||
path: "file:///workspace/missing/note.txt",
|
||||
dataBase64: Buffer.from("hello").toString("base64"),
|
||||
}),
|
||||
).rejects.toThrow("parent directory not found");
|
||||
@@ -94,7 +94,7 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/writeFile", {
|
||||
path: "/workspace/read-only.txt",
|
||||
path: "file:///workspace/read-only.txt",
|
||||
dataBase64: Buffer.from("blocked").toString("base64"),
|
||||
sandbox: codexFsSandboxContext({
|
||||
entries: [{ path: specialPath("root"), access: "read" }],
|
||||
@@ -102,7 +102,7 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
}),
|
||||
).rejects.toThrow("Codex fs sandbox denied write access");
|
||||
await rpc(socket, "fs/writeFile", {
|
||||
path: "/workspace/allowed.txt",
|
||||
path: "file:///workspace/allowed.txt",
|
||||
dataBase64: Buffer.from("allowed").toString("base64"),
|
||||
sandbox: codexFsSandboxContext({
|
||||
entries: [
|
||||
@@ -143,14 +143,14 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/writeFile", {
|
||||
path: "/workspace/.git/config",
|
||||
path: "file:///workspace/.git/config",
|
||||
dataBase64: Buffer.from("blocked").toString("base64"),
|
||||
sandbox: workspacePolicy,
|
||||
}),
|
||||
).rejects.toThrow("Codex fs sandbox denied write access");
|
||||
await expect(
|
||||
rpc(socket, "fs/remove", {
|
||||
path: "/workspace",
|
||||
path: "file:///workspace",
|
||||
recursive: true,
|
||||
force: true,
|
||||
sandbox: workspacePolicy,
|
||||
@@ -185,13 +185,13 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/readFile", {
|
||||
path: "/workspace/private/secret.txt",
|
||||
path: "file:///workspace/private/secret.txt",
|
||||
sandbox: policy,
|
||||
}),
|
||||
).rejects.toThrow("Codex fs sandbox denied read access");
|
||||
await expect(
|
||||
rpc(socket, "fs/readFile", {
|
||||
path: "/workspace/key.pem",
|
||||
path: "file:///workspace/key.pem",
|
||||
sandbox: codexFsSandboxContext({
|
||||
entries: [
|
||||
{ path: specialPath("root"), access: "read" },
|
||||
@@ -203,7 +203,7 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
).rejects.toThrow("Codex fs sandbox denied read access");
|
||||
await expect(
|
||||
rpc(socket, "fs/readFile", {
|
||||
path: "/workspace/KEY.PEM",
|
||||
path: "file:///workspace/KEY.PEM",
|
||||
sandbox: codexFsSandboxContext({
|
||||
entries: [
|
||||
{ path: specialPath("root"), access: "read" },
|
||||
@@ -214,13 +214,13 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
}),
|
||||
).rejects.toThrow("Codex fs sandbox denied read access");
|
||||
await rpc(socket, "fs/writeFile", {
|
||||
path: "/workspace/private/nested/allowed.txt",
|
||||
path: "file:///workspace/private/nested/allowed.txt",
|
||||
dataBase64: Buffer.from("ok").toString("base64"),
|
||||
sandbox: policy,
|
||||
});
|
||||
await expect(
|
||||
rpc(socket, "fs/remove", {
|
||||
path: "/workspace/private",
|
||||
path: "file:///workspace/private",
|
||||
recursive: true,
|
||||
force: true,
|
||||
sandbox: policy,
|
||||
@@ -246,7 +246,7 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
socket.send(JSON.stringify({ method: "initialized" }));
|
||||
|
||||
await rpc(socket, "fs/writeFile", {
|
||||
path: "/workspace/allowed.txt",
|
||||
path: "file:///workspace/allowed.txt",
|
||||
dataBase64: Buffer.from("ok").toString("base64"),
|
||||
sandbox: codexFsSandboxContext({
|
||||
entries: [
|
||||
@@ -279,7 +279,7 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/readFile", {
|
||||
path: "/workspace/key.pem",
|
||||
path: "file:///workspace/key.pem",
|
||||
sandbox: codexFsSandboxContext({
|
||||
entries: [
|
||||
{ path: specialPath("root"), access: "read" },
|
||||
@@ -315,7 +315,7 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/remove", {
|
||||
path: "/workspace/src",
|
||||
path: "file:///workspace/src",
|
||||
recursive: true,
|
||||
force: true,
|
||||
sandbox: policy,
|
||||
@@ -414,8 +414,8 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/copy", {
|
||||
sourcePath: "/workspace/source-dir",
|
||||
destinationPath: "/workspace/source-dir/backup",
|
||||
sourcePath: "file:///workspace/source-dir",
|
||||
destinationPath: "file:///workspace/source-dir/backup",
|
||||
recursive: true,
|
||||
}),
|
||||
).rejects.toThrow("Cannot recursively copy a directory into itself");
|
||||
@@ -435,9 +435,9 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
await rpc(socket, "initialize", { clientName: "test" });
|
||||
socket.send(JSON.stringify({ method: "initialized" }));
|
||||
|
||||
await expect(rpc(socket, "fs/getMetadata", { path: "/workspace/missing" })).rejects.toThrow(
|
||||
"file not found",
|
||||
);
|
||||
await expect(
|
||||
rpc(socket, "fs/getMetadata", { path: "file:///workspace/missing" }),
|
||||
).rejects.toThrow("file not found");
|
||||
socket.close();
|
||||
});
|
||||
|
||||
@@ -460,9 +460,9 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
await rpc(socket, "initialize", { clientName: "test" });
|
||||
socket.send(JSON.stringify({ method: "initialized" }));
|
||||
|
||||
await expect(rpc(socket, "fs/readFile", { path: "/workspace/huge.bin" })).rejects.toThrow(
|
||||
"file is too large to read through Codex sandbox exec-server",
|
||||
);
|
||||
await expect(
|
||||
rpc(socket, "fs/readFile", { path: "file:///workspace/huge.bin" }),
|
||||
).rejects.toThrow("file is too large to read through Codex sandbox exec-server");
|
||||
|
||||
expect(readFile).not.toHaveBeenCalled();
|
||||
socket.close();
|
||||
@@ -486,14 +486,14 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/createDirectory", {
|
||||
path: "/workspace/missing/child",
|
||||
path: "file:///workspace/missing/child",
|
||||
recursive: false,
|
||||
}),
|
||||
).rejects.toThrow("parent directory not found");
|
||||
expect(mkdirp).not.toHaveBeenCalled();
|
||||
|
||||
await rpc(socket, "fs/createDirectory", {
|
||||
path: "/workspace/existing/child",
|
||||
path: "file:///workspace/existing/child",
|
||||
recursive: false,
|
||||
});
|
||||
expect(mkdirp).toHaveBeenCalledWith({ filePath: "/workspace/existing/child" });
|
||||
@@ -517,7 +517,7 @@ describe("OpenClaw Codex sandbox exec-server filesystem", () => {
|
||||
|
||||
await expect(
|
||||
rpc(socket, "fs/writeFile", {
|
||||
path: "/outside/note.txt",
|
||||
path: "file:///outside/note.txt",
|
||||
dataBase64: Buffer.from("no").toString("base64"),
|
||||
}),
|
||||
).rejects.toThrow("sandbox denied write outside workspace");
|
||||
|
||||
@@ -47,7 +47,7 @@ function processStartParams(processId: string) {
|
||||
return {
|
||||
processId,
|
||||
argv: ["sh", "-lc", "true"],
|
||||
cwd: "/workspace",
|
||||
cwd: "file:///workspace",
|
||||
env: {},
|
||||
tty: false,
|
||||
pipeStdin: false,
|
||||
|
||||
@@ -213,6 +213,8 @@ describe("OpenClaw Codex sandbox exec-server", () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
["a native absolute path", "/workspace"],
|
||||
["a relative path", "workspace"],
|
||||
["a non-file scheme", "https://example.test/workspace"],
|
||||
["a remote file authority", "file://remote.example.test/workspace"],
|
||||
["a query", "file:///workspace?revision=1"],
|
||||
@@ -287,7 +289,7 @@ describe("OpenClaw Codex sandbox exec-server", () => {
|
||||
rpc(socket, "process/start", {
|
||||
processId: "proc-arg0",
|
||||
argv: ["/bin/sh", "-lc", "true"],
|
||||
cwd: "/workspace",
|
||||
cwd: "file:///workspace",
|
||||
env: {},
|
||||
tty: false,
|
||||
pipeStdin: false,
|
||||
@@ -318,7 +320,7 @@ describe("OpenClaw Codex sandbox exec-server", () => {
|
||||
await rpc(socket, "process/start", {
|
||||
processId: "proc-stdin",
|
||||
argv: ["/bin/sh", "-lc", "cat"],
|
||||
cwd: "/workspace",
|
||||
cwd: "file:///workspace",
|
||||
env: {},
|
||||
tty: false,
|
||||
pipeStdin: true,
|
||||
@@ -356,7 +358,7 @@ describe("OpenClaw Codex sandbox exec-server", () => {
|
||||
await rpc(socket, "process/start", {
|
||||
processId: "proc-tty",
|
||||
argv: ["/bin/sh", "-lc", "cat"],
|
||||
cwd: "/workspace",
|
||||
cwd: "file:///workspace",
|
||||
env: {},
|
||||
tty: true,
|
||||
pipeStdin: false,
|
||||
@@ -403,7 +405,7 @@ describe("OpenClaw Codex sandbox exec-server", () => {
|
||||
await rpc(socket, "process/start", {
|
||||
processId: "proc-secret-env",
|
||||
argv: ["/bin/sh", "-lc", "true"],
|
||||
cwd: "/workspace",
|
||||
cwd: "file:///workspace",
|
||||
env: {},
|
||||
envPolicy: {
|
||||
inherit: "all",
|
||||
@@ -449,7 +451,7 @@ describe("OpenClaw Codex sandbox exec-server", () => {
|
||||
await rpc(socket, "process/start", {
|
||||
processId: "proc-cursor",
|
||||
argv: [process.execPath, "-e", "ignored"],
|
||||
cwd: "/workspace",
|
||||
cwd: "file:///workspace",
|
||||
env: {},
|
||||
tty: false,
|
||||
pipeStdin: false,
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
/** Converts Codex PathUri protocol values into sandbox-backend path strings. */
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const URI_SCHEME_RE = /^([A-Za-z][A-Za-z0-9+.-]*):/u;
|
||||
const WINDOWS_ABSOLUTE_PATH_RE = /^[A-Za-z]:[\\/]/u;
|
||||
const WINDOWS_DRIVE_PATH_RE = /^\/[A-Za-z]:(?:\/|$)/u;
|
||||
|
||||
/** Resolves one Codex exec-server path while retaining legacy native absolute paths. */
|
||||
/** Resolves one Codex exec-server PathUri into a POSIX sandbox path. */
|
||||
export function resolveExecServerPath(rawPath: string, label: string): string {
|
||||
const scheme = WINDOWS_ABSOLUTE_PATH_RE.test(rawPath)
|
||||
? undefined
|
||||
: URI_SCHEME_RE.exec(rawPath)?.[1]?.toLowerCase();
|
||||
if (!scheme) {
|
||||
// Codex versions before PathUri sent native absolute paths here.
|
||||
return rawPath;
|
||||
}
|
||||
if (scheme !== "file") {
|
||||
throw new Error(`${label} URI must use the file scheme, received ${scheme}.`);
|
||||
}
|
||||
|
||||
let pathUrl: URL;
|
||||
try {
|
||||
pathUrl = new URL(rawPath);
|
||||
@@ -27,6 +14,11 @@ export function resolveExecServerPath(rawPath: string, label: string): string {
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
if (pathUrl.protocol !== "file:") {
|
||||
throw new Error(
|
||||
`${label} URI must use the file scheme, received ${pathUrl.protocol.slice(0, -1)}.`,
|
||||
);
|
||||
}
|
||||
if (pathUrl.search || pathUrl.hash) {
|
||||
throw new Error(`${label} file URI must not include a query or fragment.`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user