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


Quelle  secret-file.test.ts

  Sprache: JAVA
 

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

import * as fsPromises from "node:fs/promises";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js";
import {
  DEFAULT_SECRET_FILE_MAX_BYTES,
  loadSecretFileSync,
  PRIVATE_SECRET_DIR_MODE,
  PRIVATE_SECRET_FILE_MODE,
  readSecretFileSync,
  tryReadSecretFileSync,
  writePrivateSecretFileAtomic,
} from "./secret-file.js";

const tempDirs = createTrackedTempDirs();
const createTempDir = () => tempDirs.make("openclaw-secret-file-test-");

afterEach(async () => {
  await tempDirs.cleanup();
});

async function expectSecretFileError(params: {
  setup: (dir: string) => Promise<string>;
  expectedMessage: (file: string) => string;
  secretLabel?: string;
  options?: Parameters<typeof readSecretFileSync>[2];
}): Promise<void> {
  const dir = await createTempDir();
  const file = await params.setup(dir);
  expect(() =>
    readSecretFileSync(file, params.secretLabel ?? "Gateway password", params.options),
  ).toThrow(params.expectedMessage(file));
}

async function createSecretPath(setup: (dir: string) => Promise<string>): Promise<string> {
  const dir = await createTempDir();
  return setup(dir);
}

describe("readSecretFileSync", () => {
  it("rejects blank file paths", () => {
    expect(() => readSecretFileSync("   ", "Gateway password")).toThrow(
      "Gateway password file path is empty.",
    );
  });

  it("reads and trims a regular secret file", async () => {
    const dir = await createTempDir();
    const file = path.join(dir, "secret.txt");
    await fsPromises.writeFile(file, " top-secret \n", "utf8");

    expect(readSecretFileSync(file, "Gateway password")).toBe("top-secret");
    expect(tryReadSecretFileSync(file, "Gateway password")).toBe("top-secret");
  });

  it.each([
    {
      name: "surfaces resolvedPath and error details for missing files",
      assert: (file: string) => {
        expect(loadSecretFileSync(file, "Gateway password")).toMatchObject({
          ok: false,
          resolvedPath: file,
          message: expect.stringContaining(`Failed to inspect Gateway password file at ${file}:`),
          error: expect.any(Error),
        });
      },
    },
    {
      name: "preserves the underlying cause when throwing for missing files",
      assert: (file: string) => {
        let thrown: Error | undefined;
        try {
          readSecretFileSync(file, "Gateway password");
        } catch (error) {
          thrown = error as Error;
        }

        expect(thrown).toBeInstanceOf(Error);
        expect(thrown?.message).toContain(`Failed to inspect Gateway password file at ${file}:`);
        expect((thrown as Error & { cause?: unknown }).cause).toBeInstanceOf(Error);
      },
    },
  ])("$name", async ({ assert }) => {
    const file = await createSecretPath(async (dir) => path.join(dir, "missing-secret.txt"));
    assert(file);
  });

  it.each([
    {
      name: "rejects files larger than the secret-file limit",
      setup: async (dir: string) => {
        const file = path.join(dir, "secret.txt");
        await fsPromises.writeFile(file, "x".repeat(DEFAULT_SECRET_FILE_MAX_BYTES + 1), "utf8");
        return file;
      },
      expectedMessage: (file: string) =>
        `Gateway password file at ${file} exceeds ${DEFAULT_SECRET_FILE_MAX_BYTES} bytes.`,
    },
    {
      name: "rejects non-regular files",
      setup: async (dir: string) => {
        const nestedDir = path.join(dir, "secret-dir");
        await fsPromises.mkdir(nestedDir);
        return nestedDir;
      },
      expectedMessage: (file: string) => `Gateway password file at ${file} must be a regular file.`,
    },
    {
      name: "rejects symlinks when configured",
      setup: async (dir: string) => {
        const target = path.join(dir, "target.txt");
        const link = path.join(dir, "secret-link.txt");
        await fsPromises.writeFile(target, "top-secret\n", "utf8");
        await fsPromises.symlink(target, link);
        return link;
      },
      options: { rejectSymlink: true },
      expectedMessage: (file: string) => `Gateway password file at ${file} must not be a symlink.`,
    },
    {
      name: "rejects empty secret files after trimming",
      setup: async (dir: string) => {
        const file = path.join(dir, "secret.txt");
        await fsPromises.writeFile(file, " \n\t ", "utf8");
        return file;
      },
      expectedMessage: (file: string) => `Gateway password file at ${file} is empty.`,
    },
  ])("$name", async ({ setup, expectedMessage, options }) => {
    await expectSecretFileError({ setup, expectedMessage, options });
  });

  it.each([
    {
      name: "exposes resolvedPath on non-throwing read failures",
      pathValue: async () =>
        createSecretPath(async (dir) => {
          const file = path.join(dir, "secret.txt");
          await fsPromises.writeFile(file, " \n\t ", "utf8");
          return file;
        }),
      label: "Gateway password",
      options: undefined,
      helper: "load" as const,
      expected: (file: string | undefined) => ({
        ok: false,
        resolvedPath: file,
        message: `Gateway password file at ${file} is empty.`,
      }),
    },
    {
      name: "returns undefined from the non-throwing helper for rejected files",
      pathValue: async () =>
        createSecretPath(async (dir) => {
          const target = path.join(dir, "target.txt");
          const link = path.join(dir, "secret-link.txt");
          await fsPromises.writeFile(target, "top-secret\n", "utf8");
          await fsPromises.symlink(target, link);
          return link;
        }),
      label: "Telegram bot token",
      options: { rejectSymlink: true },
      helper: "try" as const,
      expected: () => undefined,
    },
    {
      name: "returns undefined from the non-throwing helper for blank file paths",
      pathValue: async () => "   ",
      label: "Telegram bot token",
      options: undefined,
      helper: "try" as const,
      expected: () => undefined,
    },
    {
      name: "returns undefined from the non-throwing helper for missing path values",
      pathValue: async () => undefined,
      label: "Telegram bot token",
      options: undefined,
      helper: "try" as const,
      expected: () => undefined,
    },
  ])("$name", async ({ pathValue, label, options, helper, expected }) => {
    const file = await pathValue();
    if (helper === "load") {
      expect(loadSecretFileSync(file as string, label, options)).toMatchObject(
        (expected as (file: string | undefined) => Record<string, unknown>)(file),
      );
      return;
    }
    expect(tryReadSecretFileSync(file, label, options)).toBe((expected as () => undefined)());
  });
});

describe("writePrivateSecretFileAtomic", () => {
  it("writes a private file with owner-only permissions", async () => {
    const dir = await createTempDir();
    const file = path.join(dir, "nested", "auth.json");

    await writePrivateSecretFileAtomic({
      rootDir: dir,
      filePath: file,
      content: '{"ok":true}\n',
    });

    expect(loadSecretFileSync(file, "Gateway password")).toMatchObject({
      ok: true,
      secret: '{"ok":true}',
    });
    if (process.platform !== "win32") {
      const dirStat = await fsPromises.stat(path.dirname(file));
      const fileStat = await fsPromises.stat(file);
      expect(dirStat.mode & 0o777).toBe(PRIVATE_SECRET_DIR_MODE);
      expect(fileStat.mode & 0o777).toBe(PRIVATE_SECRET_FILE_MODE);
    }
  });

  it("rejects symlinked target files", async () => {
    const dir = await createTempDir();
    const nestedDir = path.join(dir, "nested");
    const target = path.join(dir, "outside.txt");
    const link = path.join(nestedDir, "auth.json");
    await fsPromises.mkdir(nestedDir);
    await fsPromises.writeFile(target, "outside", "utf8");
    await fsPromises.symlink(target, link);

    await expect(
      writePrivateSecretFileAtomic({
        rootDir: dir,
        filePath: link,
        content: '{"ok":true}\n',
      }),
    ).rejects.toThrow("must not be a symlink");
  });

  it("rejects symlinked path components", async () => {
    const dir = await createTempDir();
    const targetDir = path.join(dir, "outside-dir");
    await fsPromises.mkdir(targetDir);
    await fsPromises.symlink(targetDir, path.join(dir, "linked"));

    await expect(
      writePrivateSecretFileAtomic({
        rootDir: dir,
        filePath: path.join(dir, "linked", "auth.json"),
        content: '{"ok":true}\n',
      }),
    ).rejects.toThrow("must not be a symlink");
  });

  it("tightens an existing world-readable directory before writing secrets", async () => {
    const dir = await createTempDir();
    const nestedDir = path.join(dir, "nested");
    await fsPromises.mkdir(nestedDir, { mode: 0o777 });
    if (process.platform !== "win32") {
      await writePrivateSecretFileAtomic({
        rootDir: dir,
        filePath: path.join(nestedDir, "auth.json"),
        content: '{"ok":true}\n',
      });
      const dirStat = await fsPromises.stat(nestedDir);
      expect(dirStat.mode & 0o777).toBe(PRIVATE_SECRET_DIR_MODE);
    }
  });

  it("rejects a parent directory symlink before it can escape the private root", async () => {
    const dir = await createTempDir();
    const targetDir = await createTempDir();
    const aliasDir = path.join(dir, "nested");
    await fsPromises.symlink(targetDir, aliasDir);

    await expect(
      writePrivateSecretFileAtomic({
        rootDir: dir,
        filePath: path.join(aliasDir, "auth.json"),
        content: '{"ok":true}\n',
      }),
    ).rejects.toThrow("must not be a symlink");
  });
});

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