How it works
When you run silo npm run dev, three things happen:
1. Compute a deterministic IP from the git directory 2. Configure the loopback interface (IP alias + /etc/hosts) 3. Intercept bind() and connect() syscalls in the child process
Your app still thinks it's on localhost:3000. Silo rewrites the address transparently. No code changes needed.
IP computation
Silo hashes the canonical git directory path and the branch name with FNV-1a to produce a deterministic IP in the 127.0.0.0/8 range.
input: /home/user/project + "feature-auth"
↓ FNV-1a → mod 127.0.0.0/8
ip: 127.185.176.25The first octet is always 127. Octets 2 and 4 are clamped to 1–254 to avoid broadcast and zero addresses. This gives ~16.5 million unique addresses, more than enough for any machine.
Since each git worktree has a different directory path, each gets a different IP. Same directory + same branch = same IP, every time.
Session setup
Before executing your command, silo:
# Add a loopback alias (macOS) sudo ifconfig lo0 alias 127.185.176.25 netmask 255.0.0.0 # Add a loopback alias (Linux) sudo ip addr add 127.185.176.25/8 dev lo # Add /etc/hosts entry 127.185.176.25 feature-auth.project.silo # /home/user/project
Passwordless sudo is configured once via /etc/sudoers.d/silo (silo prompts on first run). Aliases and hosts entries persist across runs. Use silo prune to clean up.
Syscall interception
Silo intercepts socket syscalls to redirect network traffic to the assigned IP. Two backends are available:
Preload (macOS + Linux)
A shared library (libsilo_bind) is injected via DYLD_INSERT_LIBRARIES (macOS) or LD_PRELOAD (Linux). It interposes on these syscalls:
bind() 0.0.0.0:port → SILO_IP:port
127.0.0.1:port → SILO_IP:port
connect() 127.0.0.1:port → SILO_IP:port
(only if a listener exists on SILO_IP:port)
sendto() same rewriting as bind
sendmsg() same rewriting as bind
getifaddrs() hides other silo IPs from the interface listThe connect() rewrite probes for a listener first. If nothing is bound on SILO_IP:port, the connection goes to 127.0.0.1 as usual. This prevents hijacking connections to databases or other services running on localhost. You can disable connect rewriting entirely by setting SILO_CONNECT=0.
eBPF (Linux only)
On Linux, silo can use cgroup-attached BPF programs to intercept at the kernel level. This works with static binaries and doesn't require LD_PRELOAD.
# One-time setup (requires root) sudo silo setup-ebpf # After this, silo automatically uses the eBPF backend silo ./my-static-binary
Requires cgroup v2 and kernel 5.8+.
IPv6 handling
When an app binds to :: (IPv6 any) or ::1 (IPv6 loopback), silo rewrites it to an IPv4-mapped IPv6 address: ::ffff:127.x.y.z. On macOS, silo replaces the IPv6 socket with an IPv4 socket entirely, copying all socket options.
macOS SIP bypass
macOS System Integrity Protection strips DYLD_INSERT_LIBRARIES from binaries in /usr/bin, /bin, etc. Silo handles this by:
1. Detecting SIP-protected paths 2. Searching PATH for a non-SIP alternative (e.g. Homebrew) 3. Following shebangs recursively (up to 5 levels) 4. Resolving /usr/bin/env -S invocations
For example, /bin/bash gets resolved to /opt/homebrew/bin/bash if available.
Limitations
CGO_ENABLED=1 for Go.--ip for non-git dirs.DYLD_INSERT_LIBRARIES. Install shells via Homebrew.127.0.0.0/8. Pure IPv6 listeners are converted to IPv4-mapped addresses.