Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  audit-workspace-skill-escape.test.ts

  Sprache: JAVA
 

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

import fs from "node:fs/promises";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { collectWorkspaceSkillSymlinkEscapeFindings } from "./audit-extra.async.js";
import { AsyncTempCaseFactory } from "./test-temp-cases.js";

const isWindows = process.platform === "win32";

describe("security audit workspace skill path escape findings", () => {
  const tempCases = new AsyncTempCaseFactory("openclaw-security-audit-workspace-");

  beforeAll(async () => {
    await tempCases.setup();
  });

  afterAll(async () => {
    await tempCases.cleanup();
  });

  it("evaluates workspace skill path escape findings", async () => {
    const runs = [
      !isWindows
        ? (async () => {
            const tmp = await tempCases.makeTmpDir("workspace-skill-symlink-escape");
            const workspaceDir = path.join(tmp, "workspace");
            const outsideDir = path.join(tmp, "outside");
            await fs.mkdir(path.join(workspaceDir, "skills", "leak"), { recursive: true });
            await fs.mkdir(outsideDir, { recursive: true });
            const outsideSkillPath = path.join(outsideDir, "SKILL.md");
            await fs.writeFile(outsideSkillPath, "# outside\n", "utf-8");
            await fs.symlink(
              outsideSkillPath,
              path.join(workspaceDir, "skills", "leak", "SKILL.md"),
            );
            const findings = await collectWorkspaceSkillSymlinkEscapeFindings({
              cfg: { agents: { defaults: { workspace: workspaceDir } } } satisfies OpenClawConfig,
            });
            const finding = findings.find(
              (entry) => entry.checkId === "skills.workspace.symlink_escape",
            );
            expect(finding?.severity).toBe("warn");
            expect(finding?.detail).toContain(outsideSkillPath);
          })()
        : Promise.resolve(),
      (async () => {
        const tmp = await tempCases.makeTmpDir("workspace-skill-in-root");
        const workspaceDir = path.join(tmp, "workspace");
        await fs.mkdir(path.join(workspaceDir, "skills", "safe"), { recursive: true });
        await fs.writeFile(
          path.join(workspaceDir, "skills", "safe", "SKILL.md"),
          "# in workspace\n",
          "utf-8",
        );
        const findings = await collectWorkspaceSkillSymlinkEscapeFindings({
          cfg: { agents: { defaults: { workspace: workspaceDir } } } satisfies OpenClawConfig,
        });
        expect(findings.some((entry) => entry.checkId === "skills.workspace.symlink_escape")).toBe(
          false,
        );
      })(),
    ];

    await Promise.all(runs);
  });

  it("treats an unresolvable realpath (timeout/error simulation) as a potential symlink escape", async () => {
    const tmp = await tempCases.makeTmpDir("workspace-skill-realpath-unresolvable");
    const workspaceDir = path.join(tmp, "workspace");
    const skillsDir = path.join(workspaceDir, "skills", "suspect-skill");
    await fs.mkdir(skillsDir, { recursive: true });
    await fs.writeFile(path.join(skillsDir, "SKILL.md"), "# suspect\n", "utf-8");

    // Simulate realpath failing for the skill file path — this mirrors what
    // happens when a slow/hanging NFS or SMB mount causes the 2 s deadline in
    // realpathWithTimeout to fire. The .catch(() => null) inside the helper
    // converts any rejection to null, which is the same signal produced by a
    // genuine timeout. All other paths resolve to their string value so the BFS
    // and workspace-root detection work normally.
    const realpathSpy = vi
      .spyOn(fs, "realpath")
      .mockImplementation(async (p: unknown): Promise<string> => {
        if (String(p).endsWith("SKILL.md")) {
          throw new Error("simulated realpath timeout");
        }
        return String(p);
      });

    try {
      const findings = await collectWorkspaceSkillSymlinkEscapeFindings({
        cfg: { agents: { defaults: { workspace: workspaceDir } } } satisfies OpenClawConfig,
      });
      const escapeFinding = findings.find((f) => f.checkId === "skills.workspace.symlink_escape");
      expect(escapeFinding).toBeDefined();
      expect(escapeFinding?.severity).toBe("warn");
      // The finding must call out that realpath was unverifiable, not that it
      // resolved to a path outside the workspace.
      expect(escapeFinding?.detail).toContain("realpath timed out");
    } finally {
      realpathSpy.mockRestore();
    }
  });

  it("surfaces scan_truncated finding when BFS visit cap is hit", async () => {
    const tmp = await tempCases.makeTmpDir("workspace-skill-bfs-truncated");
    const workspaceDir = path.join(tmp, "workspace");
    const skillsRoot = path.join(workspaceDir, "skills");
    await fs.mkdir(skillsRoot, { recursive: true });

    // Use a tiny injected visit cap to exercise the truncation branch without
    // forcing the test to await tens of thousands of mocked readdir calls.
    const FAKE_DIRS = 3;
    const fakeDirEntries = Array.from({ length: FAKE_DIRS }, (_, i) => ({
      name: `d${i}`,
      isDirectory: () => true,
      isFile: () => false,
      isSymbolicLink: () => false,
      isBlockDevice: () => false,
      isCharacterDevice: () => false,
      isFIFO: () => false,
      isSocket: () => false,
      parentPath: skillsRoot,
      path: skillsRoot,
    })) as unknown as Awaited<ReturnType<typeof fs.readdir>>;

    let readdirCalls = 0;
    const readdirSpy = vi.spyOn(fs, "readdir").mockImplementation(async () => {
      return readdirCalls++ === 0 ? fakeDirEntries : ([] as unknown as typeof fakeDirEntries);
    });
    const realpathSpy = vi
      .spyOn(fs, "realpath")
      .mockImplementation(async (p: unknown) => String(p));

    try {
      const findings = await collectWorkspaceSkillSymlinkEscapeFindings({
        cfg: { agents: { defaults: { workspace: workspaceDir } } } satisfies OpenClawConfig,
        skillScanLimits: { maxDirVisits: 2 },
      });
      const truncFinding = findings.find((f) => f.checkId === "skills.workspace.scan_truncated");
      expect(truncFinding).toBeDefined();
      expect(truncFinding?.severity).toBe("warn");
      expect(truncFinding?.detail).toContain(workspaceDir);
    } finally {
      readdirSpy.mockRestore();
      realpathSpy.mockRestore();
    }
  });
});

¤ Dauer der Verarbeitung: 0.1 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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge