Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/configs/isolation_session_streaming_smoke.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"containerId": "isolation-session-streaming-smoke",
"containment": "isolation_session",
"process": {
"commandLine": "echo line_1 & ping -n 2 127.0.0.1 >nul & echo line_2 & ping -n 2 127.0.0.1 >nul & echo line_3",
"commandLine": "echo line_1 & ping -n 6 127.0.0.1 >nul & echo line_2 & ping -n 6 127.0.0.1 >nul & echo line_3",
"timeout": 30000
},
"network": {
Expand Down
13 changes: 13 additions & 0 deletions tests/configs/seatbelt_fs_denied_metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containment": "seatbelt",
"process": {
"commandLine": "echo FS_PARENT_ENTRIES=$(ls {{TESTDIR}}/rw | tr '\\n' ','); (ls {{TESTDIR}}/rw/nested >/dev/null 2>&1 && echo FS_DENIED_LIST_SUCCEEDED); (/usr/bin/stat -f %z {{TESTDIR}}/rw/nested/secret.txt >/dev/null 2>&1 && echo FS_DENIED_STAT_SUCCEEDED); (rm {{TESTDIR}}/rw/nested/secret.txt 2>/dev/null && echo FS_DENIED_DELETE_SUCCEEDED); (mv {{TESTDIR}}/rw/nested/secret.txt {{TESTDIR}}/rw/moved.txt 2>/dev/null && echo FS_DENIED_RENAME_SUCCEEDED); echo FS_PROBE_DONE",
"timeout": 10000
},
"filesystem": {
"readwritePaths": ["{{TESTDIR}}/rw"],
"deniedPaths": ["{{TESTDIR}}/rw/nested"]
}
}
18 changes: 18 additions & 0 deletions tests/configs/seatbelt_fs_denied_unix_socket.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containment": "seatbelt",
"process": {
"commandLine": "/usr/bin/python3 -c \"import socket\ndef probe(tag, path, op):\n s = socket.socket(socket.AF_UNIX)\n try:\n getattr(s, op)(path)\n print(tag + '_SUCCEEDED')\n except OSError as e:\n print(tag + '_REFUSED', e.errno)\n s.close()\nprobe('UNIX_GRANTED_CONNECT', '{{TESTDIR}}/rw/open/agent.sock', 'connect')\nprobe('UNIX_DENIED_CONNECT', '{{TESTDIR}}/rw/nested/agent.sock', 'connect')\nprobe('UNIX_DENIED_BIND', '{{TESTDIR}}/rw/nested/new.sock', 'bind')\nprint('UNIX_PROBE_DONE')\"",
"timeout": 20000
},
"filesystem": {
"readonlyPaths": ["{{DEVDIR}}"],
"readwritePaths": ["{{TESTDIR}}/rw"],
"deniedPaths": ["{{TESTDIR}}/rw/nested"]
},
"network": {
"egress": { "default": "allow" },
"ingress": { "default": "allow", "hostLoopback": "allow" }
}
}
13 changes: 13 additions & 0 deletions tests/configs/seatbelt_fs_denied_write.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containment": "seatbelt",
"process": {
"commandLine": "(echo denied > {{TESTDIR}}/rw/nested/written.txt && echo FS_NESTED_DENIED_WRITE_SUCCEEDED); (echo denied > {{TESTDIR}}/rw/sibling.txt && echo FS_RW_WRITE_CREATED); echo FS_PROBE_DONE",
"timeout": 10000
},
"filesystem": {
"readwritePaths": ["{{TESTDIR}}/rw"],
"deniedPaths": ["{{TESTDIR}}/rw/nested"]
}
}
14 changes: 14 additions & 0 deletions tests/configs/seatbelt_path_alias_denied.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containment": "seatbelt",
"process": {
"commandLine": "(cat {{TESTDIR}}/alias/secret.txt && echo PATH_ALIAS_DENIED_READ_SUCCEEDED); (echo x > {{TESTDIR}}/alias/d.txt && echo PATH_ALIAS_DENIED_WRITE_SUCCEEDED); echo PATH_PROBE_DONE",
"timeout": 10000
},
"filesystem": {
"deniedPaths": ["{{TESTDIR_TMP}}/alias"],
"readonlyPaths": ["{{TESTDIR}}/alias"],
"readwritePaths": ["{{TESTDIR}}/./alias/"]
}
}
16 changes: 16 additions & 0 deletions tests/configs/wslc_network_bridged.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containerId": "wslc-network-bridged",
"containment": "wslc",
"process": {
"commandLine": "python3 -c \"import socket\ns = socket.socket(); s.settimeout(8)\ntry:\n s.connect(('1.1.1.1', 443)); print('NET_EGRESS_REACHED')\nexcept OSError as e:\n print('NET_EGRESS_BLOCKED', e.errno)\nprint('NET_PROBE_DONE')\""
},
"network": {
"egress": { "default": "allow" },
"ingress": { "default": "allow", "hostLoopback": "allow" }
},
"wslc": {
"image": "python:3.12-alpine"
}
}
19 changes: 19 additions & 0 deletions tests/configs/wslc_network_egress_deny_rules_rejected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containerId": "wslc-network-egress-deny-rules-rejected",
"containment": "wslc",
"process": {
"commandLine": "echo WSLC_EGRESS_RULES_SHOULD_NOT_RUN"
},
"network": {
"egress": {
"default": "allow",
"deny": [{ "to": [{ "cidr": "10.0.0.0/8" }] }]
},
"ingress": { "default": "allow", "hostLoopback": "allow" }
},
"wslc": {
"image": "alpine:latest"
}
}
19 changes: 19 additions & 0 deletions tests/configs/wslc_network_egress_rules_rejected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containerId": "wslc-network-egress-rules-rejected",
"containment": "wslc",
"process": {
"commandLine": "echo WSLC_EGRESS_RULES_SHOULD_NOT_RUN"
},
"network": {
"egress": {
"default": "deny",
"allow": [{ "to": [{ "cidr": "1.1.1.1/32" }], "ports": [{ "port": 443, "protocol": "tcp" }] }]
},
"ingress": { "default": "deny", "hostLoopback": "deny" }
},
"wslc": {
"image": "alpine:latest"
}
}
4 changes: 2 additions & 2 deletions tests/configs/wslc_network_isolated.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
"containerId": "wslc-network-isolated",
"containment": "wslc",
"process": {
"commandLine": "command -v wget >/dev/null 2>&1 || { echo 'wget not found in image' >&2; exit 1; }; wget -q -O /dev/null http://example.com && echo 'Network accessible' || echo 'Network blocked (expected)'"
"commandLine": "python3 -c \"import socket\ns = socket.socket(); s.settimeout(8)\ntry:\n s.connect(('1.1.1.1', 443)); print('NET_EGRESS_REACHED')\nexcept OSError as e:\n print('NET_EGRESS_BLOCKED', e.errno)\nprint('NET_PROBE_DONE')\""
},
"network": {
"egress": { "default": "deny" }
},
"wslc": {
"image": "alpine:latest"
"image": "python:3.12-alpine"
}
}
20 changes: 20 additions & 0 deletions tests/configs/wslc_network_proxy_credentials_rejected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containerId": "wslc-network-proxy-credentials-rejected",
"containment": "wslc",
"process": {
"commandLine": "echo WSLC_PROXY_CREDENTIALS_SHOULD_NOT_RUN",
"env": ["MXC_PROBE=1"]
},
"network": {
"egress": { "default": "allow" },
"ingress": { "default": "allow", "hostLoopback": "allow" }
},
"runtimeConfig": {
"networkProxy": "http://proxyuser:s3cr3t@127.0.0.1:8888"
},
"wslc": {
"image": "alpine:latest"
}
}
5 changes: 5 additions & 0 deletions tests/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ must be **dot-sourced, not imported as a module** β€” `Initialize-WpcContext`
publishes the suite context into the calling script's scope, which only works
because dot-sourcing merges scopes.

The IsolationSession suites dot-source `tests/scripts/lib/LoopbackAnchor.ps1`
for their positive network oracle: a host loopback listener an isolated session
can reach, so the assertion covers the session's network posture rather than
the runner's outbound internet access.

T2 (`appcontainer-bfs`) is out of scope: it is off by default behind the
`tier2_bfs` Cargo feature and is not in use, so the suite records no assertions
about it. The remaining `bfscfg` checks are guards, not coverage β€” invoking
Expand Down
81 changes: 81 additions & 0 deletions tests/scripts/lib/LoopbackAnchor.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
# LoopbackAnchor.ps1 β€” host-side loopback HTTP anchor for IsolationSession
# network tests.
#
# An isolated session is a separate OS logon session on the same machine, so a
# host loopback listener is reachable from inside it. Anchoring the positive
# network oracle here keeps it hermetic: it asserts the session's network
# posture rather than the runner's outbound internet access.

# Binds an ephemeral loopback port and serves a fixed body from a background
# runspace, so the caller can stay blocked on a sandbox exec while the
# contained process connects back. Returns $null if the port cannot be bound.
function Start-LoopbackAnchor {
# Bind port 0 and read back what the OS assigned, rather than reserving a
# port up front and re-binding it: another process can take the port in
# between, and the anchor then answers on a socket nobody can reach.
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
try {
$listener.Start()
} catch {
return $null
}
$port = $listener.LocalEndpoint.Port
$ps = [PowerShell]::Create()
[void]$ps.AddScript({
param($l)
$body = 'MXC-ISO-LOOPBACK-ANCHOR'
$response = [Text.Encoding]::ASCII.GetBytes(
"HTTP/1.1 200 OK`r`nContent-Type: text/plain`r`nContent-Length: $($body.Length)`r`nConnection: close`r`n`r`n$body")
while ($true) {
try {
$client = $l.AcceptTcpClient()
$stream = $client.GetStream()
# Read whatever request line the client sent before replying;
# curl will not report success if the peer resets first.
$stream.ReadTimeout = 2000
$buf = New-Object byte[] 1024
try { [void]$stream.Read($buf, 0, $buf.Length) } catch {}
$stream.Write($response, 0, $response.Length)
$stream.Flush()
$client.Close()
} catch { break }
}
}).AddArgument($listener)
$handle = $ps.BeginInvoke()
return [pscustomobject]@{
Port = $port
Url = "http://127.0.0.1:$port/"
Body = 'MXC-ISO-LOOPBACK-ANCHOR'
Stop = {
try { $listener.Stop() } catch {}
try { [void]$ps.EndInvoke($handle) } catch {}
try { $ps.Dispose() } catch {}
}.GetNewClosure()
}
}

function Stop-LoopbackAnchor {
param($Anchor)
if ($null -eq $Anchor) { return }
& $Anchor.Stop
}

# Command line that fetches the anchor from inside a sandbox.
#
# Deliberately a bare curl rather than a `&& echo REACHED || echo BLOCKED`
# token pair: the one-shot runner invokes wxc-exec with --debug, which echoes
# the command line, so any verdict token spelled here would appear in the
# captured output whether or not the probe ran. The anchor's response body
# cannot be echoed that way, which makes it the only honest oracle -- and
# curl's non-zero exit on a failed connect covers the negative side.
function Get-LoopbackAnchorCommand {
param(
[Parameter(Mandatory)][string]$Url,
[int]$TimeoutSec = 15
)
$curl = Join-Path $env:SystemRoot 'System32\curl.exe'
"$curl --silent --show-error --fail --max-time $TimeoutSec $Url"
Comment thread
theelliotm marked this conversation as resolved.
Resolved
}
Loading
Loading