Skip to content

Security: dv-hart/boxBot

docs/security.md

Security Model

Principles

  1. Minimal attack surface — no web UI, no unnecessary open ports, SSH key-only (password auth disabled)
  2. Explicit trust — every communication channel requires authentication
  3. Physical presence — high-privilege actions (enrollment, admin) require being physically at the box
  4. Local processing — perception runs on-device; no images or audio leave the box except explicit API calls
  5. No information leakage — unauthenticated requests receive no response

Communication Channels

Channel Auth Method Trust Basis
Voice Physical presence You're in the room
Buttons Physical presence You're at the box
WhatsApp Phone number whitelist Explicit enrollment

User Registration

Users are people with authenticated remote channels (WhatsApp by default). Registration uses single-use, time-limited codes. See user-registration.md for the complete flow.

First Admin (Bootstrap)

  • Code displayed on the 7" screen during initial setup (physical presence)
  • User texts code to BB's WhatsApp → registered as primary admin
  • Bootstrap permanently disabled after first admin registers
  • This is the ONLY registration that doesn't require an existing admin

New User Registration

  • Admin requests a code → BB sends code to admin via WhatsApp
  • Admin shares code with new user out-of-band (in person, text, etc.)
  • New user texts code to BB → registered as standard user
  • BB never initiates contact with unknown numbers — admin gatekeeps

Registration Codes

  • 6 digits, cryptographically random, single-use, expires in 10 minutes
  • One active code at a time, rate-limited (3 per hour per admin)
  • Failed attempts: silent drop, rate limiting, temp/permanent blocking

Permission Levels

  • admin — register/remove users, approve packages, promote admins
  • standard — send/receive messages, interact with skills

WhatsApp Security

Hard Block on Unknown Numbers

  • Messages from unregistered numbers → silent drop (no response)
  • No error message, no acknowledgment, no information leakage
  • Only exception: message contains a valid, unexpired registration code
  • Failed code attempts also produce no response

Privileged Channel Separation

BB's WhatsApp is for user ↔ BB communication only. It is not a proxy for other services:

  • Email checking → skill (sandbox, IMAP/Gmail API, own credentials)
  • Calendar sync → skill (sandbox, CalDAV, own credentials)
  • RSS/news → skill (sandbox, own packages)
  • Any inbox-style service → skill, never through the messaging path

BB may relay results via WhatsApp ("you have 3 unread emails"), but the data fetching runs in the sandbox. This prevents privilege confusion and keeps the authenticated channel clean.

Webhook Security

  • WHATSAPP_APP_SECRET is required — boxBot will not start the WhatsApp listener without it. Set it in .env
  • Incoming webhooks validated against WhatsApp's X-Hub-Signature-256 header using HMAC-SHA256 with the app secret
  • Forged webhooks rejected (HTTP 403) before any processing
  • Webhook port is the only open listener (required by WhatsApp API)

Data Security

Secrets Management

Two distinct surfaces, by who needs the value:

1. Main-process secrets — .env

  • API keys the main process uses directly: ANTHROPIC_API_KEY, WHATSAPP_ACCESS_TOKEN, ELEVENLABS_API_KEY, AWS keys, etc.
  • File mode 0600, owned by the main-process user
  • WhatsApp credentials also in config/whatsapp.yaml (gitignored)
  • The sandbox user has no read access (and home-dir 0700 blocks traversal regardless)

2. Agent-managed secrets — bb.secrets / data/credentials/secrets.json

  • Credentials the agent stores at runtime (third-party API keys, OAuth refresh tokens, e.g. GOOGLE_CALENDAR_TOKEN_JSON)
  • File mode 0600, owned by the main-process user, atomic write, 64-secret cap, ≤8 KB per value
  • Sandbox scripts and integrations receive their values only as BOXBOT_SECRET_<NAME> env vars at launch, scoped to:
    • The names declared in an integration's manifest secrets: list, or
    • The names the agent passed in execute_script's secrets=[…]
  • The agent itself cannot read values back. SDK calls return name lists and existence checks; values cross only the runner→subprocess boundary
  • Action log redaction: secrets.store records {name, status} only; the value is never written to the action log the agent observes

Neither store is committed; both are gitignored.

Local Data

  • Person embeddings stored locally in data/ (gitignored)
  • Photos stored locally in data/photos/ (gitignored)
  • Memory database stored locally (gitignored)
  • Optional encrypted cloud backup (S3, disabled by default)

Sandbox Isolation (OS-Level)

Sandbox security is enforced at the operating system level. Python- level restrictions can always be bypassed; OS-level enforcement cannot be circumvented from userspace code.

Dedicated user: Scripts run as boxbot-sandbox, a restricted system user with minimal permissions. The main process runs as boxbot.

seccomp filter: The sandbox process has execve, fork, vfork, and clone syscalls blocked at the kernel level. No subprocess spawning of any kind — subprocess.run(), os.system(), os.exec*(), and even ctypes-based syscalls are all killed by the kernel.

Filesystem permissions: Enforced by Unix file ownership, not Python checks:

  • .env → mode 0600 owned by boxbot → sandbox cannot read
  • src/boxbot/ → owned by boxbot → sandbox cannot write
  • data/sandbox/venv/lib/ → owned by boxbot → sandbox cannot write (read + execute only)
  • data/sandbox/venv/bin/pip → mode 0700 owned by boxbot → sandbox cannot execute
  • data/sandbox/output/, tmp/ → owned by boxbot-sandbox → writable
  • skills/ → group-writable → sandbox can create skill directories

Resource limits: 30s timeout (main process kills), 256MB memory (ulimit/cgroup), single core.

Secrets: Scripts receive only specific API keys needed for the current task, passed as env vars by the main process. Never the full .env.

Package Installation (Out-of-Band Approval)

The sandbox cannot install packages. This is enforced by four independent OS-level controls:

  1. seccomp blocks execve → can't run pip as subprocess
  2. pip binary is mode 0700 owned by boxbot → sandbox can't execute it
  3. site-packages is owned by boxbot → sandbox can't write to it
  4. venv directory is read-only to boxbot-sandbox

Only the main process (as boxbot) can install packages, and it will only do so after out-of-band human approval through a channel the sandbox cannot access:

  • Display tap: approval prompt on the touchscreen (physical input)
  • WhatsApp reply: message to the admin user, response validated against the admin's phone number

The approval channel is completely separate from the sandbox's stdout. The sandbox can only emit a REQUEST action — there is no SDK action that means "already approved." The agent cannot spoof approval because the approval comes from a different input pathway entirely.

All installs are logged to data/sandbox/installed.txt (append-only, owned by boxbot).

Agent Authoring via SDK

The agent creates skills and displays through the boxbot_sdk — a constrained, immutable API pre-installed in the sandbox venv. The agent cannot write code that runs directly in the main process:

  • SDK is in read-only site-packages (owned by boxbot)
  • Skills auto-activate (logic runs in sandbox, contained)
  • Displays are declarative (agent uses building blocks, main process generates validated render code — no raw render() from agent input)
  • Displays require user confirmation before activation
  • Core code (src/boxbot/) is off-limits (owned by boxbot)

OS Hardening

The application-level controls above protect against a compromised agent. OS-level hardening protects the Pi itself from network-based attacks.

scripts/harden-os.sh is a standalone, idempotent script that:

  • Enables a UFW firewall (deny all inbound; allow only SSH and the WhatsApp webhook port)
  • Hardens SSH: key-only authentication, no root login, idle timeout
  • Enables automatic security updates (Debian security patches only)
  • Disables unnecessary services (Bluetooth, mDNS)

SSH remains enabled — this is a tinkerer project — but password auth is disabled. The script includes lockout protection: it will not disable password auth unless at least one SSH public key is present.

See os-hardening.md for full rationale, verification steps, and how to revert each change.

Threat Model

Registration & Access

Threat Mitigation
Unauthorized WhatsApp message Hard block: silent drop, no response, no info leakage
Brute-force registration code Rate limiting (5 attempts/10min), temp/perm blocking, no response on failure
Registration code interception Single-use, 10-min expiry, admin shares out-of-band
Bootstrap hijacking Code on physical screen (requires presence), bootstrap disabled after first admin
Admin impersonation WhatsApp verifies phone ownership; admin phone number is the identity
Privilege escalation Promotion requires existing admin confirmation via WhatsApp
Self-lockout Primary admin cannot be demoted; physical button reset as fallback

Sandbox & Code Execution

Threat Mitigation
API key exposure .env mode 0600, owned by boxbot, not readable by sandbox
Direct pip install seccomp blocks execve + pip not executable + site-packages read-only
Spoof package approval Approval is out-of-band (physical tap / admin WhatsApp); sandbox stdout can only emit requests
Malicious skill code Sandbox: separate user, seccomp, filesystem restrictions
Malicious display code Declarative SDK (no raw code); user approval for activation
Supply chain attack (pip) Package install requires out-of-band human approval
Sandbox escape OS-level: separate user, seccomp, filesystem permissions, resource limits

Agent & Communication

Threat Mitigation
Prompt injection via WhatsApp Only registered users can message; agent instruction hierarchy
Prompt injection via web content Small-model content firewall: web results filtered by isolated small agent before reaching large model. Small agent has no boxBot tools/SDK access; output is plain text only. Defense in depth: small model filters, plain text boundary, large model judgment
Prompt injection → install package Approval requires human on separate channel; agent can't self-approve
Privilege confusion (email via WhatsApp) Inbox services are skills (sandbox), not WhatsApp features; privileged channel separation
Camera/mic data exfiltration All perception on-device, no streaming
Network scanning/probing UFW denies all inbound except SSH (key-only, rate-limited) and webhook (signature-validated)

Reporting Vulnerabilities

If you discover a security vulnerability, do not open a public issue. Contact the maintainers directly.

There aren't any published security advisories