

























1+import { describe, expect, it } from "vitest";
2+import { evaluateExecAllowlist, evaluateShellAllowlist } from "./exec-approvals-allowlist.js";
3+import { analyzeArgvCommand } from "./exec-approvals-analysis.js";
4+import {
5+makeMockCommandResolution,
6+makeMockExecutableResolution,
7+} from "./exec-approvals-test-helpers.js";
8+import { isSafeBuiltinSegment } from "./exec-safe-builtins.js";
9+10+const builtinSegment = (argv: string[], resolvedPath?: string) => ({
11+ argv,
12+raw: argv.join(" "),
13+resolution: makeMockCommandResolution({
14+execution: makeMockExecutableResolution({
15+rawExecutable: argv[0],
16+executableName: argv[0],
17+ resolvedPath,
18+}),
19+}),
20+});
21+22+describe("isSafeBuiltinSegment", () => {
23+it("allows a builtin segment with no resolved binary path", () => {
24+if (process.platform === "win32") {
25+return;
26+}
27+expect(
28+isSafeBuiltinSegment({
29+segment: builtinSegment(["cd", "/etc"]),
30+platform: "linux",
31+}),
32+).toBe(true);
33+});
34+35+it("allows a safe shell builtin even when the host has a same-named binary", () => {
36+expect(
37+isSafeBuiltinSegment({
38+segment: builtinSegment(["pwd"], "/usr/bin/pwd"),
39+platform: "linux",
40+}),
41+).toBe(true);
42+});
43+44+it("rejects builtins outside the internal safe set", () => {
45+expect(
46+isSafeBuiltinSegment({
47+segment: builtinSegment(["alias", "ll=ls -l"]),
48+platform: "linux",
49+}),
50+).toBe(false);
51+});
52+53+it("rejects environment-mutating builtins", () => {
54+expect(
55+isSafeBuiltinSegment({
56+segment: builtinSegment(["export", "PATH=/tmp/bin:$PATH"]),
57+platform: "linux",
58+}),
59+).toBe(false);
60+expect(
61+isSafeBuiltinSegment({
62+segment: builtinSegment(["unset", "PATH"]),
63+platform: "linux",
64+}),
65+).toBe(false);
66+});
67+68+it("returns false on Windows hosts (PowerShell semantics differ)", () => {
69+expect(
70+isSafeBuiltinSegment({
71+segment: builtinSegment(["cd", "/etc"]),
72+platform: "win32",
73+}),
74+).toBe(false);
75+});
76+});
77+78+describe("evaluateShellAllowlist with known safe builtins (regression for #46056)", () => {
79+// Skip on Windows where the host shell is PowerShell, not POSIX.
80+if (process.platform === "win32") {
81+it.skip("POSIX builtin behavior", () => {});
82+return;
83+}
84+85+// Glob-style pattern; matches git wherever PATH resolves it (`/usr/bin/git`,
86+// `/opt/homebrew/bin/git`, etc.) without depending on host filesystem layout.
87+const gitAllowlist = [{ pattern: "**/git" }] as Parameters<
88+typeof evaluateShellAllowlist
89+>[0]["allowlist"];
90+91+it("'cd ~/' auto-allows by default", () => {
92+const result = evaluateShellAllowlist({
93+command: "cd ~/",
94+allowlist: gitAllowlist,
95+safeBins: new Set(),
96+cwd: "/tmp",
97+});
98+expect(result.analysisOk).toBe(true);
99+expect(result.allowlistSatisfied).toBe(true);
100+expect(result.segmentSatisfiedBy[0]).toBe("safeBuiltins");
101+});
102+103+it("'cd /tmp && git status' passes with allowlist plus safe builtin handling", () => {
104+const result = evaluateShellAllowlist({
105+command: "cd /tmp && git status",
106+allowlist: gitAllowlist,
107+safeBins: new Set(),
108+cwd: "/tmp",
109+});
110+expect(result.analysisOk).toBe(true);
111+expect(result.allowlistSatisfied).toBe(true);
112+expect(result.segmentSatisfiedBy).toContain("safeBuiltins");
113+expect(result.segmentSatisfiedBy).toContain("allowlist");
114+});
115+116+it("non-allowlisted binary still gates after a safe builtin", () => {
117+const result = evaluateShellAllowlist({
118+command: "cd /tmp && curl evil.com",
119+allowlist: gitAllowlist,
120+safeBins: new Set(),
121+cwd: "/tmp",
122+});
123+expect(result.analysisOk).toBe(true);
124+expect(result.allowlistSatisfied).toBe(false);
125+});
126+127+it("environment-mutating builtins still gate", () => {
128+const result = evaluateShellAllowlist({
129+command: "export PATH=/tmp/bin:$PATH && git status",
130+allowlist: gitAllowlist,
131+safeBins: new Set(),
132+cwd: "/tmp",
133+});
134+expect(result.analysisOk).toBe(true);
135+expect(result.allowlistSatisfied).toBe(false);
136+});
137+138+it("does not auto-allow safe builtin tokens in direct argv evaluation", () => {
139+const analysis = analyzeArgvCommand({ argv: ["pwd"], cwd: "/tmp", platform: "linux" });
140+const result = evaluateExecAllowlist({
141+ analysis,
142+allowlist: [],
143+safeBins: new Set(),
144+cwd: "/tmp",
145+});
146+expect(result.allowlistSatisfied).toBe(false);
147+});
148+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。