Privilege boundaries as lateral movement stops

A compromised host on a flat network can reach every other node without crossing a single firewall rule. Network perimeter checks are useless if the interior is trusted by default; that is where lateral movement prevention actually matters.

Stopping lateral movement in homelab systems: ACLs, VLAN isolation, and process confinement

A flat homelab network is an attacker’s best asset. One compromised host with SSH credentials or a reused API token can reach every other node on the same subnet without crossing a single firewall rule. The perimeter is checked; the interior is trusted by default. That assumption is where lateral movement prevention fails, and it fails quietly.

Why a single breach spreads across a flat network

When all hosts share a broadcast domain, there is no network cost to moving between them. ARP requests reach every device. A compromised host can scan 192.168.1.0/24 and receive responses from your NAS, your Proxmox management interface, your DNS resolver, and your password manager container, all in under a second. Nothing in the network prevents the scan; nothing logs it as suspicious by default.

Credential reuse compounds the exposure. Admin passwords reused across nodes, SSH keys shared between service accounts, and API tokens with no scope restrictions mean that one successful breach hands over the keys to adjacent systems. The attacker does not need a new exploit; they just authenticate.

East-west traffic is the path lateral movement uses. Perimeter firewalls protect north-south traffic, meaning traffic entering or leaving the network. They do not inspect or restrict host-to-host traffic inside the same VLAN. If your media server, your home automation hub, and your backup node all sit in the same segment, any of them can initiate a connection to any other, with no policy in place to stop it.

VLAN hopping makes the situation worse when switch configuration is loose. Double-tagging attacks against 802.1Q, where a crafted frame carries two VLAN tags and the outer tag matches the native VLAN on a trunk port, can allow an attacker to send traffic into a segment they should have no access to. The fix is straightforward: set the native VLAN on all trunk ports to an unused, unrouted VLAN ID, and never assign any host to that ID. Most homelab switches running OpenWrt or Cisco IOS let you do this in a few lines.

ACLs, VLAN segmentation, and container isolation as distinct stops

These three controls operate at different layers. Stacking them means a failure at one layer does not automatically compromise the next.

Layer 3 ACLs at segment boundaries

ACLs placed on the routed interface between VLANs, not on host firewalls, control which subnets can initiate connections to which others. On a router running OpenWrt or pfSense, this means an explicit permit list on each inter-VLAN interface. The default action must be deny.

A working pattern for a homelab with three segments looks like this: the management VLAN (say 10.0.10.0/24) can initiate connections to the IoT VLAN (10.0.30.0/24) on specific ports only. The IoT VLAN cannot initiate any connection back to the management VLAN. The lab VLAN (10.0.20.0/24) can reach external DNS and NTP but has no permit rule to the management VLAN at all. Write the ACL in that direction: source VLAN initiates, destination VLAN receives, port and protocol locked to the service.

Place ACLs as close to the source as the hardware allows. An ACL applied to the outbound interface of a VLAN means traffic is checked before it hits the routing table, not after it has already crossed the segment boundary.

VLAN segmentation as a broadcast boundary

VLANs remove the passive reconnaissance step. A host in the IoT VLAN cannot see ARP broadcasts from the management VLAN because they are separate broadcast domains. The attacker cannot passively map your management hosts; they have to route through the chokepoint, where the ACL is waiting.

The chokepoint only works if trunk ports are configured correctly. Set each access port to a single VLAN, no DTP negotiation, and no dynamic trunking. On a managed switch, disable DTP on every port that is not intentionally a trunk. On Cisco-syntax switches, that is switchport nonegotiate on the access port; on OpenWrt DSA, set the port to the VLAN with untagged and assign nothing else.

Keep the native VLAN on trunk ports set to an ID that carries no traffic. VLAN 999, unrouted, no hosts. That alone closes the double-tagging attack vector.

Container network policies

Container networking sits below the VLAN layer but creates its own east-west exposure. By default, Docker puts all containers on the same bridge network and allows unrestricted traffic between them. A compromised container can reach every other container on that bridge.

The fix in Docker is to create separate user-defined bridge networks per service group and connect containers only to the networks they need. A container running a reverse proxy needs a connection to the upstream service; it does not need to reach the database directly. Create a frontend network and a backend network. Attach the proxy to both; attach the database only to backend. The proxy cannot send traffic to the database even if the proxy container is fully compromised.

In Kubernetes, NetworkPolicy resources achieve the same result at the pod level. A default-deny ingress policy applied to a namespace means no pod receives inbound traffic from any other pod unless an explicit allow rule names the source namespace and port. Set this as the baseline for every namespace, then add specific permits. The policy only takes effect if the CNI plugin supports it; Calico, Cilium, and Weave Net do. Flannel alone does not.

Privilege boundaries across tiers

Network controls stop traffic; privilege boundaries stop an attacker who has already moved. A process running as root inside a container with a mounted host socket (/var/run/docker.sock) can escape the container entirely. A service account with cluster-admin rights in Kubernetes can read secrets from every namespace. These are not hypothetical paths; they are the ones commonly abused in real post-breach scenarios.

Run containers with --cap-drop=ALL and add back only the capabilities the process actually needs. Set no-new-privileges: true in the security context. Do not mount the Docker socket into any application container. Use read-only root filesystems where the process permits it.

In Proxmox, separate management access from VM access. The root PAM account should not be used for daily operations. Create a user with the minimum PVE role needed, scoped to the specific node or pool. Storage access for backup jobs should use a token with Datastore.Backup only, not a full admin token.

Service accounts in any system should be scoped to a single function and a single target. A token that can read one secret from one path in Vault is far less useful to an attacker than a token with policy = "default" that reads across all paths. Write the policy first, then create the token against it.

The three controls reinforce each other. VLANs reduce passive reconnaissance. ACLs block unauthorised initiations at the routing layer. Container network policies and capability restrictions contain the blast radius if a workload is compromised. Each layer assumes the previous one has already failed.