Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Openclaw/extensions/openshell/src/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 20 kB image not shown  

Quelle  openshell-core.test.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import nodeFs from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { createSandboxTestContext } from "../../../src/agents/sandbox/test-fixtures.js";
import type { OpenShellSandboxBackend } from "./backend.js";
import {
  buildExecRemoteCommand,
  buildOpenShellBaseArgv,
  resolveOpenShellCommand,
  setBundledOpenShellCommandResolverForTest,
  shellEscape,
} from "./cli.js";
import { resolveOpenShellPluginConfig } from "./config.js";

const cliMocks = vi.hoisted(() => ({
  runOpenShellCli: vi.fn(),
}));

let createOpenShellSandboxBackendManager: typeof import("./backend.js").createOpenShellSandboxBackendManager;

describe("openshell cli helpers", () => {
  afterEach(() => {
    setBundledOpenShellCommandResolverForTest();
  });

  it("builds base argv with gateway overrides", () => {
    const config = resolveOpenShellPluginConfig({
      command: "/usr/local/bin/openshell",
      gateway: "lab",
      gatewayEndpoint: "https://lab.example",
    });
    expect(buildOpenShellBaseArgv(config)).toEqual([
      "/usr/local/bin/openshell",
      "--gateway",
      "lab",
      "--gateway-endpoint",
      "https://lab.example",
    ]);
  });

  it("prefers the bundled openshell command when available", () => {
    setBundledOpenShellCommandResolverForTest(() => "/tmp/node_modules/.bin/openshell");
    const config = resolveOpenShellPluginConfig(undefined);

    expect(resolveOpenShellCommand("openshell")).toBe("/tmp/node_modules/.bin/openshell");
    expect(buildOpenShellBaseArgv(config)).toEqual(["/tmp/node_modules/.bin/openshell"]);
  });

  it("falls back to the PATH command when no bundled openshell is present", () => {
    setBundledOpenShellCommandResolverForTest(() => null);

    expect(resolveOpenShellCommand("openshell")).toBe("openshell");
  });

  it("shell escapes single quotes", () => {
    expect(shellEscape(`a'b`)).toBe(`'a'"'"'b'`);
  });

  it("wraps exec commands with env and workdir", () => {
    const command = buildExecRemoteCommand({
      command: "pwd && printenv TOKEN",
      workdir: "/sandbox/project",
      env: {
        TOKEN: "abc 123",
      },
    });
    expect(command).toContain(`'env'`);
    expect(command).toContain(`'TOKEN=abc 123'`);
    expect(command).toContain(`'cd '"'"'/sandbox/project'"'"' && pwd && printenv TOKEN'`);
  });
});

describe("openshell backend manager", () => {
  beforeAll(async () => {
    vi.doMock("./cli.js", async () => {
      const actual = await vi.importActual<typeof import("./cli.js")>("./cli.js");
      return {
        ...actual,
        runOpenShellCli: cliMocks.runOpenShellCli,
      };
    });
    ({ createOpenShellSandboxBackendManager } = await import("./backend.js"));
  });

  afterAll(() => {
    vi.doUnmock("./cli.js");
  });

  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("checks runtime status with config override from OpenClaw config", async () => {
    cliMocks.runOpenShellCli.mockResolvedValue({
      code: 0,
      stdout: "{}",
      stderr: "",
    });

    const manager = createOpenShellSandboxBackendManager({
      pluginConfig: resolveOpenShellPluginConfig({
        command: "openshell",
        from: "openclaw",
      }),
    });

    const result = await manager.describeRuntime({
      entry: {
        containerName: "openclaw-session-1234",
        backendId: "openshell",
        runtimeLabel: "openclaw-session-1234",
        sessionKey: "agent:main",
        createdAtMs: 1,
        lastUsedAtMs: 1,
        image: "custom-source",
        configLabelKind: "Source",
      },
      config: {
        plugins: {
          entries: {
            openshell: {
              enabled: true,
              config: {
                command: "openshell",
                from: "custom-source",
              },
            },
          },
        },
      },
    });

    expect(result).toEqual({
      running: true,
      actualConfigLabel: "custom-source",
      configLabelMatch: true,
    });
    expect(cliMocks.runOpenShellCli).toHaveBeenCalledWith({
      context: expect.objectContaining({
        sandboxName: "openclaw-session-1234",
        config: expect.objectContaining({
          from: "custom-source",
        }),
      }),
      args: ["sandbox", "get", "openclaw-session-1234"],
    });
  });

  it("removes runtimes via openshell sandbox delete", async () => {
    cliMocks.runOpenShellCli.mockResolvedValue({
      code: 0,
      stdout: "",
      stderr: "",
    });

    const manager = createOpenShellSandboxBackendManager({
      pluginConfig: resolveOpenShellPluginConfig({
        command: "/usr/local/bin/openshell",
        gateway: "lab",
      }),
    });

    await manager.removeRuntime({
      entry: {
        containerName: "openclaw-session-5678",
        backendId: "openshell",
        runtimeLabel: "openclaw-session-5678",
        sessionKey: "agent:main",
        createdAtMs: 1,
        lastUsedAtMs: 1,
        image: "openclaw",
        configLabelKind: "Source",
      },
      config: {},
    });

    expect(cliMocks.runOpenShellCli).toHaveBeenCalledWith({
      context: expect.objectContaining({
        sandboxName: "openclaw-session-5678",
        config: expect.objectContaining({
          command: "/usr/local/bin/openshell",
          gateway: "lab",
        }),
      }),
      args: ["sandbox", "delete", "openclaw-session-5678"],
    });
  });
});

const tempDirs: string[] = [];

async function makeTempDir(prefix: string) {
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
  tempDirs.push(dir);
  return dir;
}

afterEach(async () => {
  await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
});

function cloneStatWithDev<T extends nodeFs.Stats | nodeFs.BigIntStats>(
  stat: T,
  dev: number | bigint,
): T {
  return Object.defineProperty(
    Object.create(Object.getPrototypeOf(stat), Object.getOwnPropertyDescriptors(stat)),
    "dev",
    {
      value: dev,
      configurable: true,
      enumerable: true,
      writable: true,
    },
  ) as T;
}

function createMirrorBackendMock(): OpenShellSandboxBackend {
  return {
    id: "openshell",
    runtimeId: "openshell-test",
    runtimeLabel: "openshell-test",
    workdir: "/sandbox",
    env: {},
    remoteWorkspaceDir: "/sandbox",
    remoteAgentWorkspaceDir: "/agent",
    buildExecSpec: vi.fn(),
    runShellCommand: vi.fn(),
    runRemoteShellScript: vi.fn().mockResolvedValue({
      stdout: Buffer.alloc(0),
      stderr: Buffer.alloc(0),
      code: 0,
    }),
    syncLocalPathToRemote: vi.fn().mockResolvedValue(undefined),
  } as unknown as OpenShellSandboxBackend;
}

describe("openshell fs bridges", () => {
  it("writes locally and syncs the file to the remote workspace", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir: workspaceDir,
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge } = await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });
    await bridge.writeFile({
      filePath: "nested/file.txt",
      data: "hello",
      mkdir: true,
    });

    expect(await fs.readFile(path.join(workspaceDir, "nested", "file.txt"), "utf8")).toBe("hello");
    expect(backend.syncLocalPathToRemote).toHaveBeenCalledWith(
      path.join(workspaceDir, "nested", "file.txt"),
      "/sandbox/nested/file.txt",
    );
  });

  it("rejects symlink-parent writes instead of escaping the local mount root", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    const outsideDir = await makeTempDir("openclaw-openshell-outside-");
    await fs.symlink(outsideDir, path.join(workspaceDir, "alias"));
    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir: workspaceDir,
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge } = await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });

    await expect(
      bridge.writeFile({
        filePath: "alias/escape.txt",
        data: "owned",
        mkdir: true,
      }),
    ).rejects.toThrow();
    await expect(fs.stat(path.join(outsideDir, "escape.txt"))).rejects.toThrow();
    await expect(fs.readdir(outsideDir)).resolves.toEqual([]);
    expect(backend.syncLocalPathToRemote).not.toHaveBeenCalled();
  });

  it("rejects writes whose final target is a symlink inside the local mount root", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    const linkedTarget = path.join(workspaceDir, "existing.txt");
    await fs.writeFile(linkedTarget, "keep", "utf8");
    await fs.symlink("existing.txt", path.join(workspaceDir, "link.txt"));
    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir: workspaceDir,
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge } = await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });

    await expect(
      bridge.writeFile({
        filePath: "link.txt",
        data: "owned",
        mkdir: true,
      }),
    ).rejects.toThrow();
    await expect(fs.readlink(path.join(workspaceDir, "link.txt"))).resolves.toBe("existing.txt");
    await expect(fs.readFile(linkedTarget, "utf8")).resolves.toBe("keep");
    expect(backend.syncLocalPathToRemote).not.toHaveBeenCalled();
  });

  it("rejects a parent symlink swap that lands outside the sandbox root", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    const outsideDir = await makeTempDir("openclaw-openshell-outside-");
    await fs.mkdir(path.join(workspaceDir, "subdir"), { recursive: true });
    await fs.writeFile(path.join(workspaceDir, "subdir", "secret.txt"), "inside", "utf8");
    await fs.writeFile(path.join(outsideDir, "secret.txt"), "outside", "utf8");
    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir: workspaceDir,
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge } = await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });
    const originalOpen = fs.open.bind(fs);
    const targetPath = path.join(workspaceDir, "subdir", "secret.txt");
    let swapped = false;
    const openSpy = vi.spyOn(fs, "open").mockImplementation((async (...args: unknown[]) => {
      const filePath = args[0];
      if (!swapped && filePath === targetPath) {
        swapped = true;
        nodeFs.rmSync(path.join(workspaceDir, "subdir"), { recursive: true, force: true });
        nodeFs.symlinkSync(outsideDir, path.join(workspaceDir, "subdir"));
      }
      return await (originalOpen as (...delegated: unknown[]) => Promise<unknown>)(...args);
    }) as unknown as typeof fs.open);

    try {
      await expect(bridge.readFile({ filePath: "subdir/secret.txt" })).rejects.toThrow(
        "Sandbox boundary checks failed",
      );
      expect(openSpy).toHaveBeenCalled();
    } finally {
      openSpy.mockRestore();
    }
  });

  it("falls back to inode checks when fd path resolution is unavailable", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    await fs.mkdir(path.join(workspaceDir, "subdir"), { recursive: true });
    await fs.writeFile(path.join(workspaceDir, "subdir", "secret.txt"), "inside", "utf8");

    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir: workspaceDir,
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge } = await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });
    const readlinkSpy = vi
      .spyOn(fs, "readlink")
      .mockRejectedValue(new Error("fd path unavailable"));

    try {
      await expect(bridge.readFile({ filePath: "subdir/secret.txt" })).resolves.toEqual(
        Buffer.from("inside"),
      );
      expect(readlinkSpy).toHaveBeenCalled();
    } finally {
      readlinkSpy.mockRestore();
    }
  });

  // The shared `sameFileIdentity` contract intentionally treats either-side
  // `dev=0` as "unknown device" on win32 (path-based stat can legitimately
  // report `dev=0` there) and only fails closed on other platforms. Skip the
  // Linux/macOS rejection expectation on Windows runners.
  it.skipIf(process.platform === "win32")(
    "rejects fallback reads when path stats report an unknown device id",
    async () => {
      const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
      const targetPath = path.join(workspaceDir, "subdir", "secret.txt");
      await fs.mkdir(path.join(workspaceDir, "subdir"), { recursive: true });
      await fs.writeFile(targetPath, "inside", "utf8");

      const backend = createMirrorBackendMock();
      const sandbox = createSandboxTestContext({
        overrides: {
          backendId: "openshell",
          workspaceDir,
          agentWorkspaceDir: workspaceDir,
          containerWorkdir: "/sandbox",
        },
      });

      const { createOpenShellFsBridge } = await import("./fs-bridge.js");
      const bridge = createOpenShellFsBridge({ sandbox, backend });
      const readlinkSpy = vi
        .spyOn(fs, "readlink")
        .mockRejectedValue(new Error("fd path unavailable"));
      const originalStat = fs.stat.bind(fs);
      const statSpy = vi.spyOn(fs, "stat").mockImplementation(async (...args) => {
        const stat = await originalStat(...args);
        if (args[0] === targetPath) {
          return cloneStatWithDev(stat, 0);
        }
        return stat;
      });

      try {
        await expect(bridge.readFile({ filePath: "subdir/secret.txt" })).rejects.toThrow(
          "Sandbox boundary checks failed",
        );
        expect(readlinkSpy).toHaveBeenCalled();
        expect(statSpy).toHaveBeenCalledWith(targetPath);
      } finally {
        statSpy.mockRestore();
        readlinkSpy.mockRestore();
      }
    },
  );

  it("rejects fallback reads when an ancestor directory is swapped to a symlink", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    const outsideDir = await makeTempDir("openclaw-openshell-outside-");
    await fs.mkdir(path.join(workspaceDir, "subdir"), { recursive: true });
    await fs.writeFile(path.join(workspaceDir, "subdir", "secret.txt"), "inside", "utf8");
    await fs.writeFile(path.join(outsideDir, "secret.txt"), "outside", "utf8");

    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir: workspaceDir,
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge } = await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });
    const originalOpen = fs.open.bind(fs);
    const targetPath = path.join(workspaceDir, "subdir", "secret.txt");
    let swapped = false;
    const openSpy = vi.spyOn(fs, "open").mockImplementation((async (...args: unknown[]) => {
      const filePath = args[0];
      if (!swapped && filePath === targetPath) {
        swapped = true;
        nodeFs.rmSync(path.join(workspaceDir, "subdir"), { recursive: true, force: true });
        nodeFs.symlinkSync(outsideDir, path.join(workspaceDir, "subdir"));
      }
      return await (originalOpen as (...delegated: unknown[]) => Promise<unknown>)(...args);
    }) as unknown as typeof fs.open);
    // Force the fallback verification path even on Linux so the ancestor-walk
    // guard is exercised directly.
    const readlinkSpy = vi
      .spyOn(fs, "readlink")
      .mockRejectedValue(new Error("fd path unavailable"));

    try {
      await expect(bridge.readFile({ filePath: "subdir/secret.txt" })).rejects.toThrow(
        "Sandbox boundary checks failed",
      );
      expect(openSpy).toHaveBeenCalled();
      expect(readlinkSpy).toHaveBeenCalled();
    } finally {
      readlinkSpy.mockRestore();
      openSpy.mockRestore();
    }
  });

  it("rejects fallback reads of a symlinked leaf when O_NOFOLLOW is unavailable", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    const outsideDir = await makeTempDir("openclaw-openshell-outside-");
    await fs.mkdir(path.join(workspaceDir, "subdir"), { recursive: true });
    await fs.writeFile(path.join(outsideDir, "secret.txt"), "outside", "utf8");
    // The workspace contains a symlink as the FINAL path component pointing
    // out-of-root. On Windows `O_NOFOLLOW` is `undefined`, so `open` would
    // silently traverse the symlink to the outside file; the ancestor walk
    // must lstat the leaf in that case to fail closed.
    await fs.symlink(
      path.join(outsideDir, "secret.txt"),
      path.join(workspaceDir, "subdir", "secret.txt"),
    );

    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir: workspaceDir,
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge, setReadOpenFlagsResolverForTest } =
      await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });
    // Force the fallback path so the leaf-lstat guard is exercised.
    const readlinkSpy = vi
      .spyOn(fs, "readlink")
      .mockRejectedValue(new Error("fd path unavailable"));
    // Simulate a host that lacks `O_NOFOLLOW` (e.g. Windows) without touching
    // the non-configurable native `fs.constants` data property. The bridge
    // exposes a test-only seam for exactly this case.
    setReadOpenFlagsResolverForTest(() => ({
      flags: nodeFs.constants.O_RDONLY,
      supportsNoFollow: false,
    }));

    try {
      await expect(bridge.readFile({ filePath: "subdir/secret.txt" })).rejects.toThrow(
        "Sandbox boundary checks failed",
      );
      expect(readlinkSpy).toHaveBeenCalled();
    } finally {
      setReadOpenFlagsResolverForTest(undefined);
      readlinkSpy.mockRestore();
    }
  });

  it("rejects hardlinked files inside the sandbox root", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    const outsideDir = await makeTempDir("openclaw-openshell-outside-");
    await fs.mkdir(path.join(workspaceDir, "subdir"), { recursive: true });
    await fs.writeFile(path.join(outsideDir, "secret.txt"), "outside", "utf8");
    await fs.link(
      path.join(outsideDir, "secret.txt"),
      path.join(workspaceDir, "subdir", "secret.txt"),
    );

    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir: workspaceDir,
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge } = await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });

    await expect(bridge.readFile({ filePath: "subdir/secret.txt" })).rejects.toThrow(
      "Sandbox boundary checks failed",
    );
  });

  it("maps agent mount paths when the sandbox workspace is read-only", async () => {
    const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
    const agentWorkspaceDir = await makeTempDir("openclaw-openshell-agent-");
    await fs.writeFile(path.join(agentWorkspaceDir, "note.txt"), "agent", "utf8");
    const backend = createMirrorBackendMock();
    const sandbox = createSandboxTestContext({
      overrides: {
        backendId: "openshell",
        workspaceDir,
        agentWorkspaceDir,
        workspaceAccess: "ro",
        containerWorkdir: "/sandbox",
      },
    });

    const { createOpenShellFsBridge } = await import("./fs-bridge.js");
    const bridge = createOpenShellFsBridge({ sandbox, backend });
    const resolved = bridge.resolvePath({ filePath: "/agent/note.txt" });
    expect(resolved.hostPath).toBe(path.join(agentWorkspaceDir, "note.txt"));
    expect(await bridge.readFile({ filePath: "/agent/note.txt" })).toEqual(Buffer.from("agent"));
  });
});

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.