ShrineTech
Illustration of a computer with outbound network connections being inspected, for an egress and telemetry audit guide
TutorialsSecurity

Find what your software phones home: a practical egress-audit method

TutorialsSecurity

Find what your software phones home: a practical egress-audit method

In short

Many tools contact vendor servers on their own: update checks, analytics, version probes, error reporters. This post gives a method to find them on a machine you own: sample connections with ss for 24 hours, build a program-to-destination table, sweep configs statically, then apply opt-outs and re-check.

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 -e adds the owning cgroup or unit, which identifies root daemons too.
  • For containers, read each container's /proc/<pid>/net/tcp (and udp, tcp6).
  • Keep the poller light: a low CPUQuota is 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

  1. Run the ss sampler for 24 hours on your main workstation or server.
  2. Build the program-to-destination table and mark each row expected, review or unexpected.
  3. 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.
  4. Move alerts and logs that contain internal labels to a self-hosted or private channel.
  5. 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=connect and confirm there are no unexpected non-loopback connects. Only run strace on 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 tailscaled flags).
  • man ss, man proc for /proc/<pid>/net/tcp.

Comments (0)

Loading comments...

© ShrineTech LLC