My app's landing page says 'Zero Telemetry'. So I wiretapped my own app to prove it. A solo developer of the macOS network-security app RoamSwitch wiretapped his own app to prove its 'Zero Telemetry' claim, using tcpdump and free tools to capture and analyze outbound traffic. The procedure, documented in a blog post, verifies that only four whitelisted destinations are contacted and can be re-run for every release to catch regressions. A Japanese version of this is on Zenn. I build and sell a macOS network-security app as a solo dev. The landing page says: Zero Telemetry— no outbound traffic, no data collection Words are cheap, and that's just a claim . To a user, this is "an unknown solo dev's app that rewrites your firewall with root, saying 'trust me, I send nothing.'" There's no reason to believe it. And this app sees sensitive stuff, constantly: the gateway MAC and fingerprint, listening ports, the Wi-Fi SSID you're on, FileVault / SIP / firewall state, files it scanned, URLs it checked. If any of that leaked, it would betray exactly the people who picked it for privacy. So I decided to turn the claim into a measurement. tcpdump and free tools only — wiretap my own app. This post is the method and the places I tripped. "The MCP server and detection logic are open source — just read them." A few holes: 1. The main app isn't open source. Only the read-only MCP server and detection logic are public; the privileged helper, pf control, and licensing stay closed that surface helps attackers . Code alone doesn't cover the whole thing. 2. No App Sandbox. To be upfront: RoamSwitch does not run inside App Sandbox — it controls the firewall and sharing daemons, so it can't. The consequence is that entitlements don't restrict outbound traffic . codesign -d --entitlements showing "no network-client key" is not proof it can't send. You have to show behaviour. 3. Regressions. Even if this build is truly silent, a future version could add an analytics SDK, a crash reporter, a "check for news" feature. A one-time check is worthless — this needs to be a procedure you can re-run every release . So instead of "read the code," I hand people "run this procedure." The only traffic allowed out is the four paths documented in the whitepaper §7 : | | destination | process | when | |---|---|---|---| | 1 | lafine.net/api/v1/license/ | RoamSwitch | license activate / deactivate only | | 2 | lafine.net/updates/appcast.xml | RoamSwitch | Sparkle launch + every 24 h | | 3 | ClamAV mirrors | freshclam not the app | only with ClamAV installed | | 4 | HEAD to a target URL | RoamSwitch | only when the in-app Link Safety sheet is used | Anything else out of RoamSwitch / RoamSwitchHelper / RoamSwitchMCPServer is a fail . I don't own Little Snitch, so: brew install wireshark — pull destinations and TLS SNI from the pcapLuLu is a fine Little Snitch substitute, and it's by a well-known name in Mac security — which, for a write-up, only helps. I'd convinced myself that "Sparkle's periodic check is every 24 h, so I have to capture for 25 h straight to span it." I set up for an overnight run. Then I looked at Info.plist : SUScheduledCheckInterval = 86400 , and SULastCheckTime sits in defaults in the clear. So there's no waiting: BID=com.tetsuharu.RoamSwitch defaults delete $BID SULastCheckTime osascript -e 'quit app "RoamSwitch"'; sleep 2; open -a RoamSwitch launch sees "overdue" and checks within seconds to a couple of minutes The appcast HTTPS GET is byte-for-byte identical whether it comes from this, the "Check for Updates" menu item, or a fresh first launch. The only reason to run long is to catch a wall-clock "once a day" telemetry beacon — an unusual design, and there's no analytics SDK in the app anyway open source, checkable . So a few hours of idle is plenty. Total active work: ~3–4 h, one sitting. tcpdump tells you "a SYN went to 140.82.112.x." It does not tell you whether RoamSwitch, Chrome, Dropbox, or the OS sent it. On a daily-driver machine, even idle, there's a flood of non-Apple traffic: Safe Browsing, component updaters, cloud sync, chat keep-alives. Picking RoamSwitch's packets out of that by timestamp and port is genuinely error-prone — you end up waving things away as "probably Chrome." So you make the machine silent first. A dedicated account or spare Mac is ideal; quieting the same login also works: launchctl bootout the third-party agents reverts on reboot softwareupdate --schedule off , analytics off, Spotlight suggestions offThen, before launching RoamSwitch, run tcpdump for 10 minutes and check that every remaining destination is Apple 17.0.0.0/8 , etc. . If a non-Apple destination is still there, something's still running — find it with lsof -nP -iTCP@