Disclosure: this post was drafted with AI assistance and reviewed by ShrineTech. The header image was generated with Krea 2. Defensive guidance only: audit machines you own or administer.
While watching our own machines, we noticed that a coding tool we use was calling a vendor catalogue endpoint every hour. Nobody had asked it to. We removed that behaviour from our fork of ZCode and then asked the obvious follow-up: what else on our machines is quietly talking to the internet?
Our answer was "more than expected, and almost all of it harmless-looking." Update checks, analytics pings, version probes, error reporters. Individually they are small. Together they tell third parties which tools you run, how often, and sometimes what is installed. This post is the method, so you can run it on your own box.
Method
1. Sample live connections, without root and without packet capture. ss lists sockets with their owning process. Poll it once a second for a day and write the results to a file:
ss -tunapH state established | awk '{print strftime("%FT%T"), $0}' >> egress-sample.log
Run that in a loop (a small systemd service or watch-style loop with sleep 1). Notes:
- As a normal user you only see process names for your own processes.
ss -eadds the owning cgroup or unit, which identifies root daemons too. - For containers, read each container's
/proc/<pid>/net/tcp(andudp,tcp6). - Keep the poller light: a low
CPUQuotais enough. - Very short connections can slip between samples and show up only as TIME-WAIT with no owner. Match them to a nearby connection to the same address, and mark those matches as inferred.
2. Turn addresses into names. You rarely have SNI without intercepting TLS, and you should not intercept your own traffic just for this. Instead, resolve the hostnames your programs are configured to use, and match addresses against that list and against published provider prefixes. Fall back to reverse DNS. The result is a table of program, destination, frequency.
3. Sweep the code and config statically. Grep installed applications and their dependencies for outbound hosts and telemetry switches:
grep -rIn -E 'telemetry|analytics|sentry|check[_-]?update|version[_-]?check' --include='*.py' --include='*.js' --include='*.json' --include='*.toml' <app-dir> | head -100
Also list hard-coded external hostnames and compare them with what the live sampling saw. Anything in one list and not the other is worth a closer look.
4. Classify each finding. Expected (a tunnel to your own provider, your VPN control plane). Review (it works as a feature but you did not choose it). Unexpected (nothing you asked for). Then fix, in that order of priority, and re-measure.
What we saw on our own machines, by category
- Update and version checks in package managers, GUI tools and CLIs (PyPI, GitHub, vendor sites). They leak your IP and tool versions.
- Analytics SDKs pulled in by dependencies, including ones you never installed directly, for example a vision library in an image-generation plugin.
- Frontend error reporters and registry lookups that run in your browser when you open a local web UI, and can send installed-plugin lists.
- Hub and model-registry revalidation that attaches your access token to every request.
- Alerts sent to a public topic on a hosted notification service. Anyone who knows the topic name can read them.
- Scheduled jobs and cron wrappers that start a tool with a different settings file than your interactive one, silently dropping your privacy settings.
Opt-out switches for common tools
These are vendor or community-documented switches. Rows marked checked were confirmed against the vendor documentation on 2026-10-31. Rows marked not re-checked come from our own notes and tool sources, so confirm them against your version before relying on them. Test, and keep a note of what you changed.
| Tool | Setting |
|---|---|
| Next.js (dev) | NEXT_TELEMETRY_DISABLED=1 (checked) |
| Gradio apps | GRADIO_ANALYTICS_ENABLED=False (checked) |
| Hugging Face Hub | HF_HUB_DISABLE_TELEMETRY=1; HF_HUB_OFFLINE=1 only once your model cache is complete (checked) |
| Many CLIs | DO_NOT_TRACK=1 (checked for the Hugging Face ecosystem only; other tools vary) |
| Ultralytics (YOLO) | YOLO_OFFLINE=true, and sync: false in its settings file (not re-checked) |
| Claude Code | DISABLE_TELEMETRY, DISABLE_ERROR_REPORTING, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, DISABLE_AUTOUPDATER set to 1 (and make sure cron or restricted-mode runs actually load them) (not re-checked) |
| Tailscale | --no-logs-no-support (or TS_NO_LOGS_NO_SUPPORT) turns off client logging (checked; Tailscale warns it may limit support, and on macOS only the open-source tailscaled supports the flag) |
| Ubuntu | ENABLED=0 in /etc/default/motd-news; pro config set apt_news=false (not re-checked) |
| opencode | OPENCODE_DISABLE_MODELS_FETCH=1 (not re-checked) |
| Lightning SDK | LIGHTNING_DISABLE_VERSION_CHECK=1 (not re-checked) |
Services that keep their config in the app (ComfyUI's manager network mode, Open WebUI's community sharing and update checks, a browser's Privacy settings) need the in-app toggle instead. Where a tool has no switch at all, the options are a patch, a firewall rule, or a different tool.
What to do now
- Run the
sssampler for 24 hours on your main workstation or server. - Build the program-to-destination table and mark each row expected, review or unexpected.
- Apply the opt-outs for the "unexpected" rows. Put them in the systemd unit or pm2 ecosystem file, not just your shell profile, because services do not read your profile.
- Move alerts and logs that contain internal labels to a self-hosted or private channel.
- Re-run the sampler and confirm the rows are gone.
How to verify
- Compare the before and after tables: no connections to the destinations you opted out of.
- For a single tool, run it in a throwaway profile under
strace -f -e trace=connectand confirm there are no unexpected non-loopback connects. Only runstraceon programs you own. - Check the effective environment of a running service:
tr '\0' '\n' < /proc/<pid>/environ | grep -i -E 'telemetry|analytics|offline'.
Honest limits: sampling misses very short connections and some UDP. A quiet day shows quiet behaviour. Updates, crashes and first-run flows only appear when they happen, so combine the live data with the static sweep.
Quick answers
How do I see which programs connect to the internet on my own machine?
Sample established connections with ss over a day, map each connection to its program, and mark each destination as expected, review or unexpected. The post has the exact method.
Does setting an opt-out in my shell profile stop a service phoning home?
Usually not. Services don't read your shell profile, so put the setting in the systemd unit or the pm2 ecosystem file.
How can I check the opt-out actually applied?
Read the running service's environment with tr '\0' '\n' < /proc/<pid>/environ, then re-run the sampler and compare the before and after tables.
What are the limits of this method?
Sampling misses very short connections and some UDP, and update or crash flows only show up when they happen. Combine live sampling with the static sweep.
Related in this series: hardening your tailnet; the Brotli CVE-2026-111497 patch guide; the Linux TCP and Chrome CVE guide.
Sources
- Documentation for each tool above (Next.js telemetry, Gradio analytics, Hugging Face Hub environment variables, Ultralytics settings, Claude Code settings, Tailscale
tailscaledflags). man ss,man procfor/proc/<pid>/net/tcp.

Comments (0)
Loading comments...