Network Policy¶
The VM runs a UFW (Uncomplicated Firewall) with a default-deny policy in both directions. Only explicitly allowlisted traffic is permitted. All denied connections are logged for audit.
Firewall Rules¶
| Direction | Port | Protocol | Purpose |
|---|---|---|---|
| IN | 18789 | TCP | Gateway API (agents connect here) |
| IN | 22 | TCP | SSH (Ansible provisioning, limactl shell) |
| OUT | 443 | TCP | HTTPS (LLM APIs, GitHub, npm registries) |
| OUT | 80 | TCP | HTTP (APT package updates) |
| OUT | 53 | UDP | DNS (name resolution) |
| OUT | 53 | TCP | DNS (large responses, zone transfers) |
| OUT | 100.64.0.0/10 | * | Tailscale CGNAT range |
| OUT | 41641 | UDP | Tailscale direct connections |
| OUT | 123 | UDP | NTP (time synchronization) |
| OUT | 4318 | TCP | OTEL export to host collector (when qortex_otel_enabled, to 192.168.5.2) |
| IN/OUT | lo | * | Loopback (required for local services) |
Everything else is denied and logged.
What Each Rule Allows¶
Inbound¶
Gateway (TCP 18789): The OpenClaw gateway listens on this port. Agents, the TUI, and the host claw CLI connect here. This is the only way to interact with the gateway from outside the VM.
SSH (TCP 22): Required for Lima VM management. Ansible uses SSH to provision the VM, and limactl shell uses SSH to open interactive sessions. Without this rule, you cannot manage the VM.
Outbound¶
HTTPS (TCP 443): Required for LLM API calls (Anthropic, OpenAI, Google AI, OpenRouter), GitHub API (gh commands), npm/PyPI package downloads, messaging API calls (Slack, Discord, Telegram), and the LinWheel API (LinkedIn content management).
HTTP (TCP 80): Required for APT package repository updates. Ubuntu mirrors serve package metadata over HTTP.
DNS (UDP/TCP 53): Required for name resolution. Without DNS, no outbound HTTPS connections can be established.
Tailscale (100.64.0.0/10 + UDP 41641): Allows routing traffic to your Tailscale network through the host. The CGNAT range covers all Tailscale node IPs. Port 41641 enables direct peer-to-peer connections.
NTP (UDP 123): Required for time synchronization. TLS certificate validation fails with incorrect system time, which would break all HTTPS connections.
OTEL (TCP 4318): When qortex_otel_enabled is true (default), allows the VM to send OpenTelemetry traces and metrics to the host collector at 192.168.5.2:4318 (Lima's host gateway IP). This is scoped to a single IP, not a broad outbound rule. The qortex learning pipeline uses this to export bandit selection events, observation rewards, and Prometheus metrics to host-side Grafana.
Default Policies¶
The firewall starts with:
Default incoming: DENY
Default outgoing: DENY
This is a strict allowlist model. If a port or destination is not explicitly in the table above, it is blocked and logged.
Configuration Variables¶
| Variable | Default | Description |
|---|---|---|
firewall_reset_on_run |
true |
Reset UFW to clean state on each provision |
firewall_gateway_port |
18789 |
Inbound port to allow for the gateway |
firewall_tailscale_cidr |
100.64.0.0/10 |
Tailscale IP range |
firewall_tailscale_port |
41641 |
Tailscale direct connection port |
firewall_enable_logging |
true |
Log denied packets |
firewall_log_limit |
3/min |
Rate limit for log entries |
firewall_otel_host_ip |
192.168.5.2 |
Host IP for OTEL export (Lima gateway) |
firewall_otel_ports |
["4318"] |
Ports to allow for OTEL export |
firewall_reset_on_run
By default, UFW is reset to a clean state on every provision run. This ensures the firewall matches the expected configuration. If you add custom rules manually, set firewall_reset_on_run=false to preserve them:
bash
./bootstrap.sh --openclaw ~/Projects/openclaw -e "firewall_reset_on_run=false"
Customizing Rules¶
Adding an outbound rule¶
SSH into the VM and add the rule:
# Allow outbound to a specific service
limactl shell openclaw-sandbox -- sudo ufw allow out to any port 8080 proto tcp
# Allow outbound to a specific IP
limactl shell openclaw-sandbox -- sudo ufw allow out to 10.0.0.5
Custom rules are lost on re-provision
Unless firewall_reset_on_run=false, all custom rules are wiped on the next ./bootstrap.sh run. For permanent additions, modify ansible/roles/firewall/tasks/main.yml.
Allowed domains reference¶
The firewall_allowed_domains list in the firewall defaults documents which domains the HTTPS rule is intended to cover:
firewall_allowed_domains:
- api.openai.com
- api.anthropic.com
- generativelanguage.googleapis.com
- bedrock-runtime.us-east-1.amazonaws.com
- slack.com
- api.slack.com
- discord.com
- discordapp.com
- linwheel.io
- www.linwheel.io
The HTTPS rule is port-based, not domain-based
The current firewall allows all outbound TCP 443 traffic, not just the domains listed above. The domain list is documentary -- it describes which services the sandbox is designed to reach. Any HTTPS endpoint is reachable from the VM. Domain-based filtering would require a transparent proxy or DNS-level blocking, which is not currently implemented.
Verification Commands¶
# Check firewall status
limactl shell openclaw-sandbox -- sudo ufw status verbose
# Check firewall is active
limactl shell openclaw-sandbox -- sudo ufw status | head -1
# View denied connections in logs
limactl shell openclaw-sandbox -- sudo journalctl -k | grep UFW
# Test outbound HTTPS
limactl shell openclaw-sandbox -- curl -s -o /dev/null -w '%{http_code}' https://api.anthropic.com
# Test that non-allowed ports are blocked
limactl shell openclaw-sandbox -- curl -s --connect-timeout 5 telnet://example.com:25
# Should timeout/fail
# Check specific rules
limactl shell openclaw-sandbox -- sudo ufw status numbered
Audit Trail¶
With firewall_enable_logging=true (the default), denied packets are logged to the kernel log:
# View firewall denials
limactl shell openclaw-sandbox -- sudo dmesg | grep UFW
# Follow live
limactl shell openclaw-sandbox -- sudo journalctl -kf | grep UFW
Log entries include source/destination IP, port, protocol, and interface -- giving you visibility into what the VM (or containers) attempted to reach and was denied.