astrid-workspace 0.5.0

Operational workspace boundaries for Astrid secure agent runtime
Documentation

astrid-workspace

License: MIT OR Apache-2.0 MSRV: 1.94

Host-level process containment. The second sandbox.

Astrid has two sandboxes. The WASM sandbox is inescapable by design: capsule code runs in WebAssembly with zero ambient authority. But the agent also spawns native processes (npm, python, shell commands) that run outside WASM. This crate wraps those processes in OS-native containment so they cannot write outside the workspace or read sensitive host paths.

On Linux: bwrap (bubblewrap). On macOS: sandbox-exec with a generated Seatbelt profile. On other platforms: passthrough with a warning.

The WASM sandbox protects against malicious capsule code. This sandbox protects against malicious tool output that reaches native execution. They are complementary. The workspace sandbox is escapable with user approval. The WASM sandbox is not.

How it works

SandboxCommand takes a std::process::Command and returns a new command prepended with the platform's sandbox wrapper. On Linux, it prepends bwrap with --ro-bind / / for read access, --bind for the writable root, --tmpfs /tmp, --unshare-all, --share-net, and --die-with-parent. On macOS, it generates an SBPL profile inline (passed via -p, not a temp file) that denies by default and allows reads/writes only to the workspace and /tmp.

ProcessSandboxConfig is a data-oriented builder for when you need a tokio::process::Command or finer control. It produces a SandboxPrefix (program + args) that you prepend to any command type.

Injection-safe profile generation

Every path interpolated into a Seatbelt profile is validated first. The crate rejects:

  • Relative paths (must be absolute)
  • Non-UTF-8 paths (lossy coercion would misalign the SBPL rule with the real path)
  • Double-quote (") in paths (SBPL string delimiter, allows sandbox escape)
  • Backslash (\) in paths (SBPL escape character, silently reinterprets the path)
  • Null bytes (defense in depth)

Validation runs on all platforms, not just macOS, so the API contract is consistent everywhere. The tests include an actual SBPL injection payload to verify rejection.

Builder API

use astrid_workspace::ProcessSandboxConfig;

let config = ProcessSandboxConfig::new("/home/user/project")
    .with_network(true)
    .with_extra_read("/usr/local/share/myapp")
    .with_extra_write("/data/output")
    .with_hidden("/home/user/.astrid");

if let Some(prefix) = config.sandbox_prefix()? {
    let mut cmd = tokio::process::Command::new(&prefix.program);
    cmd.args(&prefix.args);
    cmd.arg("npx").arg("@anthropic/mcp-server-filesystem");
}

Hidden paths are overlaid with empty tmpfs on Linux and excluded from the Seatbelt allowlist on macOS.

Internal modules

Several modules are pub(crate) and consumed by higher-level Astrid crates, not directly by users:

  • Boundaries: pre-compiled glob matching, canonical path resolution, and symlink evaluation for directory traversal prevention. Three modes: Safe (always ask), Guided (smart defaults), Autonomous (unrestricted).
  • Escape/airlock: structured EscapeRequest/EscapeDecision flows with session-scoped and permanent approval memory. Remembered paths are canonicalized on store and validated on restore (relative and non-existent paths rejected).
  • Profiles: predefined workspace configurations for safe, power_user, autonomous (aliased as yolo), and ci environments.
  • Worktrees: ActiveWorktree creates a per-session Git branch and worktree. Auto-commits WIP on drop, then removes the physical worktree to reclaim disk space.

Development

cargo test -p astrid-workspace

License

Dual MIT/Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.