Important
AICD.Microsandbox is an unofficial, independently maintained package
and is not an official microsandbox distribution.
.NET SDK targeting .NET 8 over microsandbox's native C ABI. It covers native loading, creation and detached creation, collected and streaming command execution, name-addressed lookup and lifecycle operations, cancellation, and explicit native handle ownership.
- Sandbox lifecycle, lookup/list, request-style stop/kill/drain, stop-and-wait, persisted-state removal, collected and streaming command execution, cancellation, and explicit handle ownership.
- Streaming exec events, collection/wait/control, PTY resize, and a single-take asynchronous stdin sink.
Sandbox.Filesystem: byte/string andStream-compatible read/write, list, stat, host copy in both directions, mkdir, remove/remove-directory, guest copy/rename, and exists.- Collected and streaming sandbox logs with sources, timestamps, cursors, follow mode, and 48 MiB receive buffers.
- Point-in-time and streaming sandbox metrics plus client-wide metrics keyed by sandbox name.
- Client image cache get/list/inspect/remove/prune/load/save operations with typed OCI metadata.
- Named volume create/get/list/remove operations with typed directory/disk metadata.
- Snapshot create/open/verify/get/list/list-directory/remove/reindex/export/import operations.
- Native SSH client/server handles, collected SSH exec, command/default-shell attach, stdio serving with compatibility aliases, and collected SFTP file operations.
- Raw agent connections by sandbox or socket path, byte-exact request/send/ready operations, and disposable raw streams.
- Live-handle and name-addressed sandbox modification with typed patch and plan contracts.
- Name-addressed collected metrics and snapshot convenience methods on
SandboxHandle. - Go-compatible creation options for root disks, bind rootfs, security profiles, registry auth, ports, network policy/DNS/TLS, secrets, patches, and volume mounts.
Direct host-side volume filesystem access is intentionally excluded because lexical path rooting alone does not prevent symlink traversal outside a volume root.
mise pins .NET 10 and provides the local workflow:
mise install
mise run checkRunnable .NET 10 file-based app examples cover basic lifecycle, streaming exec, filesystem I/O, detached sandboxes, and metrics:
dotnet run --file scripts/download-runtime.cs
source .runtime/env.sh
dotnet run --file examples/basic.cs -p:Version="$MICROSANDBOX_RELEASE_VERSION"Download the current package version's five prebuilt FFI assets from the
superradcompany/microsandbox GitHub release, validate them, and exercise the
current platform binary from TUnit:
mise run native-testCreate a managed-only development package:
mise run pack-local
mise run inspectOrdinary dotnet pack is release-shaped: it requires and includes all five native
runtime assets. Use mise run pack-local only when an explicit managed-only package
is needed for local development.
For a release-shaped package, download all five upstream FFI assets and the
release's checksums.sha256, then validate and stage them:
mise run native-download
mise run pack-release
mise run inspectnative-download uses gh release download in GitHub Actions and portable
curl locally. It fetches an exact tag and fixed asset list, then stage-native.sh
maps the Linux x64/arm64, macOS arm64, and Windows x64/arm64
release filenames to canonical libraries under runtimes/<rid>/native. It verifies
every SHA-256 against both the upstream checksum manifest and the version-bound,
reviewed native-assets.sha256 allowlist. It also validates each binary's format and
target architecture before transactionally replacing
the staged runtime tree and recording the source release version. pack-release
requires the staged native, NuGet package, and managed assembly versions to match.
To adopt another release, update the project Version and reviewed
native-assets.sha256 together before running scripts/download-native.sh VERSION.
Native binaries come from upstream release assets, are checksum-validated, and
are never committed here. Staged binaries and packages are ignored by git.
The SDK resolves the native library in this order:
- The explicit path passed to
MicrosandboxClient.Load(path). MICROSANDBOX_FFI_LIBRARY.- A NuGet-style
runtimes/<rid>/nativeasset beside the application. - The platform loader's normal search path.
Each candidate must export the complete ABI and report the same msb_version as
the managed package. Incompatible candidates are unloaded before resolution falls
back to the next candidate.
Install the package from NuGet.org:
dotnet add package AICD.Microsandboxusing Microsandbox;
var client = MicrosandboxClient.Load();
client.SetMsbPath("/path/to/msb");
await using var sandbox = await client.CreateAsync("dotnet-demo", new SandboxOptions
{
Image = "alpine:3.20",
MemoryMiB = 512,
CPUs = 1,
Replace = true,
Detached = true,
});
var result = await sandbox.ShellAsync("echo 'Hello from microsandbox!'");
Console.WriteLine(result.StandardOutput);
var ping = await sandbox.PingAsync();
Console.WriteLine($"agent latency: {ping.Latency}");
await using (var exec = await sandbox.ShellStreamingAsync("read value; echo $value", new ExecOptions
{
StdinPipe = true,
}))
{
await using var stdin = exec.TakeStdin()!;
await stdin.WriteAsync("hello\n"u8.ToArray());
await stdin.CompleteAsync();
while (true)
{
var message = await exec.ReceiveAsync();
if (message is ExecDoneEvent)
{
break;
}
if (message is ExecStandardOutputEvent output)
{
Console.Write(System.Text.Encoding.UTF8.GetString(output.Data));
}
}
}
await using (var writer = await sandbox.Filesystem.WriteStreamAsync("/tmp/large.bin"))
{
await writer.WriteAsync(new byte[1024]);
} // DisposeAsync sends EOF and waits for write confirmation.
await using var logs = await sandbox.LogStreamAsync(new LogStreamOptions { Follow = true });
var firstLog = await logs.ReceiveAsync();
await using var metrics = await sandbox.MetricsStreamAsync(TimeSpan.FromSeconds(1));
var firstSnapshot = await metrics.ReceiveAsync();
await using (var ssh = await sandbox.Ssh.OpenClientAsync(new SshClientOptions { EnableSftp = true }))
{
var sshResult = await ssh.ExecuteAsync("printf hello");
Console.WriteLine(sshResult.StandardOutputText);
await using var sftp = await ssh.OpenSftpAsync();
await sftp.WriteStringAsync("/tmp/message", "hello");
}
await using (var agent = await sandbox.ConnectAgentAsync())
{
// Bodies are byte-exact CBOR protocol payloads; decoding is caller-owned.
var readyBody = agent.GetReadyBytes();
}
// Detach consumes the native handle while leaving this detached VM running.
await sandbox.DetachAsync();
var handle = await client.LookupAsync("dotnet-demo");
await using var connected = await handle.ConnectAsync();
await connected.KillAsync();pack-release creates AICD.Microsandbox.<version>.nupkg; the assembly is
also named AICD.Microsandbox, while the source namespace remains
Microsandbox.
Tag releases publish through the publish job in
.github/workflows/release.yml. The job uses GitHub OIDC to obtain a temporary
NuGet.org API key, so no long-lived API key is stored in GitHub. The matching
trusted publishing policy on NuGet.org must use repository owner
aicd-dev, repository microsandbox-dotnet, workflow release.yml, and GitHub
environment nuget. Set the NUGET_USER variable on that environment to the
NuGet.org username that owns the policy.
The release workflow's read-only package job is the only job that downloads or
executes native code. It transfers the verified NuGet package as an Actions artifact
to the nuget environment's OIDC-enabled publish job, which only publishes it.
.github/workflows/upstream-check.yml runs weekly and may be dispatched manually.
It detects the latest stable superradcompany/microsandbox release and, when
newer, validates its assets, pushes a package-version update branch, and opens an
issue with a one-click pull-request link. It never merges upstream history, commits
binaries, merges the PR, tags, or publishes. Its read-only job executes the
candidate ABI and emits only an updated project file and digest allowlist; a
separate write-enabled job pushes the reviewable branch.
NuGet package versions are immutable. Increment Version before publishing a
replacement, and stage native assets from the matching microsandbox release.
DisposeAsync closes any handle that has not been detached. For a
lifecycle-owning sandbox, native close stops the VM; call DetachAsync only
when the sandbox was created or started in detached mode and should continue
running.
- SSH exec and SFTP operations are collected because the C ABI exposes collected results.
AttachAsync,AttachShellAsync, SSH client attach, andServeStdioAsyncbridge the current process terminal and block until completion.ServeConnectionAsyncandServeConsoleConnectionAsyncare compatibility aliases for the same native stdio behavior. - Raw agent frame bodies remain CBOR bytes; the package does not select or bundle a CBOR object model.
- Agent streams depend on their parent client remaining open. Dispose streams before disposing the client.
SandboxHandlesupports request stop/kill/drain, collected and streaming logs, collected metrics, and named snapshot creation where the ABI has name-addressed exports; stop-and-wait, synchronous drain, persisted-state removal, attach, and metrics streaming require a live sandbox handle.- Direct host-side volume filesystem access remains excluded because lexical rooting alone does not prevent symlink traversal outside a volume root.