SandD
Sandbox Daemon for Agent Command Execution
Rust-powered WebSocket server with Python API for remote command execution and interactive sessions.
Features
- Command Execution - Run shell commands on remote machines with timeout control
- Interactive Sessions - Full PTY sessions with bash for manual work
- Snapshots - Save, tag and restore workspace state on a worker
- Labels & Broadcast - Filter workers by label and run a command on many at once
- Tunnel Mode (VPN) - Secure mesh networking with WireGuard encryption via Tailscale
- High Performance - Rust async runtime handles high-concurrency workloads
- Auto Reconnection - Workers reconnect automatically on network failures
- Cross-Platform - Linux and macOS release binaries; Windows builds from source
Architecture
┌──────────────────────────────────────────┐
│ Python Agent Application │
│ ┌────────────────────────────────────┐ │
│ │ from sandd import Server │ │
│ │ │ │
│ │ server = Server("0.0.0.0", 8765) │ │
│ │ result = server.exec( │ │
│ │ "daemon-1", "ls -la" │ │
│ │ ) │ │
│ └────────────────────────────────────┘ │
│ ▲ │
│ │ Python bindings (PyO3) │
│ ▼ │
│ ┌────────────────────────────────────┐ │
│ │ Rust WebSocket Server (tokio) │ │
│ │ • Command routing │ │
│ │ • Session management │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
▲
│ WebSocket
│ (ws:// in direct mode, encrypted via VPN in tunnel mode)
│
┌─────────┼─────────┐
│ │ │
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│Daemon │ │Daemon │ │Daemon │
│ #1 │ │ #2 │ │ #n │
└───────┘ └───────┘ └───────┘
Quick Start
Run a server and a daemon on one machine and execute a command. You need Rust (https://rustup.rs) and Python 3.8+.
1. Build both from source:
git clone https://github.com/InftyAI/SandD
cd SandD
pip install . # server: the `sandd` Python package
make daemon-release # daemon: ./target/release/sandd
2. Terminal 1, start the server:
from sandd import Server
server = Server() # listens on 0.0.0.0:8765
print("waiting for worker-1...")
server.wait_for_daemon("worker-1", timeout=120)
result = server.exec("worker-1", "uname -a")
print(result.stdout)
3. Terminal 2, start a daemon:
./target/release/sandd --server-url ws://127.0.0.1:8765/ws --daemon-id worker-1
Terminal 1 prints the daemon's uname -a. On real machines, run the daemon on each
worker with ws://<server-ip>:8765/ws; workers dial out, so only the server's
port needs to be reachable.
Installation
The server is a Python package (from sandd import Server); the daemon is a
separate binary, also named sandd, that runs on each worker.
Server (Python package)
From PyPI:
pip install sandd
From source (needs Rust):
git clone https://github.com/InftyAI/SandD
cd SandD
pip install .
Daemon (worker binary)
Quick Install
# Direct mode (no tunnel)
curl -fsSL https://raw.githubusercontent.com/InftyAI/SandD/main/hack/scripts/install.sh | sudo bash
# Tunnel mode (with Tailscale)
curl -fsSL https://raw.githubusercontent.com/InftyAI/SandD/main/hack/scripts/install.sh | sudo bash -s -- --tunnel
This installs the latest release to /usr/local/bin/sandd. To pin a specific version:
curl -fsSL https://raw.githubusercontent.com/InftyAI/SandD/main/hack/scripts/install.sh | sudo bash -s -- --version v0.2.1
Alternative Methods
Install from crates.io:
cargo install sandd
Build from source:
git clone https://github.com/InftyAI/SandD
cd SandD
make daemon-release # binary at ./target/release/sandd
# or put it on your PATH (~/.cargo/bin/sandd):
cargo install --path sandd
Build from the main branch:
cargo install --git https://github.com/InftyAI/SandD sandd
Usage
Direct Mode (Development)
from sandd import Server
server = Server() # Direct mode (default)
server.wait_for_daemon("worker-1", timeout=30)
result = server.exec("worker-1", "hostname")
print(result.stdout)
sandd --server-url ws://server-ip:8765/ws --daemon-id worker-1
Tunnel Mode (Production)
For secure multi-cloud deployments with mesh VPN (no TLS setup needed):
from sandd import Server, TunnelConfig
config = TunnelConfig(
authkey="YOUR_KEY",
server="http://headscale:8080",
)
server = Server(connect="tunnel", tunnel_config=config)
# ✓ Encrypted with WireGuard (no TLS needed)
# ✓ Works across NAT/firewalls
# ✓ No public IPs required
sandd --server-url ws://10.200.0.1:8765/ws \
--daemon-id worker-1 \
--tunnel \
--tunnel-authkey YOUR_KEY \
--tunnel-server http://headscale:8080
In a cluster, don't bake a key into your image — mint one per start from a key
broker ($SANDD_KEYBROKER_URL) and take the headscale URL from
$SANDD_TUNNEL_SERVER:
from sandd import Server, tunnel_config_from_env
server = Server(connect="tunnel", tunnel_config=tunnel_config_from_env())
See Tunnel Mode Guide for setup instructions.
Security
SandD does not authenticate daemons yet. The server accepts any client that
speaks the sandd.v1 protocol, and a daemon can register under any id, so anyone who
can reach the server's port can join and receive commands meant for that id.
- Direct mode: run the server only on a trusted network.
- Tunnel mode: only nodes admitted to your Headscale mesh can reach the
server, and traffic is encrypted with WireGuard. The
authkeyis a Headscale key for joining the mesh, not a SandD credential.
Daemon authentication and authorization with Keycloak is planned (see Architecture).
Documentation
- Quick Start Guide
- Architecture Details
- Protocol Specification
- Tunnel Mode Guide
- Development Guide
- Examples
Roadmap
- Authentication & Authorization - Keycloak-issued tokens for daemons (see Security)
- File Transfer - Upload and download files between server and workers
- Audit Logging - Track all commands, sessions, and file transfers
- Metrics - Prometheus-compatible metrics for monitoring
- Resource Limits - CPU/memory/timeout controls per daemon
- Multi-tenancy - Isolated workspaces with access control
- Rate Limiting - Prevent abuse and resource exhaustion
- Command Allowlist - Restrict allowed commands per daemon
Contributing
We welcome any kind of contributions, feedback, and suggestions! See DEVELOP.md for development setup and guidelines.
License
Apache-2.0 — see LICENSE. Third-party components (e.g. Tailscale, used by tunnel mode) are listed in THIRD_PARTY_NOTICES.md.
Metadata
Release files for sandd 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sandd-0.2.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ x86-64 | Details for sandd-0.2.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
| sandd-0.2.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ ARM64 | Details for sandd-0.2.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
| sandd-0.2.1-cp38-abi3-macosx_11_0_arm64.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64 | Details for sandd-0.2.1-cp38-abi3-macosx_11_0_arm64.whl |
Total release size: 5.1 MB
Release files / sandd-0.2.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | sandd-0.2.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 1.7 MB |
| Tags | CPython 3.8 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
7effe8844e43d72e74aaf20b0e3a3c4b724304a88d406da909803d9f0af7db19
|
|
BLAKE2b-256 checksum How to use checksums |
f312f702007de21d7dab2d059ff7378584f96f87440f76e5bdee9c19fa177d6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.13.3
|
Release files / sandd-0.2.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | sandd-0.2.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 1.8 MB |
| Tags | CPython 3.8 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
bbd70738cb603bc245b03aaa1c902da7842137070b521161a52ee8e1351c30a2
|
|
BLAKE2b-256 checksum How to use checksums |
fa98a9d30d3d9986f56da5ee3ce5c305f52fe6df52d8f35f813ce07f2251665c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.13.3
|
Release files / sandd-0.2.1-cp38-abi3-macosx_11_0_arm64.whl
| Download URL | sandd-0.2.1-cp38-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.8 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
dd79f004a0bfa850ff9fb68136dcec4ca3cad85b803dbea7340079d06f7e325b
|
|
BLAKE2b-256 checksum How to use checksums |
092d99ca27e62792525cb6127f5f98c57afed087a25df859ce3ce544bfa5ab9b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.13.3
|
Security for sandd 0.2.1
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.