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.0

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.0

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.0
File Interpreter ABI Platform Actions
sandd-0.2.0-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details for sandd-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
sandd-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 CPython 3.9 Linux glibc 2.17+ x86-64 Details for sandd-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
sandd-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 CPython 3.9 Linux glibc 2.17+ ARM64 Details for sandd-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Total release size: 5.1 MB

Release files / sandd-0.2.0-cp312-cp312-macosx_11_0_arm64.whl

Download URL sandd-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
Size 1.6 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
149ef7d8c6a1008266a702ec733d32d497659ea87f495c8adfba5a3195e99095
BLAKE2b-256 checksum
How to use checksums
822b1e5ff7538a9e47166b9ae6a05566326688b9c0cfefd5ea5c48a8ecea293d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.13.3

Release files / sandd-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL sandd-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.8 MB
Tags CPython 3.9 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
eb460eb9e3ca7a6d20fbcb15664bed9dbebeb057b62d41cdef99740a54bfeafb
BLAKE2b-256 checksum
How to use checksums
11053ddb57fc9cb695456ca1736ec77e00cfb86fc412292b5ed8ea76b428454e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.13.3

Release files / sandd-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL sandd-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.8 MB
Tags CPython 3.9 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
388e40655e1fd22183b611c7c4200026bfe723884f0456def4ba5a4061556aa1
BLAKE2b-256 checksum
How to use checksums
5732727923bfac1c6d43a194ca01864e1d0fa5cd9d032c2626855253876771d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via maturin/1.13.3

Security for sandd 0.2.0

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