wren-linux / data / claude-vm-firewall.nft
# Keeps the claude-vm user off your computer and your local network
# (Wren Linux extras/claude-vm.sh). Only that user's traffic is touched.
#
# claude-vm runs the disposable VM (one qemu-system-x86_64 process). QEMU's
# user-mode networking turns the guest's traffic into ordinary host sockets
# owned by claude-vm, so this rule governs the GUEST's reach as well: it can
# talk to the internet (Claude's servers, git hosts, package mirrors), but
# not your router, printers, other machines, or the services listening on
# this computer.
#
# The one exception is loopback TCP to port 2242: that is the guest's own
# SSH console (QEMU forwards 127.0.0.1:2242 to the guest's port 22), and the
# host has to reach it to log in, install Claude Code and bring work out.
# Nothing else on 127.0.0.1 is reachable. If you change SSH_HOST_PORT in
# extras/claude-vm.sh, change 2242 here too, then:
# sudo systemctl restart wren-claude-vm-firewall
#
# DNS goes only to the resolver this computer uses (parts/sandbox-dns.sh).
table inet wren_claude_vm
delete table inet wren_claude_vm
table inet wren_claude_vm {
# The resolver this computer uses (Unbound on 127.0.0.1 with secure DNS on, the
# network's while it is off), kept up to date by wren-sandbox-dns
# (parts/sandbox-dns.sh): DNS goes there only, not to any address
set resolvers {
type ipv4_addr
}
chain output {
type filter hook output priority 0; policy accept;
meta skuid "claude-vm" jump claude_vm
}
chain claude_vm {
# Answers only from the guest's console port, which QEMU listens on for the
# login (another account's VM, claude-vm-NAME, answers from its own): nothing
# else, so it can't stand in for a service here
# (only answers: from that port alone it could start connections too)
ct direction reply meta skuid "claude-vm" ip saddr 127.0.0.1 tcp sport 2242 accept
ip daddr @resolvers udp dport 53 accept
ip daddr @resolvers tcp dport 53 accept
# The guest's SSH console on the host loopback, and nothing else on it
# (another account's VM, claude-vm-NAME, reaches only its own port)
meta skuid "claude-vm" ip daddr 127.0.0.1 tcp dport 2242 accept
# Anything addressed to this computer itself goes out through "lo",
# whichever of its addresses is used (127.0.0.1, its LAN address, its
# public IPv4 or IPv6 address), so this catches them all
# (admin-prohibited: how bin/claude-vm-net-check tells this from a closed port)
oifname "lo" reject with icmpx admin-prohibited
ip daddr { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8,
169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16,
224.0.0.0/4, 255.255.255.255 } reject
# No IPv6 at all: on a network with global addresses, the router and other
# computers have addresses no fixed rule can tell from the internet's.
# Sites fall back to IPv4 at once (an IPv6-only network wouldn't work)
meta nfproto ipv6 reject
}
}