Private
Public Access
0
0

feat(scripts): add run_tests_sandboxed.ps1 (FR5 OS-level sandbox) + smoke test

This commit is contained in:
2026-06-19 07:50:34 -04:00
parent 0a8d394537
commit dc5afc21ec
2 changed files with 171 additions and 1 deletions
+155
View File
@@ -0,0 +1,155 @@
# 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
+16 -1
View File
@@ -245,4 +245,19 @@ def test_appcontroller_init_does_not_load_config() -> None:
"(this would trigger config reads before fixtures apply)"
)
return
raise AssertionError("AppController.__init__ not found")
raise AssertionError("AppController.__init__ not found")
@pytest.mark.skipif(os.name != "nt", reason="Windows-only sandbox wrapper")
def test_run_tests_sandboxed_whatif() -> None:
"""pwsh -File scripts/run_tests_sandboxed.ps1 -WhatIf exits 0 without
acquiring a restricted token or launching pytest.
[C: scripts/run_tests_sandboxed.ps1]"""
result = subprocess.run(
["pwsh", "-File", "scripts/run_tests_sandboxed.ps1", "-WhatIf"],
capture_output=True, text=True, timeout=30,
)
assert result.returncode == 0, (
f"Expected exit 0, got {result.returncode}: {result.stderr}"
)
assert "whatif" in result.stdout.lower() or "[run-tests-sandboxed-whatif]" in result.stdout