Skip to content

Commit 1dffcd4

Browse files
committed
test: derive argPattern from production code instead of hardcoding format
1 parent 47e7329 commit 1dffcd4

1 file changed

Lines changed: 26 additions & 29 deletions

File tree

src/infra/exec-approvals-windows-cmdlet.test.ts

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { resolvePowerShellPath } from "./executable-path.js";
99

1010
const WINDOWS_PLATFORM = "win32";
1111
const safeBins = resolveSafeBins(undefined);
12+
const CMD = "Get-ChildItem -LiteralPath C:\\Users\\foo";
1213

1314
function evalWindows(command: string, allowlist: Array<{ pattern: string; argPattern?: string }>) {
1415
return evaluateShellAllowlist({
@@ -20,52 +21,52 @@ function evalWindows(command: string, allowlist: Array<{ pattern: string; argPat
2021
});
2122
}
2223

24+
/** Derive the allow-always entry that production code would generate for a command. */
25+
function deriveAllowAlwaysEntry(command: string) {
26+
const analysis = evalWindows(command, []);
27+
const entries = resolveAllowAlwaysPatternEntries({
28+
segments: analysis.segments,
29+
cwd: process.cwd(),
30+
platform: WINDOWS_PLATFORM,
31+
});
32+
expect(entries.length).toBe(1);
33+
return entries[0];
34+
}
35+
2336
describe("windows powershell cmdlet allowlist fallback", () => {
24-
it("matches a bare cmdlet against a powershell allowlist entry with argPattern", () => {
25-
const psPath = resolvePowerShellPath();
26-
// Build the argPattern that collectAllowAlwaysPatterns would generate:
27-
// argv.slice(1) of syntheticArgv [psPath, "Get-ChildItem", "-LiteralPath", "C:\\Users\\foo"]
28-
// = ["Get-ChildItem", "-LiteralPath", "C:\\Users\\foo"]
29-
// joined with \x00 and regex-escaped. Note: - is not a regex metachar, so not escaped.
30-
const argPattern = `^Get-ChildItem\x00-LiteralPath\x00C:\\\\Users\\\\foo\x00$`;
37+
it("matches a bare cmdlet against its derived allow-always entry", () => {
38+
const entry = deriveAllowAlwaysEntry(CMD);
3139

32-
const result = evalWindows("Get-ChildItem -LiteralPath C:\\Users\\foo", [
33-
{ pattern: psPath, argPattern },
34-
]);
40+
const result = evalWindows(CMD, [entry]);
3541

3642
expect(result.analysisOk).toBe(true);
3743
expect(result.allowlistSatisfied).toBe(true);
3844
});
3945

4046
it("rejects a bare cmdlet when argPattern does not include the cmdlet name", () => {
41-
const psPath = resolvePowerShellPath();
42-
// argPattern only matches args, not the cmdlet name — should NOT match
43-
const argPattern = `^-LiteralPath\x00C:\\\\Users\\\\foo\x00$`;
47+
const entry = deriveAllowAlwaysEntry(CMD);
48+
// Strip the cmdlet name from the argPattern — should no longer match
49+
const tampered = { ...entry, argPattern: entry.argPattern!.replace("Get-ChildItem", "Remove-Item") };
4450

45-
const result = evalWindows("Get-ChildItem -LiteralPath C:\\Users\\foo", [
46-
{ pattern: psPath, argPattern },
47-
]);
51+
const result = evalWindows(CMD, [tampered]);
4852

4953
expect(result.analysisOk).toBe(true);
5054
expect(result.allowlistSatisfied).toBe(false);
5155
});
5256

5357
it("rejects a bare cmdlet when no powershell entry exists in the allowlist", () => {
54-
const result = evalWindows("Get-ChildItem -LiteralPath C:\\Users\\foo", [
55-
{ pattern: "/usr/bin/node" },
56-
]);
58+
const result = evalWindows(CMD, [{ pattern: "/usr/bin/node" }]);
5759

5860
expect(result.analysisOk).toBe(true);
5961
expect(result.allowlistSatisfied).toBe(false);
6062
});
6163

6264
it("matches a powershell-wrapped cmdlet after wrapper stripping", () => {
63-
const psPath = resolvePowerShellPath();
64-
const argPattern = `^Get-ChildItem\x00-LiteralPath\x00C:\\\\Users\\\\foo\x00$`;
65+
const entry = deriveAllowAlwaysEntry(CMD);
6566

6667
const result = evalWindows(
6768
'powershell -NoProfile -Command "Get-ChildItem -LiteralPath C:\\Users\\foo"',
68-
[{ pattern: psPath, argPattern }],
69+
[entry],
6970
);
7071

7172
expect(result.analysisOk).toBe(true);
@@ -75,17 +76,15 @@ describe("windows powershell cmdlet allowlist fallback", () => {
7576
it("matches a bare powershell path allowlist entry without argPattern (broad grant)", () => {
7677
const psPath = resolvePowerShellPath();
7778

78-
const result = evalWindows("Get-ChildItem -LiteralPath C:\\Users\\foo", [
79-
{ pattern: psPath },
80-
]);
79+
const result = evalWindows(CMD, [{ pattern: psPath }]);
8180

8281
expect(result.analysisOk).toBe(true);
8382
expect(result.allowlistSatisfied).toBe(true);
8483
});
8584

8685
it("generates allow-always pattern with cmdlet name in argPattern", () => {
8786
const psPath = resolvePowerShellPath();
88-
const analysis = evalWindows("Get-ChildItem -LiteralPath C:\\Users\\foo", []);
87+
const analysis = evalWindows(CMD, []);
8988

9089
const patterns = resolveAllowAlwaysPatterns({
9190
segments: analysis.segments,
@@ -96,7 +95,6 @@ describe("windows powershell cmdlet allowlist fallback", () => {
9695
expect(patterns.length).toBe(1);
9796
expect(patterns[0]).toBe(psPath);
9897

99-
// Verify that the full pattern entries include an argPattern with the cmdlet name
10098
const entries = resolveAllowAlwaysPatternEntries({
10199
segments: analysis.segments,
102100
cwd: process.cwd(),
@@ -106,13 +104,12 @@ describe("windows powershell cmdlet allowlist fallback", () => {
106104
expect(entries.length).toBe(1);
107105
expect(entries[0].pattern).toBe(psPath);
108106
expect(entries[0].argPattern).toBeDefined();
109-
// The argPattern should match the cmdlet name
110107
expect(entries[0].argPattern).toContain("Get-ChildItem");
111108
});
112109

113110
it("does not apply cmdlet fallback on non-windows platforms", () => {
114111
const result = evaluateShellAllowlist({
115-
command: "Get-ChildItem -LiteralPath /home/foo",
112+
command: CMD,
116113
allowlist: [{ pattern: resolvePowerShellPath() }],
117114
safeBins,
118115
cwd: process.cwd(),

0 commit comments

Comments
 (0)