Skip to main content

maf-sandbox-wslc

PyPI Python License

Experimental. Releases before 1.0 may change or remove APIs. Importing this package emits MafSandboxWslcExperimentalWarning.

Run sandbox commands in Linux containers managed by wslc, the container CLI included with WSL. This backend transfers input files, returns command output and reads artifacts on supported engines. It has no Azure dependency.

This is an independent package, not a Microsoft product.

Quickstart

pip install maf-sandbox-wslc
from maf_sandbox import Isolation, SandboxRouter
from maf_sandbox_wslc import WslcSandboxBackend, WslcSandboxConfig

backend = await WslcSandboxBackend.create(WslcSandboxConfig())
router = SandboxRouter([backend], min_isolation=Isolation.CONTAINER)

Containers share the WSL kernel. The explicit minimum permits container isolation, below the router's default microVM minimum.

Use Windows with WSL 2.9.3 or later. wsl --version reports the installed version. The backend's CLI contract has been measured on WSLC 2.9.4.0 and 2.9.12.0.

The Python event loop must support subprocesses. Windows' default Proactor loop does; WindowsSelectorEventLoopPolicy does not.

See the Bicep sample or CodeAct sample for complete applications.

Supported operations

Setting Behavior
Isolation CONTAINER
Capabilities EXEC, FILES_IN; FILES_OUT through the async factory on WSLC 3.0.2.0+
Guest OS POSIX
Network CLOSED; ALLOWLIST with a configured proxy
Lifetime Conversation or separate sandbox per call
Transfer ceiling 8 MiB per file, 32 MiB total, 64 files per direction
Command output 8 MiB of stdout and stderr together; more refuses the call and discards the container
Cleanup Disposal; no reclaim or snapshot reset

await WslcSandboxBackend.create(WslcSandboxConfig()) additionally declares FILES_OUT after a bounded version check reports WSLC 3.0.2.0 or later. The plain constructor, older versions and unreadable versions withhold it. Directory listing, file deletion, runtime run_code and host-tool calls remain unavailable. A kind requiring an undeclared capability is refused before attachment.

Output files

Use the async factory before constructing the router to enable output reads. Every acquire rechecks the supported version before creating or reusing a workload. WSLC 3.0.2.0 is the tested minimum; its container cp CONTAINER:PATH - streams engine tar metadata and binary bytes without guest utilities or host temporary files. The file must be in the container root filesystem; mounted-volume output is not qualified.

stat_file reads only the effective entry header, reporting final symlinks as links. read_file refuses links and every other non-regular entry, checks the size before reading its body, and requires a complete archive and successful engine exit. Each read enforces the smaller of the caller's cap and 8 MiB. GNU/PAX metadata is limited to 64 KiB and 32 headers; sparse, malformed, truncated and extra-entry archives are refused. Stderr retention and trailing zero padding each have a separate 64 KiB cap. The router enforces the 32 MiB/64-file collection ceilings.

Output confinement is checked, not atomic. Engine metadata checks each ancestor, including those above the working directory, and refuses links even when they point inside the base. A guest that keeps running can replace an ancestor after its check and redirect the later copy. The engine reads with rootfs authority, so a redirected read may reach files the image user cannot read. There is no freezer or held no-follow traversal here; hosts must account for this residual when enabling FILES_OUT.

Timeouts, cancellation and metadata-only reads kill and reap the CLI, with a three-second cleanup allowance for draining pipes. This does not terminate guest programs. On a supported engine the factory also selects archive metadata for input path checks; writes still run as the image user.

Memory and CPU limits are optional configuration: WslcSandboxConfig(memory="512M", cpus=1.5). They apply to the workload and to its egress proxy. The memory limit does not cover swap, so a workload can page past it. Unlike the Docker backend, this one sets no PID limit, drops no capabilities and leaves no-new-privileges off, because wslc container run has no flag for any of them. See resource limits and hardening.

Acquisition checks sh for commands. Input transfer also needs the external /usr/bin/test command, and sh plus mkdir, cat, wc, mv and rm for the image user: its probe and every write run through that user's shell. A resolved image user is needed only where a base has to be created, because a write runs as that user and stamps nothing. Every acquire still asks, though: id -u always, and id -g when the image names its user rather than numbering it. Working-directory setup needs root /bin/sh, mkdir, chown and ls on the pinned system PATH, pwd as a builtin or command, and the image user's mkdir where it leaves the base to that user, checked when a base has to be created rather than at acquisition, so an image whose base already exists needs none of them. Both acquisition and root path probes invoke that absolute executable, without searching the guest's PATH. Failed prerequisite checks are retryable; another test on PATH is not a fallback. The image must protect /usr/bin/test, its dependencies and ancestor directories from the runtime user.

Input files and their limits

Acquisition prepares the storage base for workloads using commands or files. work_dir=None selects /maf-sandbox/work; an explicit path requests that exact base. Existing directories keep their contents, ownership and modes.

A missing base is created as root only where every directory root acts inside is root's and writable by nobody else, since nothing there can be swapped; the base then goes to the image user through a held chown. Anywhere else — under a directory the image user owns, or under /tmp — the image user creates it with mkdir -p, and a base that user cannot create raises PermissionError. A base that was already there keeps its owner, whichever path named it: acquire preserves the ownership it finds. An unset Config.User means root. Named users or an omitted group need working id commands. An id that answers something unusable leaves ownership unresolved; one that does not answer at all discards the container, because it may still be running there. Unresolved ownership refuses any capability that has to create a base, EXEC and FILES_IN alike, before anything is created; an existing base needs no resolved identity at all.

Writes run as the image user. The file and any missing parents belong to that user. A destination it cannot write raises PermissionError; nothing falls back to root. The path check is separate from the write, so a guest can swap a checked parent for a symlink first. The write then reaches only what the image user could write anyway. A write this host stops discards the container, as exec does — an expired deadline, or stdout reaching the read cap.

Setup is bounded the same way. Root holds each directory it enters, confirms it with pwd -P and has CDPATH cleared, and acts only where no swap can reach; where one could, the image user creates the base. Cancelling a write after it starts is not a rollback: short content is refused, but content that fully arrived still lands.

Some path classification runs inside the guest, as root, with the image's test. Its answer can pick which refusal a caller sees. A write it lets through still runs as the image user.

Without the factory's supported archive contract, input path inspection copies into a private host temporary directory. Its disk use is not bounded by input limits or stdout limits. Normal exits remove the temporary copy, but a host crash or cleanup failure can leave it behind. Use an enforced temporary-filesystem quota when that consumption is unacceptable. Use the factory on WSLC 3.0.2.0+: changes to directory extraction can make the legacy probe fail on trees containing symlinks.

See the backend guide for the precise file contract and remaining limits.

Network access

Without a proxy, only Egress.CLOSED is supported. Containers use --network none, and an allowlist request is refused.

Build the packaged proxy and select it in configuration:

from maf_sandbox_wslc import WslcSandboxConfig, proxy_build_context

print(f"wslc build -t maf-egress-proxy:local {proxy_build_context()}")
config = WslcSandboxConfig(egress_proxy_image="maf-egress-proxy:local")

Each allowlisted sandbox gets an internal network and a filtering proxy. The proxy is its only route out. The packaged image builds pinned iron-proxy with a policy patch. It terminates guest TLS to enforce host, method and path rules, validates upstream certificates, and supplies a per-sandbox CA certificate at a fixed readable guest path independent of the workload's work directory. Its CA key stays in the proxy. Clients must honor the injected CA environment or configure trust explicitly. Public destinations require TLS on every port. Private destinations require TLS by default; WslcSandboxConfig(allow_private_http=True, egress_proxy_image=...) permits plaintext only when the listed host resolves to a private address. Use that option only for development or test workloads. Unrestricted access remains unavailable.

The router's observer can receive proxy decisions after confirmed removal. Failed removal can leave a window unreported. See observability.

Cleanup and retention

Router-managed calls dispose their sandbox even when a kind claims to keep changes inside its call directory. WSLC cannot establish the ancestry needed for privileged reclaim and declares no snapshot reset.

dispose(key, kind=...) removes a selected kind and its infrastructure. dispose_scope(scope, thread_id) discovers conversation resources through engine labels, not only process memory.

An operator can clean up stopped workloads and orphan infrastructure:

from datetime import timedelta

result = await backend.reap(timedelta(hours=24), scope="my-app")
print(result.disposed, result.proxies_removed, result.networks_removed)
for failure in result.failures:
    print(failure)

Run this under the Windows account that owns the WSLC engine. The backend starts no timer. An external scheduler must select the environment, prevent overlapping sweeps and monitor failures.

Pause and drain acquisition, restarts and other resource changes in the selected scope before sweeping. Engine rechecks cannot make name-based network deletion atomic. Keep Windows and WSL clocks synchronized.

Resource Age used for retention
Stopped workload Time since its inspected stop; a restart and stop resets it.
Never-started workload Creation time.
Proxy without a workload Creation time, including for a running orphan proxy.
Network alone Its backend creation-time label; missing labels retain it for manual handling.

Running or uncertain workload states retain their whole group. Resources exactly at the cutoff are retained. An incomplete inventory prevents deletion; individual failures are reported. Retention does not infer that a host has died or impose a maximum running lifetime.

See operations for scheduling and cleanup ownership. The live suite covers writes, networks and separate-process retention using explicitly configured images.

Maintained by SOKOLAI BV.

Metadata

Release files for maf-sandbox-wslc 0.28.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for maf-sandbox-wslc 0.28.0
File Size Uploaded
maf_sandbox_wslc-0.28.0.tar.gz 71.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for maf-sandbox-wslc 0.28.0
File Interpreter ABI Platform
maf_sandbox_wslc-0.28.0-py3-none-any.whl Python 3 none any Details

Total release size: 148.0 kB

Release files / maf_sandbox_wslc-0.28.0.tar.gz

Download URL maf_sandbox_wslc-0.28.0.tar.gz
Size 71.8 kB
Tags Source
SHA-256 checksum
How to use checksums
5ea43b21ea76b2acafb8be044d05ee7cda4b60ee8440af003e8f75afb09dcfea
BLAKE2b-256 checksum
How to use checksums
7130ccc88dcc40982c8036e9b3427c49c4c47a39d5c27c62bd673b40b3820985
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / maf_sandbox_wslc-0.28.0-py3-none-any.whl

Download URL maf_sandbox_wslc-0.28.0-py3-none-any.whl
Size 76.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c094458aea25322b226b373f7f5db7c2d84ed4451d25a9104548e3c040c275fc
BLAKE2b-256 checksum
How to use checksums
2b4c94b12604ed26e5cdea5c769ff35f21eef283243dfd0267d351619e5efeb4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release history Release notifications | RSS feed

0.29.1

2 release files

0.29.0

2 release files

This release

0.28.0 This release

2 release files

0.26.1

2 release files

0.26.0

2 release files

0.25.1

2 release files

0.25.0

2 release files

0.24.0

2 release files

0.23.1

2 release files

0.23.0

2 release files

0.22.0

2 release files

0.21.1

2 release files

0.20.0

2 release files

0.19.1

2 release files

0.19.0

2 release files

0.13.0

2 release files

0.12.0

2 release files

0.11.3

2 release files

0.11.2

2 release files

0.10.2

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.7

2 release files

0.5.6

2 release files

0.5.5

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page