Keep It Local Developer Simon Willison warns against accidentally exposing local apps to the internet, advocating for binding to localhost or private networks like Tailscale by default. He demonstrates how to secure tools such as his Claude Code dashboard clodhopper, air, Python's http.server, and App::HTTPThis by using explicit bind addresses instead of the default 0.0.0.0. Who wants to talk about networking today? I know I do. Two addresses do most of the heavy lifting here: 127.0.0.1 aka localhost or the loopback address — only your own machine can reach it and 0.0.0.0 bind to this and you’re reachable on every network interface . See Localhost https://en.wikipedia.org/wiki/Localhost and 0.0.0.0 https://en.wikipedia.org/wiki/0.0.0.0 Binding for a refresher. clodhopper clodhopper Yesterday I talked about clodhopper /2026/06/29/on-hopping-claudes/ , my personal Claude Code dashboard. It collects data from your running agents and spins up a read-only app to show their status. By default, it binds to localhost 127.0.0.1 , which means I don’t accidentally broadcast my workflows to the world. I also run it on my Tailscale network, which means I can view it on my own private network, but the world can’t. I was doing this by interpolating the Tailscale IP in the startup command: clodhopper serve --host "$ tailscale ip -4 " . Today I added a --tailscale arg, to make this a touch easier. Default: loopback only — only this machine can reach it clodhopper serve Don't do this on an untrusted network — binds every interface CLODHOPPER HOST=0.0.0.0 clodhopper serve Tailnet only — reachable from your tailnet subject to ACLs , not the LAN clodhopper serve --tailscale So, that’s all fine, but we are now living in a world where anyone can spin up a custom app on their machine and accidentally broadcast it to the world. Is this bad? Not always, but consider the following: - your app keeps secrets in .env - your app spins up a web server .env somehow ends up in the path that your app is serving- random bot sniffs out your app and fetches .env - now you need to rotate your secrets and you may not even be aware that your secrets are in the hands of a bad actor " Localhost https://www.flickr.com/photos/10768314@N00/4823138926 " by Wesley Nitsckie https://www.flickr.com/photos/nitsckie/ is licensed under CC BY-SA 2.0 https://creativecommons.org/licenses/by-sa/2.0/ . Ideally you’d restrict access to your resources to only the audiences which require them. So, defaulting to localhost and then expanding your reach from there is a good way to go. In my case I’ve been enjoying using a Tailscale https://tailscale.com/ tailnet. Only my own authenticated devices can connect. Internet creeping will have to take place elsewhere, because my apps are now for my eyes only. Moving beyond clodhopper , here are ways to apply the same principle to some open source apps. air air air https://github.com/air-verse/air runs full bin through a shell, so you can interpolate tailscale ip -4 straight into your app’s host flag in .air.toml — no hardcoded address: build Loopback only full bin = "./tmp/main -port 5003 -host 127.0.0.1" Tailnet only — resolved at startup, no hardcoded address full bin = "./tmp/main -port 5003 -host $ tailscale ip -4 " Python’s built-in file server pythons-built-in-file-server http.server binds 0.0.0.0 by default — pass an explicit --bind . Default binds 0.0.0.0 all interfaces python3 -m http.server 5000 Loopback only python3 -m http.server 5000 --bind 127.0.0.1 Tailnet only python3 -m http.server 5000 --bind "$ tailscale ip -4 " App::HTTPThis apphttpthis App::HTTPThis https://metacpan.org/dist/App-HTTPThis/view/bin/http this serves the current directory over HTTP.- Use --host to control the bind address. Loopback only http this --host 127.0.0.1 Tailnet only http this --host "$ tailscale ip -4 " nota bene : the current version of http this binds to every interface but emits a message that implies that it is binding only to 127.0.0.1 . bash $ http this . Exporting '.', available at: http://127.0.0.1:7007/ 😬 Today I opened 13 https://github.com/davorg-cpan/app-httpthis/pull/13 to clarify the behaviour, but maybe take this as a reminder that it’s good to be explicit about the things that really matter, rather than relying on the defaults.