refactor: tighten safe-bin policy model and docs parity

This commit is contained in:
Peter Steinberger
2026-02-21 19:24:23 +01:00
parent c730d4dd72
commit 89aad7b922
4 changed files with 227 additions and 106 deletions

View File

@@ -519,6 +519,103 @@ describe("exec approvals safe bins", () => {
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: "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",
}),
];
const cases: SafeBinCase[] = [
{
name: "allows safe bins with non-path args",
@@ -540,54 +637,7 @@ describe("exec approvals safe bins", () => {
expected: false,
cwd: "/tmp",
},
{
name: "blocks sort output path via -o <file>",
argv: ["sort", "-o", "malicious.sh"],
resolvedPath: "/usr/bin/sort",
expected: false,
safeBins: ["sort"],
executableName: "sort",
},
{
name: "blocks sort output path via attached short option (-ofile)",
argv: ["sort", "-omalicious.sh"],
resolvedPath: "/usr/bin/sort",
expected: false,
safeBins: ["sort"],
executableName: "sort",
},
{
name: "blocks sort output path via --output=file",
argv: ["sort", "--output=malicious.sh"],
resolvedPath: "/usr/bin/sort",
expected: false,
safeBins: ["sort"],
executableName: "sort",
},
{
name: "blocks sort external program flag via --compress-program=<prog>",
argv: ["sort", "--compress-program=sh"],
resolvedPath: "/usr/bin/sort",
expected: false,
safeBins: ["sort"],
executableName: "sort",
},
{
name: "blocks sort external program flag via --compress-program <prog>",
argv: ["sort", "--compress-program", "sh"],
resolvedPath: "/usr/bin/sort",
expected: false,
safeBins: ["sort"],
executableName: "sort",
},
{
name: "blocks grep recursive flags that read cwd",
argv: ["grep", "-R", "needle"],
resolvedPath: "/usr/bin/grep",
expected: false,
safeBins: ["grep"],
executableName: "grep",
},
...deniedFlagCases,
{
name: "blocks grep file positional when pattern uses -e",
argv: ["grep", "-e", "needle", ".env"],
@@ -690,13 +740,13 @@ describe("exec approvals safe bins", () => {
for (const [name, fixture] of Object.entries(SAFE_BIN_PROFILE_FIXTURES)) {
const profile = SAFE_BIN_PROFILES[name];
expect(profile).toBeDefined();
const fixtureBlockedFlags = fixture.blockedFlags ?? [];
const compiledBlockedFlags = profile?.blockedFlags ?? new Set<string>();
for (const blockedFlag of fixtureBlockedFlags) {
expect(compiledBlockedFlags.has(blockedFlag)).toBe(true);
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(compiledBlockedFlags).toSorted()).toEqual(
[...fixtureBlockedFlags].toSorted(),
expect(Array.from(compiledDeniedFlags).toSorted()).toEqual(
[...fixtureDeniedFlags].toSorted(),
);
}
});