diff --git a/AGENTS.md b/AGENTS.md index 3ae47b3..c3d7c07 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -120,6 +120,48 @@ if d.AllowInternet { - NOT used for server-side WireGuard kernel config - Example: `10.172.21.0/24` allows peer to reach full subnet via firewall + routes full subnet through VPN on client +## FIREWALL CHAINS — INPUT vs FORWARD (CRITICAL) + +nftables traffic enters different chains depending on destination: +- **Traffic TO server's own IP** (e.g. 10.172.21.1) → **INPUT chain** +- **Traffic THROUGH server** (peer-to-peer, e.g. 10.172.21.2 → 10.172.21.3) → **FORWARD chain** + +### Rule routing in code +| Destination | Chain | Method | +|-------------|-------|--------| +| Server's own `interface_address` | INPUT | `AddInputFirewallRule()` | +| Other peer IPs / subnets | FORWARD | `AddFirewallRule()` | + +### Detection logic (syncRuleToFirewall) +```go +serverIP = strings.Split(localServer.InterfaceAddress, "/")[0] +destBase = strings.Split(destCIDR, "/")[0] +if destBase == serverIP { + fw.AddInputFirewallRule(...) // → INPUT chain +} else { + fw.AddFirewallRule(...) // → FORWARD chain +} +``` + +### Anti-pattern +```go +// WRONG — all rules go to FORWARD, server IP rules are dead +fw.AddFirewallRule(...) // for dest=10.172.21.1 → enters FORWARD chain → never matched +``` + +### Correct pattern +```go +// CORRECT — detect server IP, route to correct chain +if destBase == serverIP { + fw.AddInputFirewallRule(...) // → INPUT chain (matches traffic TO server) +} else { + fw.AddFirewallRule(...) // → FORWARD chain (matches traffic THROUGH server) +} +``` + +### Why +Traffic to server's own IP is processed by INPUT chain, not FORWARD chain. Placing rules in FORWARD chain for server-bound traffic makes them dead rules that never match. + ## UNIQUE STYLES - **Zero-Attack Surface**: `/auth/register` locked; admin via `-create-admin` CLI only - **Stealth Agent**: No `/etc/wireguard/` — config in memory only