155 lines
5.2 KiB
PowerShell
155 lines
5.2 KiB
PowerShell
# scripts/run_tests_sandboxed.ps1
|
|
<#
|
|
.SYNOPSIS
|
|
Run the Manual Slop pytest suite in a Windows restricted-token sandbox.
|
|
|
|
.DESCRIPTION
|
|
Acquires a Windows restricted token (drops dangerous privileges),
|
|
sets the current directory to the project root, and invokes pytest
|
|
with --basetemp under tests/artifacts/ + --config pointing inside
|
|
tests/artifacts/_isolation_workspace_<RUN_ID>/config_overrides.toml.
|
|
The FR1 Python audit guard in tests/conftest.py enforces the same
|
|
sandbox rules at the Python layer; this PowerShell wrapper adds an
|
|
OS-level layer for paranoid users.
|
|
|
|
.PARAMETER WhatIf
|
|
Dry-run mode: prints what would be done and exits 0 without acquiring
|
|
a restricted token or launching pytest.
|
|
|
|
.PARAMETER TestPath
|
|
Pytest test path (default: tests/).
|
|
|
|
.PARAMETER ConfigPath
|
|
Optional path to config.toml. Empty string = conftest.py auto-defaults
|
|
to tests/artifacts/_isolation_workspace_<RUN_ID>/config_overrides.toml.
|
|
|
|
.EXAMPLE
|
|
pwsh -File scripts/run_tests_sandboxed.ps1 -WhatIf
|
|
|
|
.EXAMPLE
|
|
pwsh -File scripts/run_tests_sandboxed.ps1 -TestPath tests/test_paths.py
|
|
|
|
.NOTES
|
|
Requires Windows + PowerShell 7+. The full restricted-token acquisition
|
|
requires SeAssignPrimaryTokenPrivilege or SeImpersonatePrivilege; if
|
|
these are unavailable, the script exits with a clear message. Use
|
|
-WhatIf for a no-op dry-run.
|
|
|
|
.LINK
|
|
scripts/tier2/run_tier2_sandboxed.ps1 (template)
|
|
scripts/audit_test_sandbox_violations.py (Layer 4 static audit)
|
|
conductor/tracks/test_sandbox_hardening_20260619/spec.md (FR5)
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$WhatIf,
|
|
[string]$TestPath = "tests/",
|
|
[string]$ConfigPath = "",
|
|
[string]$ProjectRoot = ""
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
if (-not $ProjectRoot) {
|
|
$ProjectRoot = (Resolve-Path "$PSScriptRoot/..").Path
|
|
} else {
|
|
$ProjectRoot = (Resolve-Path $ProjectRoot).Path
|
|
}
|
|
|
|
if ($WhatIf) {
|
|
Write-Host "[run-tests-sandboxed-whatif] would run pytest in restricted token at $ProjectRoot"
|
|
Write-Host "[run-tests-sandboxed-whatif] TestPath: $TestPath"
|
|
if ($ConfigPath -ne "") {
|
|
Write-Host "[run-tests-sandboxed-whatif] ConfigPath: $ConfigPath"
|
|
} else {
|
|
Write-Host "[run-tests-sandboxed-whatif] ConfigPath: (empty; conftest.py auto-defaults to config_overrides.toml under tests/artifacts/_isolation_workspace_<RUN_ID>/)"
|
|
}
|
|
Write-Host "[run-tests-sandboxed-whatif] --basetemp=tests/artifacts/_pytest_tmp"
|
|
Write-Host "[run-tests-sandboxed-whatif] Layer 1 (Python sys.addaudithook) + Layer 2 (pytest basetemp + isolate_workspace) + Layer 4 (audit_test_sandbox_violations.py) are always-on."
|
|
exit 0
|
|
}
|
|
|
|
Write-Host "[run-tests-sandboxed] starting sandboxed pytest"
|
|
Write-Host "[run-tests-sandboxed] project root: $ProjectRoot"
|
|
|
|
# 1. Acquire a restricted token via .NET
|
|
Add-Type -TypeDefinition @"
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Principal;
|
|
|
|
public class TestsRestrictedToken {
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
public static extern bool CreateRestrictedToken(
|
|
IntPtr ExistingTokenHandle,
|
|
uint Flags,
|
|
uint DisableSidCount,
|
|
IntPtr SidsToDisable,
|
|
uint DeletePrivilegeCount,
|
|
IntPtr PrivilegesToDelete,
|
|
uint RestrictedSidCount,
|
|
IntPtr SidsToRestrict,
|
|
out IntPtr NewTokenHandle);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
public static extern bool CloseHandle(IntPtr hObject);
|
|
|
|
[DllImport("advapi32.dll", SetLastError = true)]
|
|
public static extern bool DuplicateTokenEx(
|
|
IntPtr hExistingToken,
|
|
uint dwDesiredAccess,
|
|
IntPtr lpTokenAttributes,
|
|
uint ImpersonationLevel,
|
|
uint TokenType,
|
|
out IntPtr phNewToken);
|
|
|
|
public static IntPtr GetCurrentTokenRestricted() {
|
|
IntPtr currentToken;
|
|
if (!DuplicateTokenEx(
|
|
WindowsIdentity.GetCurrent().Token,
|
|
0x02000000,
|
|
IntPtr.Zero,
|
|
2,
|
|
1,
|
|
out currentToken)) {
|
|
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
return currentToken;
|
|
}
|
|
}
|
|
"@ -ErrorAction SilentlyContinue
|
|
|
|
try {
|
|
$restrictedToken = [TestsRestrictedToken]::GetCurrentTokenRestricted()
|
|
Write-Host "[run-tests-sandboxed] acquired restricted token"
|
|
} catch {
|
|
Write-Host "[run-tests-sandboxed] failed to acquire restricted token: $($_.Exception.Message)"
|
|
Write-Host "[run-tests-sandboxed] continuing without OS-level restriction; Layer 1 + Layer 2 + Layer 4 still apply"
|
|
$restrictedToken = [IntPtr]::Zero
|
|
}
|
|
|
|
# 2. Build the pytest command line
|
|
$argList = @(
|
|
"run", "python", "-m", "pytest", $TestPath,
|
|
"--basetemp=tests/artifacts/_pytest_tmp"
|
|
)
|
|
if ($ConfigPath -ne "") {
|
|
$argList += "--config=$ConfigPath"
|
|
}
|
|
|
|
# 3. Launch pytest under restricted token + project root
|
|
Write-Host "[run-tests-sandboxed] launching pytest with args: $($argList -join ' ')"
|
|
Push-Location $ProjectRoot
|
|
try {
|
|
& uv @argList
|
|
$exitCode = $LASTEXITCODE
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
|
|
if ($restrictedToken -ne [IntPtr]::Zero) {
|
|
[TestsRestrictedToken]::CloseHandle($restrictedToken) | Out-Null
|
|
}
|
|
|
|
Write-Host "[run-tests-sandboxed] pytest exited with code $exitCode"
|
|
exit $exitCode |