Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/rust-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,5 @@ jobs:
with:
toolchain: stable

- name: Run Unit tests
- name: Run tests
run: make test
Comment thread
kerthcet marked this conversation as resolved.

- name: Run E2E tests
run: make test-e2e
7 changes: 2 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,12 @@ test-e2e: $(PYTEST) dev
@echo "Cleaning up containers..."
docker compose -f docker-compose.e2e.yml down

docker-build:
docker compose -f docker-compose.e2e.yml build
docker-up:
docker compose -f docker-compose.e2e.yml up -d

docker-down:
docker compose -f docker-compose.e2e.yml down

test-all: test test-e2e
@echo "All tests completed successfully"

.PHONY: lint
lint: $(RUFF)
$(RUFF) check .
Expand Down
46 changes: 23 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# SandD

**A Lightweight Sandbox Daemon for Secure Agent Execution in Isolated Environments.**
**A Lightweight Sandbox Daemon that Provides Secure, Isolated Execution Environments for Agents.**

[![Rust](https://img.shields.io/badge/rust-1.70+-orange.svg)](https://www.rust-lang.org/)
[![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/)
Expand All @@ -18,8 +18,8 @@ Rust-powered WebSocket server with Python API for secure command execution in is

## Features

- ✅ **Command Execution**: Execute shell commands remotely with timeout support
- ✅ **Interactive Shell (PTY)**: Full terminal sessions for debugging and manual work
- ✅ **Command Execution**: Execute commands remotely with timeout support
- ✅ **Interactive Sessions (PTY)**: Full terminal sessions for debugging and manual work
- ✅ **File Transfer**: Upload/download files between agent and daemons
- ✅ **High Performance**: Rust-powered WebSocket server handles 200+ concurrent connections
- ✅ **Auto Reconnection**: Daemons automatically reconnect if connection drops
Expand All @@ -29,25 +29,25 @@ Rust-powered WebSocket server with Python API for secure command execution in is
## Architecture

```
┌─────────────────────────────────────────┐
│ Python Agent Application │
│ ┌────────────────────────────────────┐ │
│ │ from sandd import Server │ │
│ │ │ │
│ │ server = Server("0.0.0.0", 8765) │ │
│ │ result = server.execute_command( │ │
│ │ "daemon-1", "ls -la" │ │
│ │ ) │ │
│ └────────────────────────────────────┘ │
│ ▲ │
│ │ Python bindings (PyO3) │
│ ▼ │
│ ┌────────────────────────────────────┐ │
│ │ Rust WebSocket Server (tokio) │ │
│ │ • Command routing │ │
│ │ • Session management │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────
│ Python Agent Application
│ ┌────────────────────────────────────┐
│ │ from sandd import Server │
│ │ │
│ │ server = Server("0.0.0.0", 8765) │
│ │ result = server.exec( │
│ │ "daemon-1", "ls -la" │
│ │ ) │
Comment thread
kerthcet marked this conversation as resolved.
│ └────────────────────────────────────┘
│ ▲
│ │ Python bindings (PyO3)
│ ▼
│ ┌────────────────────────────────────┐
│ │ Rust WebSocket Server (tokio) │
│ │ • Command routing │
│ │ • Session management │
│ └────────────────────────────────────┘
└─────────────────────────────────────────
│ WebSocket (WSS)
│ (Daemon initiates connection)
Expand Down Expand Up @@ -90,7 +90,7 @@ print(f"Server listening on {server.address}")
server.wait_for_daemon("worker-1", timeout=30)

# Execute command
result = server.execute_command("worker-1", "hostname")
result = server.exec("worker-1", "hostname")
print(f"Output: {result.stdout}")
```

Expand Down
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ At 200+ connections, Python asyncio:
### Why WebSocket?

- Persistent bidirectional connection
- Efficient for streaming (shell output)
- Efficient for streaming (session output)
- Well-supported libraries
- Can multiplex multiple sessions over one connection

Expand Down
16 changes: 6 additions & 10 deletions docs/DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ SandD/
│ ├── src/
│ │ ├── main.rs # Daemon entry point
│ │ ├── executor.rs # Command execution
│ │ ├── shell.rs # Shell (not implemented)
│ │ ├── session.rs # Interactive sessions (PTY)
│ │ └── protocol.rs # Message protocol
│ └── Cargo.toml
Expand Down Expand Up @@ -124,7 +124,7 @@ command_tx: mpsc::UnboundedSender<Message> // Stored in registry
**Incoming (Daemon → Python):**
```rust
pending_commands: oneshot::Sender<Result> // Request/Response
shell_sessions: mpsc::Sender<Vec<u8>> // Streaming
sessions: mpsc::Sender<Vec<u8>> // Streaming
file_transfers: Vec<Vec<u8>> // Chunked buffering
```

Expand Down Expand Up @@ -211,11 +211,7 @@ RUST_LOG=server=debug python3 examples/simple_test.py

### Not Implemented

1. **Interactive Shell**: Infrastructure exists, daemon returns "not implemented"
- Reason: `PtySystem` Sync issues
- Fix: Refactor shell manager to avoid Sync constraints

2. **File Transfer**: Protocol defined, daemon just logs
1. **File Transfer**: Protocol defined, daemon just logs
- Reason: Deferred for MVP
- Fix: Implement actual file I/O in daemon

Expand Down Expand Up @@ -278,14 +274,14 @@ Include motivation and context.
- Check daemon logs: `RUST_LOG=info ./target/release/sandd ...`

**Commands timing out:**
- Increase `timeout` parameter in `execute_command()` (in seconds)
- Increase `timeout` parameter in `exec()` (in seconds)
- Check daemon system resources: `top`, `free -h`
- Verify command actually completes when run manually
- Check daemon logs for errors

**High memory usage:**
- Monitor active shell sessions (they hold state)
- Close unused shell sessions
- Monitor active sessions (they hold state)
- Close unused sessions with `session.close()`
- Check number of connected daemons: `server.daemon_count()`

### Development Issues
Expand Down
81 changes: 46 additions & 35 deletions docs/PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ All messages are JSON with a `type` field indicating the message type:

```json
{
"type": "execute_command",
"command_id": "uuid-here",
"type": "exec",
Comment thread
kerthcet marked this conversation as resolved.
"request_id": "uuid-here",
"command": "ls -la",
"timeout_secs": 300,
"env": {},
Expand Down Expand Up @@ -130,8 +130,8 @@ All messages are JSON with a `type` field indicating the message type:

```json
{
"type": "execute_command",
"command_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "exec",
Comment thread
kerthcet marked this conversation as resolved.
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"command": "python script.py",
"timeout_secs": 300,
"env": {
Expand All @@ -142,7 +142,7 @@ All messages are JSON with a `type` field indicating the message type:
```

**Fields**:
- `command_id`: Unique identifier for tracking this command
- `request_id`: Unique identifier for tracking this request
- `command`: Shell command to execute
- `timeout_secs`: Maximum execution time (default: 300)
- `env`: Environment variables (optional)
Expand All @@ -155,7 +155,7 @@ All messages are JSON with a `type` field indicating the message type:
```json
{
"type": "command_output",
"command_id": "550e8400-e29b-41d4-a716-446655440000",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"stdout": "output text...",
"stderr": "",
"exit_code": 0,
Expand All @@ -170,88 +170,99 @@ All messages are JSON with a `type` field indicating the message type:
```json
{
"type": "command_error",
"command_id": "550e8400-e29b-41d4-a716-446655440000",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"error": "command not found"
}
```

### Interactive Shell (PTY)
### Interactive Session (PTY)

#### StartShell
#### StartSession
**Direction**: Agent → Daemon
**Purpose**: Start an interactive shell session
**Purpose**: Start an interactive session

```json
{
"type": "start_shell",
"type": "start_session",
"session_id": "550e8400-e29b-41d4-a716-446655440001",
"rows": 24,
"cols": 80,
"term": "xterm-256color"
}
```

#### ShellStarted
#### SessionStarted
**Direction**: Daemon → Agent
**Purpose**: Acknowledge shell started
**Purpose**: Acknowledge session started

```json
{
"type": "shell_started",
"type": "session_started",
"session_id": "550e8400-e29b-41d4-a716-446655440001",
"success": true,
"error": null
}
```

#### ShellInput
#### SessionInput
**Direction**: Agent → Daemon
**Purpose**: Send user input to shell
**Purpose**: Send user input to session

```json
{
"type": "shell_input",
"type": "session_input",
"session_id": "550e8400-e29b-41d4-a716-446655440001",
"data": "bHMgLWxhCg=="
}
```

**Note**: `data` is base64-encoded bytes

#### ShellOutput
#### SessionOutput
**Direction**: Daemon → Agent
**Purpose**: Stream shell output back to agent
**Purpose**: Stream session output back to agent

```json
{
"type": "shell_output",
"type": "session_output",
"session_id": "550e8400-e29b-41d4-a716-446655440001",
"data": "ZmlsZTEgIGZpbGUyICBmaWxlMwo="
}
```

**Note**: `data` is base64-encoded bytes

#### ShellResize
#### SessionResize
**Direction**: Agent → Daemon
**Purpose**: Resize terminal window

```json
{
"type": "shell_resize",
"type": "session_resize",
"session_id": "550e8400-e29b-41d4-a716-446655440001",
"rows": 50,
"cols": 120
}
```

#### ShellExit
#### SessionClose
**Direction**: Agent → Daemon
**Purpose**: Close session

```json
{
"type": "session_close",
"session_id": "550e8400-e29b-41d4-a716-446655440001"
}
```

#### SessionExit
**Direction**: Daemon → Agent
**Purpose**: Shell session terminated
**Purpose**: Session terminated

```json
{
"type": "shell_exit",
"type": "session_exit",
"session_id": "550e8400-e29b-41d4-a716-446655440001",
"exit_code": 0
}
Expand Down Expand Up @@ -367,25 +378,25 @@ All messages are JSON with a `type` field indicating the message type:

### Request/Response (Command Execution)

1. Agent generates unique `command_id`
2. Agent registers oneshot channel for this command
1. Agent generates unique `request_id`
2. Agent registers oneshot channel for this request
3. Agent sends `ExecuteCommand` message
4. Daemon executes and sends back `CommandOutput`
5. Agent resolves channel, Python receives result

**Concurrency**: Multiple commands can execute in parallel

### Streaming (Shell Sessions)
### Streaming (Interactive Sessions)

1. Agent generates unique `session_id`
2. Agent registers mpsc channel for this session
3. Agent sends `StartShell` message
3. Agent sends `StartSession` message
4. Daemon starts PTY and begins streaming output
5. Agent sends `ShellInput` as user types
6. Daemon sends `ShellOutput` continuously
7. Session ends with `ShellExit`
5. Agent sends `SessionInput` as user types
6. Daemon sends `SessionOutput` continuously
7. Session ends with `SessionExit`

**Concurrency**: Multiple shell sessions per daemon supported
**Concurrency**: Multiple sessions per daemon supported

### Chunked Transfer (File Download)

Expand Down Expand Up @@ -413,7 +424,7 @@ All messages are JSON with a `type` field indicating the message type:
- Agent detects closed connection
- Registry removes daemon
- All pending commands fail
- Shell sessions terminate
- Terminate
```
Comment thread
kerthcet marked this conversation as resolved.

## Heartbeat & Connection Monitoring
Expand All @@ -434,4 +445,4 @@ All messages are JSON with a `type` field indicating the message type:

See `server/src/protocol.rs` for the complete Rust implementation using serde for JSON serialization.

Binary data (shell I/O, file chunks) is base64-encoded for JSON compatibility.
Binary data (session I/O, file chunks) is base64-encoded for JSON compatibility.
Loading
Loading