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

Quelle  exec-approvals-safe-bins.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";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
  makeMockCommandResolution,
  makeMockExecutableResolution,
  makePathEnv,
  makeTempDir,
} from "./exec-approvals-test-helpers.js";
import {
  evaluateExecAllowlist,
  evaluateShellAllowlist,
  isSafeBinUsage,
  normalizeSafeBins,
  resolveSafeBins,
} from "./exec-approvals.js";
import {
  SAFE_BIN_PROFILE_FIXTURES,
  SAFE_BIN_PROFILES,
  resolveSafeBinProfiles,
} from "./exec-safe-bin-policy.js";

describe("exec approvals safe bins", () => {
  type SafeBinCase = {
    name: string;
    argv: string[];
    resolvedPath: string;
    expected: boolean;
    safeBins?: string[];
    safeBinProfiles?: Readonly<Record<string, { minPositional?: number; maxPositional?: number }>>;
    executableName?: string;
    rawExecutable?: string;
    cwd?: string;
    setup?: (cwd: string) => void;
  };

  function buildDeniedFlagVariantCases(params: {
    executableName: string;
    resolvedPath: string;
    safeBins?: string[];
    flag: string;
    takesValue: boolean;
    label: string;
  }): SafeBinCase[] {
    const value = "blocked";
    const argvVariants: string[][] = [];
    if (!params.takesValue) {
      argvVariants.push([params.executableName, params.flag]);
    } else if (params.flag.startsWith("--")) {
      argvVariants.push([params.executableName, `${params.flag}=${value}`]);
      argvVariants.push([params.executableName, params.flag, value]);
    } else if (params.flag.startsWith("-")) {
      argvVariants.push([params.executableName, `${params.flag}${value}`]);
      argvVariants.push([params.executableName, params.flag, value]);
    } else {
      argvVariants.push([params.executableName, params.flag, value]);
    }
    return argvVariants.map((argv) => ({
      name: `${params.label} (${argv.slice(1).join(" ")})`,
      argv,
      resolvedPath: params.resolvedPath,
      expected: false,
      safeBins: params.safeBins ?? [params.executableName],
      executableName: params.executableName,
    }));
  }

  const deniedFlagCases: SafeBinCase[] = [
    ...buildDeniedFlagVariantCases({
      executableName: "sort",
      resolvedPath: "/usr/bin/sort",
      flag: "-o",
      takesValue: true,
      label: "blocks sort output flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "sort",
      resolvedPath: "/usr/bin/sort",
      flag: "--output",
      takesValue: true,
      label: "blocks sort output flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "sort",
      resolvedPath: "/usr/bin/sort",
      flag: "--compress-program",
      takesValue: true,
      label: "blocks sort external program flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "sort",
      resolvedPath: "/usr/bin/sort",
      flag: "--compress-prog",
      takesValue: true,
      label: "blocks sort denied flag abbreviations",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "sort",
      resolvedPath: "/usr/bin/sort",
      flag: "--files0-fro",
      takesValue: true,
      label: "blocks sort denied flag abbreviations",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "sort",
      resolvedPath: "/usr/bin/sort",
      flag: "--random-source",
      takesValue: true,
      label: "blocks sort filesystem-dependent flags",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "sort",
      resolvedPath: "/usr/bin/sort",
      flag: "--temporary-directory",
      takesValue: true,
      label: "blocks sort filesystem-dependent flags",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "sort",
      resolvedPath: "/usr/bin/sort",
      flag: "-T",
      takesValue: true,
      label: "blocks sort filesystem-dependent flags",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "grep",
      resolvedPath: "/usr/bin/grep",
      flag: "-R",
      takesValue: false,
      label: "blocks grep recursive flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "grep",
      resolvedPath: "/usr/bin/grep",
      flag: "--recursive",
      takesValue: false,
      label: "blocks grep recursive flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "grep",
      resolvedPath: "/usr/bin/grep",
      flag: "--file",
      takesValue: true,
      label: "blocks grep file-pattern flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "jq",
      resolvedPath: "/usr/bin/jq",
      flag: "-f",
      takesValue: true,
      label: "blocks jq file-program flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "jq",
      resolvedPath: "/usr/bin/jq",
      flag: "--from-file",
      takesValue: true,
      label: "blocks jq file-program flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "wc",
      resolvedPath: "/usr/bin/wc",
      flag: "--files0-from",
      takesValue: true,
      label: "blocks wc file-list flag",
    }),
    ...buildDeniedFlagVariantCases({
      executableName: "wc",
      resolvedPath: "/usr/bin/wc",
      flag: "--files0-fro",
      takesValue: true,
      label: "blocks wc denied flag abbreviations",
    }),
  ];

  const cases: SafeBinCase[] = [
    {
      name: "allows safe bins with non-path args",
      argv: ["jq", ".foo"],
      resolvedPath: "/usr/bin/jq",
      expected: true,
    },
    {
      name: "blocks jq env builtin even when jq is explicitly opted in",
      argv: ["jq", "env"],
      resolvedPath: "/usr/bin/jq",
      expected: false,
    },
    {
      name: "blocks jq $ENV builtin variable even when jq is explicitly opted in",
      argv: ["jq", "$ENV"],
      resolvedPath: "/usr/bin/jq",
      expected: false,
    },
    {
      name: "blocks jq $ENV property access even when jq is explicitly opted in",
      argv: ["jq", "($ENV).OPENAI_API_KEY"],
      resolvedPath: "/usr/bin/jq",
      expected: false,
    },
    {
      name: "blocks awk scripts even when awk is explicitly profiled",
      argv: ["awk", 'BEGIN { system("id") }'],
      resolvedPath: "/usr/bin/awk",
      expected: false,
      safeBins: ["awk"],
      safeBinProfiles: { awk: {} },
      executableName: "awk",
    },
    {
      name: "blocks sed scripts even when sed is explicitly profiled",
      argv: ["sed", "e"],
      resolvedPath: "/usr/bin/sed",
      expected: false,
      safeBins: ["sed"],
      safeBinProfiles: { sed: {} },
      executableName: "sed",
    },
    {
      name: "blocks safe bins with file args",
      argv: ["jq", ".foo", "secret.json"],
      resolvedPath: "/usr/bin/jq",
      expected: false,
      setup: (cwd) => fs.writeFileSync(path.join(cwd, "secret.json"), "{}"),
    },
    {
      name: "blocks safe bins resolved from untrusted directories",
      argv: ["jq", ".foo"],
      resolvedPath: "/tmp/evil-bin/jq",
      expected: false,
      cwd: "/tmp",
    },
    ...deniedFlagCases,
    {
      name: "blocks grep file positional when pattern uses -e",
      argv: ["grep", "-e", "needle", ".env"],
      resolvedPath: "/usr/bin/grep",
      expected: false,
      safeBins: ["grep"],
      executableName: "grep",
    },
    {
      name: "blocks grep file positional after -- terminator",
      argv: ["grep", "-e", "needle", "--", ".env"],
      resolvedPath: "/usr/bin/grep",
      expected: false,
      safeBins: ["grep"],
      executableName: "grep",
    },
    {
      name: "rejects unknown long options in safe-bin mode",
      argv: ["sort", "--totally-unknown=1"],
      resolvedPath: "/usr/bin/sort",
      expected: false,
      safeBins: ["sort"],
      executableName: "sort",
    },
    {
      name: "rejects ambiguous long-option abbreviations in safe-bin mode",
      argv: ["sort", "--f=1"],
      resolvedPath: "/usr/bin/sort",
      expected: false,
      safeBins: ["sort"],
      executableName: "sort",
    },
    {
      name: "rejects unknown short options in safe-bin mode",
      argv: ["tr", "-S", "a", "b"],
      resolvedPath: "/usr/bin/tr",
      expected: false,
      safeBins: ["tr"],
      executableName: "tr",
    },
  ];

  it.runIf(process.platform !== "win32").each(cases)("$name", (testCase) => {
    const cwd = testCase.cwd ?? makeTempDir();
    testCase.setup?.(cwd);
    const executableName = testCase.executableName ?? "jq";
    const rawExecutable = testCase.rawExecutable ?? executableName;
    const ok = isSafeBinUsage({
      argv: testCase.argv,
      resolution: {
        rawExecutable,
        resolvedPath: testCase.resolvedPath,
        executableName,
      },
      safeBins: normalizeSafeBins(testCase.safeBins ?? [executableName]),
      safeBinProfiles: testCase.safeBinProfiles,
    });
    expect(ok).toBe(testCase.expected);
  });

  it("supports injected trusted safe-bin dirs for tests/callers", () => {
    if (process.platform === "win32") {
      return;
    }
    const ok = isSafeBinUsage({
      argv: ["jq", ".foo"],
      resolution: {
        rawExecutable: "jq",
        resolvedPath: "/custom/bin/jq",
        executableName: "jq",
      },
      safeBins: normalizeSafeBins(["jq"]),
      trustedSafeBinDirs: new Set(["/custom/bin"]),
    });
    expect(ok).toBe(true);
  });

  it("supports injected platform for deterministic safe-bin checks", () => {
    const ok = isSafeBinUsage({
      argv: ["jq", ".foo"],
      resolution: {
        rawExecutable: "jq",
        resolvedPath: "/usr/bin/jq",
        executableName: "jq",
      },
      safeBins: normalizeSafeBins(["jq"]),
      platform: "win32",
    });
    expect(ok).toBe(false);
  });

  it("supports injected trusted path checker for deterministic callers", () => {
    if (process.platform === "win32") {
      return;
    }
    const baseParams = {
      argv: ["jq", ".foo"],
      resolution: {
        rawExecutable: "jq",
        resolvedPath: "/tmp/custom/jq",
        executableName: "jq",
      },
      safeBins: normalizeSafeBins(["jq"]),
    };
    expect(
      isSafeBinUsage({
        ...baseParams,
        isTrustedSafeBinPathFn: () => true,
      }),
    ).toBe(true);
    expect(
      isSafeBinUsage({
        ...baseParams,
        isTrustedSafeBinPathFn: () => false,
      }),
    ).toBe(false);
  });

  it("keeps safe-bin profile fixtures aligned with compiled profiles", () => {
    for (const [name, fixture] of Object.entries(SAFE_BIN_PROFILE_FIXTURES)) {
      const profile = SAFE_BIN_PROFILES[name];
      expect(profile).toBeDefined();
      const fixtureDeniedFlags = fixture.deniedFlags ?? [];
      const compiledDeniedFlags = profile?.deniedFlags ?? new Set<string>();
      for (const deniedFlag of fixtureDeniedFlags) {
        expect(compiledDeniedFlags.has(deniedFlag)).toBe(true);
      }
      expect(Array.from(compiledDeniedFlags).toSorted()).toEqual(
        [...fixtureDeniedFlags].toSorted(),
      );
    }
  });

  it("does not include sort/grep in default safeBins", () => {
    const defaults = resolveSafeBins(undefined);
    expect(defaults.has("jq")).toBe(false);
    expect(defaults.has("sort")).toBe(false);
    expect(defaults.has("grep")).toBe(false);
  });

  it("does not auto-allow unprofiled safe-bin entries", () => {
    if (process.platform === "win32") {
      return;
    }
    const result = evaluateShellAllowlist({
      command: "python3 -c \"print('owned')\"",
      allowlist: [],
      safeBins: normalizeSafeBins(["python3"]),
      cwd: "/tmp",
    });
    expect(result.analysisOk).toBe(true);
    expect(result.allowlistSatisfied).toBe(false);
  });

  it("allows caller-defined custom safe-bin profiles", () => {
    if (process.platform === "win32") {
      return;
    }
    const safeBinProfiles = resolveSafeBinProfiles({
      echo: {
        maxPositional: 1,
      },
    });
    const allow = isSafeBinUsage({
      argv: ["echo", "hello"],
      resolution: {
        rawExecutable: "echo",
        resolvedPath: "/bin/echo",
        executableName: "echo",
      },
      safeBins: normalizeSafeBins(["echo"]),
      safeBinProfiles,
    });
    const deny = isSafeBinUsage({
      argv: ["echo", "hello", "world"],
      resolution: {
        rawExecutable: "echo",
        resolvedPath: "/bin/echo",
        executableName: "echo",
      },
      safeBins: normalizeSafeBins(["echo"]),
      safeBinProfiles,
    });
    expect(allow).toBe(true);
    expect(deny).toBe(false);
  });

  it("blocks sort output flags independent of file existence", () => {
    if (process.platform === "win32") {
      return;
    }
    const cwd = makeTempDir();
    fs.writeFileSync(path.join(cwd, "existing.txt"), "x");
    const resolution = {
      rawExecutable: "sort",
      resolvedPath: "/usr/bin/sort",
      executableName: "sort",
    };
    const safeBins = normalizeSafeBins(["sort"]);
    const existing = isSafeBinUsage({
      argv: ["sort", "-o", "existing.txt"],
      resolution,
      safeBins,
    });
    const missing = isSafeBinUsage({
      argv: ["sort", "-o", "missing.txt"],
      resolution,
      safeBins,
    });
    const longFlag = isSafeBinUsage({
      argv: ["sort", "--output=missing.txt"],
      resolution,
      safeBins,
    });
    expect(existing).toBe(false);
    expect(missing).toBe(false);
    expect(longFlag).toBe(false);
  });

  it("threads trusted safe-bin dirs through allowlist evaluation", () => {
    if (process.platform === "win32") {
      return;
    }
    const analysis = {
      ok: true as const,
      segments: [
        {
          raw: "jq .foo",
          argv: ["jq", ".foo"],
          resolution: makeMockCommandResolution({
            execution: makeMockExecutableResolution({
              rawExecutable: "jq",
              resolvedPath: "/custom/bin/jq",
              executableName: "jq",
            }),
          }),
        },
      ],
    };
    const denied = evaluateExecAllowlist({
      analysis,
      allowlist: [],
      safeBins: normalizeSafeBins(["jq"]),
      trustedSafeBinDirs: new Set(["/usr/bin"]),
      cwd: "/tmp",
    });
    expect(denied.allowlistSatisfied).toBe(false);

    const allowed = evaluateExecAllowlist({
      analysis,
      allowlist: [],
      safeBins: normalizeSafeBins(["jq"]),
      trustedSafeBinDirs: new Set(["/custom/bin"]),
      cwd: "/tmp",
    });
    expect(allowed.allowlistSatisfied).toBe(true);
  });

  it("does not auto-trust PATH-shadowed safe bins without explicit trusted dirs", () => {
    if (process.platform === "win32") {
      return;
    }
    const tmp = makeTempDir();
    const fakeDir = path.join(tmp, "fake-bin");
    fs.mkdirSync(fakeDir, { recursive: true });
    const fakeHead = path.join(fakeDir, "head");
    fs.writeFileSync(fakeHead, "#!/bin/sh\nexit 0\n");
    fs.chmodSync(fakeHead, 0o755);

    const result = evaluateShellAllowlist({
      command: "head -n 1",
      allowlist: [],
      safeBins: normalizeSafeBins(["head"]),
      env: makePathEnv(fakeDir),
      cwd: tmp,
    });
    expect(result.analysisOk).toBe(true);
    expect(result.allowlistSatisfied).toBe(false);
    expect(result.segmentSatisfiedBy).toEqual([null]);
    expect(result.segments[0]?.resolution?.execution.resolvedPath).toBe(fakeHead);
  });

  it("fails closed for semantic env wrappers in allowlist mode", () => {
    if (process.platform === "win32") {
      return;
    }
    const result = evaluateShellAllowlist({
      command: "env -S 'sh -c \"echo pwned\"' tr",
      allowlist: [{ pattern: "/usr/bin/tr" }],
      safeBins: normalizeSafeBins(["tr"]),
      cwd: "/tmp",
      platform: process.platform,
    });
    expect(result.analysisOk).toBe(true);
    expect(result.allowlistSatisfied).toBe(false);
    expect(result.segmentSatisfiedBy).toEqual([null]);
    expect(result.segments[0]?.resolution?.policyBlocked).toBe(true);
  });
});

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