@@ -9,6 +9,7 @@ import { resolvePowerShellPath } from "./executable-path.js";
99
1010const WINDOWS_PLATFORM = "win32" ;
1111const safeBins = resolveSafeBins ( undefined ) ;
12+ const CMD = "Get-ChildItem -LiteralPath C:\\Users\\foo" ;
1213
1314function 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+
2336describe ( "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