From 64aab802015a89fab110cae158eeaa040ff11901 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 24 Feb 2026 03:10:05 +0000 Subject: [PATCH] test(exec): add regressions for safe-bin metadata and chain semantics --- src/infra/exec-approvals.test.ts | 65 ++++++++++++++++++++++++++ src/infra/exec-safe-bin-policy.test.ts | 34 ++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/src/infra/exec-approvals.test.ts b/src/infra/exec-approvals.test.ts index cddc8cfbd..49d2319dd 100644 --- a/src/infra/exec-approvals.test.ts +++ b/src/infra/exec-approvals.test.ts @@ -680,6 +680,71 @@ describe("exec approvals allowlist evaluation", () => { expect(result.allowlistSatisfied).toBe(false); expect(result.segmentSatisfiedBy).toEqual([null]); }); + + it("returns empty segment details for chain misses", () => { + const segment = { + raw: "tool", + argv: ["tool"], + resolution: { + rawExecutable: "tool", + resolvedPath: "/usr/bin/tool", + executableName: "tool", + }, + }; + const analysis = { + ok: true, + segments: [segment], + chains: [[segment]], + }; + const result = evaluateExecAllowlist({ + analysis, + allowlist: [{ pattern: "/usr/bin/other" }], + safeBins: new Set(), + cwd: "/tmp", + }); + expect(result.allowlistSatisfied).toBe(false); + expect(result.allowlistMatches).toEqual([]); + expect(result.segmentSatisfiedBy).toEqual([]); + }); + + it("aggregates segment satisfaction across chains", () => { + const allowlistSegment = { + raw: "tool", + argv: ["tool"], + resolution: { + rawExecutable: "tool", + resolvedPath: "/usr/bin/tool", + executableName: "tool", + }, + }; + const safeBinSegment = { + raw: "jq .foo", + argv: ["jq", ".foo"], + resolution: { + rawExecutable: "jq", + resolvedPath: "/usr/bin/jq", + executableName: "jq", + }, + }; + const analysis = { + ok: true, + segments: [allowlistSegment, safeBinSegment], + chains: [[allowlistSegment], [safeBinSegment]], + }; + const result = evaluateExecAllowlist({ + analysis, + allowlist: [{ pattern: "/usr/bin/tool" }], + safeBins: normalizeSafeBins(["jq"]), + cwd: "/tmp", + }); + if (process.platform === "win32") { + expect(result.allowlistSatisfied).toBe(false); + return; + } + expect(result.allowlistSatisfied).toBe(true); + expect(result.allowlistMatches.map((entry) => entry.pattern)).toEqual(["/usr/bin/tool"]); + expect(result.segmentSatisfiedBy).toEqual(["allowlist", "safeBins"]); + }); }); describe("exec approvals policy helpers", () => { diff --git a/src/infra/exec-safe-bin-policy.test.ts b/src/infra/exec-safe-bin-policy.test.ts index 886e95ccc..285b1465e 100644 --- a/src/infra/exec-safe-bin-policy.test.ts +++ b/src/infra/exec-safe-bin-policy.test.ts @@ -4,6 +4,8 @@ import { describe, expect, it } from "vitest"; import { SAFE_BIN_PROFILE_FIXTURES, SAFE_BIN_PROFILES, + buildLongFlagPrefixMap, + collectKnownLongFlags, renderSafeBinDeniedFlagsDocBullets, validateSafeBinArgv, } from "./exec-safe-bin-policy.js"; @@ -76,6 +78,38 @@ describe("exec safe bin policy wc", () => { }); }); +describe("exec safe bin policy long-option metadata", () => { + it("precomputes long-option prefix mappings for compiled profiles", () => { + const sortProfile = SAFE_BIN_PROFILES.sort; + expect(sortProfile.knownLongFlagsSet?.has("--compress-program")).toBe(true); + expect(sortProfile.longFlagPrefixMap?.get("--compress-prog")).toBe("--compress-program"); + expect(sortProfile.longFlagPrefixMap?.get("--f")).toBe(null); + }); + + it("preserves behavior when profile metadata is missing and rebuilt at runtime", () => { + const sortProfile = SAFE_BIN_PROFILES.sort; + const withoutMetadata = { + ...sortProfile, + knownLongFlags: undefined, + knownLongFlagsSet: undefined, + longFlagPrefixMap: undefined, + }; + expect(validateSafeBinArgv(["--compress-prog=sh"], withoutMetadata)).toBe(false); + expect(validateSafeBinArgv(["--totally-unknown=1"], withoutMetadata)).toBe(false); + }); + + it("builds prefix maps from collected long flags", () => { + const sortProfile = SAFE_BIN_PROFILES.sort; + const flags = collectKnownLongFlags( + sortProfile.allowedValueFlags ?? new Set(), + sortProfile.deniedFlags ?? new Set(), + ); + const prefixMap = buildLongFlagPrefixMap(flags); + expect(prefixMap.get("--compress-pr")).toBe("--compress-program"); + expect(prefixMap.get("--f")).toBe(null); + }); +}); + describe("exec safe bin policy denied-flag matrix", () => { for (const [binName, fixture] of Object.entries(SAFE_BIN_PROFILE_FIXTURES)) { const profile = SAFE_BIN_PROFILES[binName];