Skip to main content

SandD

Sandbox Daemon for Agent Command Execution

Rust Python License: Apache 2.0

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 authkey is a Headscale key for joining the mesh, not a SandD credential.

Daemon authentication and authorization with Keycloak is planned (see Architecture).

Documentation

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)

Table of built distributions (wheels) for sandd 0.2.1
File Interpreter ABI Platform Actions
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.

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