Security Hardening
Network exposure model
Section titled “Network exposure model”| Scenario | Recommended config |
|---|---|
| Local only (single machine) | Default — no changes needed |
| LAN access (home/office) | Bind to LAN IP, firewall to local subnet |
| Internet-facing | Reverse proxy with HTTPS, firewall, IP allowlist |
Use a reverse proxy
Section titled “Use a reverse proxy”Never expose the Snippbot port directly to the internet. Use nginx or Caddy to terminate TLS:
# Snippbot listens on localhost onlysnippbot start --host 127.0.0.1 --port 18781Your reverse proxy handles HTTPS and forwards to localhost:18781. See Reverse Proxy.
Firewall configuration
Section titled “Firewall configuration”Allow only trusted IPs to reach the proxy:
# Ubuntu — ufwufw allow from YOUR_IP_HERE to any port 443ufw deny 443
# Block direct access to Snippbot portsufw deny 18781ufw deny 18790The channel adapter (port 18790) needs to accept webhook traffic from specific platform IPs:
- Slack: see Slack IP ranges
- Discord: no fixed IP ranges — accept from anywhere, verify signatures
- Telegram:
149.154.160.0/20and91.108.4.0/22
TLS / HTTPS
Section titled “TLS / HTTPS”Always use HTTPS for internet-facing deployments:
# With Caddy (automatic Let's Encrypt)caddy run --config Caddyfile
# With nginx + certbotcertbot --nginx -d snippbot.yourdomain.comRedirect all HTTP to HTTPS — never serve on plain HTTP in production.
API key management
Section titled “API key management”-
Use strong API keys — the auto-generated
snip_keys are cryptographically random (256 bits). Don’t create short or guessable keys. -
Rotate keys regularly — revoke old keys and issue new ones:
Terminal window # Revoke via UI: Settings → API Keys → Revoke# Or via API:curl -X DELETE http://localhost:18781/api/auth/keys/key_abc123 \-H "Authorization: Bearer $CURRENT_KEY" -
Use one key per client — don’t share keys between scripts, CI/CD, and users. This way you can revoke one without disrupting others.
-
Store keys securely — use environment variables or a secrets manager. Never commit keys to version control.
Rate limiting
Section titled “Rate limiting”Add rate limiting at the reverse proxy layer:
# nginx — limit to 30 requests/second per IPlimit_req_zone $binary_remote_addr zone=snippbot:10m rate=30r/s;
server { location /api/ { limit_req zone=snippbot burst=60 nodelay; proxy_pass http://localhost:18781; }}Authentication hardening
Section titled “Authentication hardening”# Require auth for all endpoints (default: true)api_key_required = true
# Shorter session expiry for shared machinessession_expiry = 3600 # 1 hour
# Shorter inactivity timeoutinactivity_timeout = 900 # 15 minutesSandbox isolation
Section titled “Sandbox isolation”The code-execution sandbox is not configured with environment variables. It
is managed at runtime through the Sandbox settings page (or the
PUT /api/sandbox/config API) and persisted to ~/.snippbot/sandbox_config.json.
To harden it, set the Isolation Profile to Strict, which containerizes all commands, mounts the workspace read-only, and disables the container network (zero outbound egress). Equivalently, via the API:
curl -X PUT http://127.0.0.1:18781/api/sandbox/config \ -H "Authorization: Bearer $SNIPPBOT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode": "all", "workspace_access": "ro", "network": {"enabled": false}}'Cap per-container memory with the Resources fields on that page (or the
resources.memory_mb config field). Ensure a real container runtime is present
(Docker or Podman) rather than the process fallback — set runtime_preference
to prefer docker/podman, and consider block_when_no_runtime: true so a
missing runtime fails closed (blocks) instead of falling back to the host.
Package network permission
Section titled “Package network permission”A marketplace package asks for network access in its manifest (the hosts it calls and why). You answer when you install it, and you can change the answer later in Packages → the package → Permissions. The answer is per installed version.
-
Packages that were never asked. A package installed by a path that shows no permission dialog (the CLI, a bundle, an update) and that declares network shows up under Needs your answer at the top of the Packages page, with a count on the Packages menu entry. Until you answer, Snippbot treats it as “no”. Allow grants the hosts its manifest lists; Don’t allow records a “no” so it isn’t asked again.
-
Turning network off for a package records a “no” for the installed version.
-
What is enforced. Snippbot checks a package’s HTTP tools against this permission: a host you didn’t allow, cloud-metadata addresses and the domains you block under Security are refused before anything is sent.
-
The operating-system lock (macOS, and containers). A package with no network permission (said no, not answered yet, or allowed only this computer) runs its programs (Python and bash tools, page backends, stdio MCP servers) with no internet: they can reach this computer (
localhost) only, including Snippbot itself and local services such as ComfyUI. Names other thanlocalhostdon’t resolve inside the lock, and Unix sockets (for example Docker’s or ssh-agent’s) are refused, so Unix-socket IPC such as Python’smultiprocessing.Managerdoesn’t work either. Locked programs can’t hand work to other apps:opencan’t pass a link to the browser, Apple Events and new launchd jobs are refused, and Snippbot’s own code and Python environment are read-only to them. In a container (Safe or Strict isolation) such a package gets no network. When a locked package’s tool fails, the error says it has no network permission and where to allow it. On Linux the lock is not built yet, and Windows has no package sandbox, so there the permission is a record of what you agreed to, not a lock. -
Watching first. The lock ships in
shadowmode: nothing is blocked, and the daemon log notes each package program that would have run offline (package network (shadow): would run offline: …, plus a daily summary). Settings → Security → Package network lists those packages, so you can allow the ones that need the internet before turning the lock on withSNIPPBOT_PACKAGE_NETWORK_OS=enforce. A page backend or MCP server restarts when a permission change turns its network on or off. -
Host lists (the egress proxy). A package you allowed some hosts (rather than any) runs its programs with
HTTP_PROXY/HTTPS_PROXYset to a proxy inside the Snippbot daemon, on127.0.0.1and a port picked at start. Each program run gets its own password for it, which stops working when the run ends. For every new connection, the proxy asks the package’s current permission:- It connects to a host you allowed.
*.example.comcoversexample.comand its subdomains, and a host without a port means ports 80 and 443. - It refuses anything else with
403. - It refuses cloud-metadata addresses and your blocked domains, even after a name resolves.
The proxy reads the host and port only, never the content, so HTTPS stays end-to-end and certificate pinning keeps working. It also accepts SOCKS5 on the same port (
SNIPPBOT_SOCKS_PROXY). Each blocked connection appears in the activity feed, at most once per host every 10 minutes, and in~/.snippbot/logs/egress/blocked-YYYY-MM-DD.jsonl(hosts only). When you remove a host from the permission, the proxy closes that package’s open connections to it.With
SNIPPBOT_PACKAGE_EGRESS_PROXY=enforceon macOS, the program is also locked to this computer like a package with no network permission, so the proxy is its only way out. Clients that ignore proxy variables then fail:ssh,smtplib, Node before v24, gRPC, and aiohttp withouttrust_env=True. In containers a host list is not enforced (on/off only). - It connects to a host you allowed.
-
SSH from a package.
sshignores proxy variables, so a host-list package setsSNIPPBOT_CONNECTas its SSHProxyCommand:ssh -o "ProxyCommand=$SNIPPBOT_CONNECT %h %p" user@host. The helper tunnels the connection through the proxy, so the host is checked (and a refusal is shown) like any other. A host needs its port in the permission (host.example:22), since a host without a port means 80 and 443 only.SNIPPBOT_CONNECTis set only for host-list packages, so a package should use it when it is present. -
A package that asks for more hosts. Snippbot enforces the hosts you allowed, never the manifest’s list. When a package’s manifest names hosts its permission doesn’t have (an update that calls a new CDN, for example), it shows up under Asks for more hosts at the top of the Packages page, with a count on the Packages menu entry. Until you answer, the new hosts aren’t part of its permission. Allow adds exactly the hosts shown; Keep current leaves the permission as it is and records them as declined, so they aren’t asked again. An update that asks for the hosts you already answered (or fewer) keeps your answer.
-
Watching the proxy first. The proxy also ships in
shadowmode. Host-list packages get the proxy variables, but nothing is blocked: the proxy forwards what it would refuse and logspackage network (shadow): would block <package> → <host:port>(and the jsonl line, with"shadow": true). Programs that ignore proxy variables aren’t seen in shadow mode. -
Settings → Security → Package network says which applies on this computer, and the install dialog labels the Network permission the same way.
-
Shell permission lists the programs a package says it runs. It is disclosure only: Snippbot doesn’t stop the package running other programs.
Three environment variables control the checks. Each accepts off, shadow
(log only) and enforce; an unknown value enforces.
| Variable | What it controls | Default |
|---|---|---|
SNIPPBOT_PACKAGE_NETWORK_HTTP | The check on package HTTP tools | enforce |
SNIPPBOT_PACKAGE_NETWORK_OS | The operating-system network lock for package programs with no network permission (macOS and containers) | shadow |
SNIPPBOT_PACKAGE_EGRESS_PROXY | The egress proxy for packages allowed a host list. off means a host list allows any host, as before | shadow |
SNIPPBOT_PACKAGE_OS_SANDBOX=off turns off the whole package sandbox (the
file protections for package tools, page backends and MCP servers too), and
with it the network lock and the egress proxy; Settings warns when it is set.
Monitoring for threats
Section titled “Monitoring for threats”Enable debug logging for the auth subsystem:
SNIPPBOT_DEBUG=1 snippbot start 2>&1 | grep -i 'auth\|unauthorized\|failed'Review the access log periodically:
grep '401\|403' ~/.snippbot/logs/daemon.log | tail -50