workspace_sandbox 0.1.3
workspace_sandbox: ^0.1.3 copied to clipboard
Cross-platform sandboxed workspace manager for running shell commands with streaming output and native isolation.
workspace_sandbox #
A cross-platform workspace abstraction for running shell commands in isolated directories with native sandboxing, network control, and real-time process streaming.
Designed for AI agents, automation tools, and build systems that need to execute arbitrary commands with strong isolation guarantees and file system observability.
Features #
- Isolated workspaces – Ephemeral or persistent directory roots with automatic cleanup
- Native sandboxing – Job Objects (Windows), Bubblewrap (Linux), Seatbelt (macOS)
- Network control – Block or allow network access per workspace
- Streaming output – Real-time stdout/stderr via Dart streams
- Reactive events – Monitor process lifecycle and output via
onEventstream - Timeout & cancellation – Automatic process termination with configurable timeouts
- File system helpers – Tree visualization, recursive grep, glob search, binary I/O
- Simple API – Write commands as you would in a terminal, no complex process management
Installation #
Add to your pubspec.yaml:
dependencies:
workspace_sandbox: ^0.1.3
Then run:
dart pub get
Quick Start #
Basic Usage #
import 'package:workspace_sandbox/workspace_sandbox.dart';
void main() async {
final ws = Workspace.ephemeral();
await ws.writeFile('script.sh', 'echo "Hello World"');
final result = await ws.run('sh script.sh');
print(result.stdout); // Hello World
await ws.dispose();
}
With Network Isolation #
final ws = Workspace.ephemeral(
options: const WorkspaceOptions(
sandbox: true,
allowNetwork: false, // Block all network access
),
);
// This will fail with network unreachable
await ws.run('ping google.com');
Streaming Long-Running Processes #
final ws = Workspace.ephemeral();
final process = await ws.start('npm install');
process.stdout.listen((line) => print('[NPM] $line'));
process.stderr.listen((line) => print('[ERR] $line'));
final exitCode = await process.exitCode;
print('Installation complete: $exitCode');
await ws.dispose();
Reactive Event Monitoring #
final ws = Workspace.ephemeral();
// Listen to all workspace events
ws.onEvent.listen((event) {
if (event is ProcessLifecycleEvent) {
print('Process ${event.pid}: ${event.state}');
} else if (event is ProcessOutputEvent) {
print('[${event.isError ? "ERR" : "OUT"}] ${event.content}');
}
});
await ws.run('echo "Hello"');
await ws.dispose();
API Reference #
Workspace #
Primary interface for creating and managing isolated execution environments.
Constructors
Workspace.ephemeral({ String? id, WorkspaceOptions? options })
Creates an ephemeral workspace in the system temp directory with sandboxing enabled by default. Automatically deleted on dispose().
Workspace.at(String path, { String? id, WorkspaceOptions? options })
Uses an existing directory as the workspace root. Files persist after dispose(). Sandboxing is optional.
Core Methods
Future<CommandResult> run(String commandLine, { WorkspaceOptions? options })
Execute a command to completion. Returns buffered stdout/stderr and exit code.
final result = await ws.run('python script.py');
if (result.isSuccess) {
print(result.stdout);
}
Future<WorkspaceProcess> start(String commandLine, { WorkspaceOptions? options })
Start a long-running process with streaming output.
final proc = await ws.start('tail -f log.txt');
proc.stdout.listen(print);
Future<CommandResult> exec(String executable, List<String> args, { WorkspaceOptions? options })
Execute a binary directly with explicit arguments (bypasses shell).
Future<WorkspaceProcess> spawn(String executable, List<String> args, { WorkspaceOptions? options })
Spawn a binary as a background process with explicit arguments.
File Operations
All paths are relative to workspace root.
Future<File> writeFile(String path, String content)Future<String> readFile(String path)Future<File> writeBytes(String path, List<int> bytes)Future<List<int>> readBytes(String path)Future<bool> exists(String path)Future<Directory> createDir(String path)Future<void> delete(String path)Future<void> copy(String source, String dest)Future<void> move(String source, String dest)
Observability Helpers
Future<String> tree({ int maxDepth = 5 })
Generate a visual directory tree.
final tree = await ws.tree();
print(tree);
// workspace_root
// ├── src
// │ └── main.dart
// └── README.md
Future<String> grep(String pattern, { bool recursive = true })
Search for text patterns in files.
final results = await ws.grep('TODO');
// src/utils.dart:42: // TODO: implement
Future<List<String>> find(String pattern)
Find files matching a glob pattern.
final dartFiles = await ws.find('*.dart');
// ['src/main.dart', 'lib/utils.dart']
Event Stream
Stream<WorkspaceEvent> onEvent
Unified stream of all events happening in the workspace.
ws.onEvent.listen((event) {
if (event is ProcessLifecycleEvent) {
print('${event.command} -> ${event.state}');
} else if (event is ProcessOutputEvent) {
print('[${event.pid}] ${event.content}');
}
});
WorkspaceOptions #
Configuration for command execution behavior.
const WorkspaceOptions({
Duration? timeout,
Map<String, String>? env,
bool includeParentEnv = true,
String? workingDirectoryOverride,
bool sandbox = false,
bool allowNetwork = true,
})
Fields:
timeout– Kill process after durationenv– Additional environment variablesincludeParentEnv– Inherit parent process environmentworkingDirectoryOverride– Custom working directorysandbox– Enable native OS sandboxingallowNetwork– Allow network access (requiressandbox: truefor enforcement)
Example:
final result = await ws.run(
'python train.py',
options: const WorkspaceOptions(
timeout: Duration(hours: 2),
env: {'CUDA_VISIBLE_DEVICES': '0'},
sandbox: true,
allowNetwork: false,
),
);
CommandResult #
Result of a completed command.
Fields:
int exitCodeString stdoutString stderrDuration durationbool isCancelled
Getters:
bool isSuccess– ReturnsexitCode == 0bool isFailure– ReturnsexitCode != 0
WorkspaceProcess #
Handle to a running process.
Streams:
Stream<String> stdoutStream<String> stderr
Future:
Future<int> exitCode
Methods:
void kill()– Terminate process immediately
Events #
WorkspaceEvent (sealed base class)
DateTime timestampString workspaceId
ProcessLifecycleEvent extends WorkspaceEvent
int pidString commandProcessState state(started, stopped, failed)int? exitCode
ProcessOutputEvent extends WorkspaceEvent
int pidString commandString contentbool isError(true = stderr, false = stdout)
Security & Sandboxing #
Isolation Mechanisms #
Windows (x64)
Processes run in a Job Object with:
- Process group isolation
- Automatic child process termination
- Network blocking via environment proxy settings
Linux (x64)
Processes run under Bubblewrap with:
- Root passthrough strategy (host mounted read-only at
/) - Workspace mounted read-write
- Tool caches exposed (
.m2,.gradle,.cargo,.pub-cache) - Network namespace isolation (
--unshare-netwhenallowNetwork: false)
macOS (x64 / ARM64)
Processes run under Seatbelt (sandbox-exec) with:
- Read-only host filesystem
- Write access to workspace and temp directories
- Tool cache allowlisting
- Network policy enforcement
Network Control #
Set allowNetwork: false to block all network access:
final ws = Workspace.ephemeral(
options: const WorkspaceOptions(
sandbox: true,
allowNetwork: false,
),
);
// Blocked at OS level
await ws.run('curl https://example.com'); // Fails
Best Practices #
- Always enable sandboxing for untrusted code
- Use
allowNetwork: falseunless network is explicitly required - Set aggressive timeouts for AI-generated commands
- Validate command strings before execution
- Use
Workspace.ephemeral()for ephemeral, isolated environments
Platform Support #
| Platform | Architecture | Sandboxing | Network Isolation |
|---|---|---|---|
| Windows | x64 | Job Objects | ENV proxy blocking |
| Linux | x64 | Bubblewrap (bwrap) | Kernel-level (--unshare-net) |
| macOS | x64 / ARM64 | Seatbelt (sandbox-exec) | Process-level blocking |
Requirements #
Linux #
Bubblewrap must be installed for sandboxing:
# Ubuntu/Debian
sudo apt install bubblewrap
# Fedora/RHEL
sudo dnf install bubblewrap
# Arch
sudo pacman -S bubblewrap
Windows #
No additional dependencies. Job Objects are built into Windows.
macOS #
No additional dependencies. Seatbelt (sandbox-exec) is built into macOS.
Examples #
See the example/ directory for comprehensive usage demonstrations:
example.dart– Quick-start basic usage01_advanced_python_api.dart– HTTP server with streaming logs02_security_audit.dart– Network isolation validation03_git_workflow_at.dart– Persistent workspace git operations04_data_processing_spawn.dart– Long-running background processes05_interactive_repl.dart– Real-time stdin/stdout interaction
Run any example:
dart run example/example.dart
Building Native Binaries #
The native launcher is written in Rust. Rebuild for all platforms:
Prerequisites #
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Linux #
cd native
cargo build --release
cp target/release/workspace_launcher ../bin/linux/x64/
Windows #
cd native
cargo build --release
Copy-Item target\release\workspace_launcher.exe ..\bin\windows\x64\
macOS #
cd native
cargo build --release
cp target/release/workspace_launcher ../bin/macos/x64/
Cross-Compilation #
# For Windows from Linux
rustup target add x86_64-pc-windows-gnu
cargo build --release --target x86_64-pc-windows-gnu
# For macOS from Linux (requires osxcross)
rustup target add x86_64-apple-darwin
cargo build --release --target x86_64-apple-darwin
Contributing #
Contributions are welcome. Before submitting:
dart format lib test example
dart analyze
dart test
Ensure native library builds successfully on target platforms.
Issues & PRs: GitHub Repository
License #
Apache-2.0 License. See LICENSE for details.
Acknowledgments #
- Built with Rust for cross-platform native execution
- Sandboxing: Windows Job Objects, Linux Bubblewrap, macOS Seatbelt
- Designed for AI agent safety and build automation
Links #
- Documentation: pub.flutter-io.cn/packages/workspace_sandbox
- Issues: GitHub Issues
- Organization: DeskHand Software