Skip to content

Commit 36ca060

Browse files
senamakelmedullabot
andcommitted
test(audit): normalize Windows PowerShell fixture module paths
Co-authored-by: Medulla <medulla@tinyhumans.ai>
1 parent f52420d commit 36ca060

1 file changed

Lines changed: 76 additions & 1 deletion

File tree

‎crates/tinysecurity-audit/src/sink_windows_negative_tests.rs‎

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,79 @@ fn windows_reparse_directory_is_denied_without_outside_mutation()
221221
Ok(())
222222
}
223223

224+
// PowerShell 7 only normalizes module paths when it launches Windows PowerShell
225+
// directly. Cargo and this Rust test process are intermediaries, so construct the
226+
// Desktop defaults in the child instead of inheriting potentially Core modules.
227+
fn use_desktop_module_paths(command: &mut std::process::Command) {
228+
command.env_remove("PSModulePath");
229+
}
230+
231+
#[test]
232+
fn windows_powershell_child_reconstructs_desktop_module_paths()
233+
-> std::result::Result<(), Box<dyn std::error::Error>> {
234+
// Probe the actual launch environment independently of the account fixture.
235+
// Its inherited branch can succeed under Bash and fail under PowerShell 7.
236+
// Neither branch creates an account or exposes paths or error text.
237+
const PROBE: &str = r"
238+
$ErrorActionPreference = 'Stop'
239+
$desktop = [int]($PSVersionTable.PSEdition -ceq 'Desktop')
240+
$corePath = [int]($env:PSModulePath -match '(?i)\\PowerShell\\7(?:\\|;)')
241+
[Console]::WriteLine(('audit-module-context desktop={0} core-path={1}' -f $desktop,$corePath))
242+
$failed = $false
243+
$ordinal = 0
244+
foreach ($name in @('ConvertTo-SecureString','New-LocalUser','Get-Acl','Set-Acl')) {
245+
$ordinal++
246+
try {
247+
$command = Get-Command -Name $name -ErrorAction Stop
248+
$core = [int]($command.Module.CompatiblePSEditions -contains 'Core' -and $command.Module.CompatiblePSEditions -notcontains 'Desktop')
249+
[Console]::WriteLine(('audit-module-context command={0} resolved=1 core-only={1} category=0' -f $ordinal,$core))
250+
if ($core -ne 0) { $failed = $true }
251+
} catch {
252+
[Console]::WriteLine(('audit-module-context command={0} resolved=0 core-only=0 category={1}' -f $ordinal,[int]$_.CategoryInfo.Category))
253+
$failed = $true
254+
}
255+
}
256+
if ($failed -or $desktop -ne 1) { exit 1 }
257+
exit 0
258+
";
259+
let parent = std::env::var_os("PSModulePath");
260+
for scenario in 0..3 {
261+
let mut command = std::process::Command::new("powershell.exe");
262+
if scenario == 2 {
263+
// Deliberately start with a foreign-edition module search path even
264+
// when this test's parent was launched from Bash.
265+
command.env("PSModulePath", r"C:\Program Files\PowerShell\7\Modules");
266+
}
267+
if scenario != 0 {
268+
use_desktop_module_paths(&mut command);
269+
}
270+
let output = command
271+
.args(["-NoProfile", "-NonInteractive", "-Command", PROBE])
272+
.output()?;
273+
eprintln!(
274+
"audit-module-context scenario={scenario} exit={:?}",
275+
output.status.code()
276+
);
277+
for line in String::from_utf8_lossy(&output.stdout).lines() {
278+
if line.starts_with("audit-module-context ")
279+
&& line
280+
.chars()
281+
.all(|c| c.is_ascii_alphanumeric() || "=- ".contains(c))
282+
{
283+
eprintln!("{line}");
284+
}
285+
}
286+
if scenario != 0 {
287+
assert!(
288+
output.status.success(),
289+
"Desktop child must resolve its own security and local-account cmdlets"
290+
);
291+
}
292+
}
293+
assert_eq!(std::env::var_os("PSModulePath"), parent);
294+
Ok(())
295+
}
296+
224297
#[test]
225298
fn windows_second_ordinary_account_cannot_read_or_replace_the_namespace()
226299
-> std::result::Result<(), Box<dyn std::error::Error>> {
@@ -254,7 +327,9 @@ fn windows_second_ordinary_account_cannot_read_or_replace_the_namespace()
254327
"audit-account-stage=0 outer-script-units={}",
255328
ORDINARY_ACCOUNT_SCRIPT.encode_utf16().count()
256329
);
257-
let output = std::process::Command::new("powershell.exe")
330+
let mut command = std::process::Command::new("powershell.exe");
331+
use_desktop_module_paths(&mut command);
332+
let output = command
258333
.args([
259334
"-NoProfile",
260335
"-NonInteractive",

0 commit comments

Comments
 (0)