#Privacy, security and limits
#Files Hostwatch writes
Hostwatch keeps what it learns on your Mac. Nothing is sent anywhere, with three exceptions:
- what you send to an AI you chose
- a daily anonymous update check, if it is on
- the daily threat-list downloads, if they are on
Here is where everything lives:
| Where | What |
|---|---|
~/Library/Application Support/Hostwatch/ | patterns (signatures-v2.json), stories, domains, rules, signing facts, collector.token (0600), claude-guard.json, firewall-watch.json |
…/Hostwatch/intel/ | threat lists, state.json, hits.json |
…/Hostwatch/ask-claude/ | the empty working folder Claude runs in |
~/Library/Logs/Hostwatch/ | agent.log (collector) and the History folder (by default): activity-* events, metrics-* resource samples, processes-* process lists |
~/Library/Preferences/local.hostwatch.app.plist | settings and exclusions |
#Security notes
Part of Hostwatch runs as root, and it sees command lines that can contain passwords. These notes explain how it keeps that power and that data from being misused.
Who runs with which rights
- The HUD: runs as the logged-in user. Only the collector is elevated.
- The collector: it is the app's own binary, run as root (
--collector-launcher … --collector). Keep the app somewhere only you can write to, for example/Applications. Otherwise anything that can modify it gains root on the next start. - Hardened runtime: the app is signed with
--options runtime. Ad-hoc builds are too, andbuild-app.shrefuses to finish without it.- macOS then ignores
DYLD_INSERT_LIBRARIESand similar variables, and loads only Apple-signed libraries into Hostwatch. - So code can't be injected into the app, the root collector or the MCP server.
- Verified: a test library injected into a build without the flag ran. With the flag, it doesn't.
- macOS then ignores
The collector channel
The app and the collector talk over a Unix socket, /var/run/hostwatch/collector.sock, never over a network port. A Unix socket is a local file-like connection point that only processes on this Mac can reach.
- The folder: root creates it with mode 0755. The collector refuses a folder it doesn't own, so no other process can create or replace the socket. A leftover socket is replaced. Any other file is not.
- The socket: it belongs to the client user, with mode 0600. Other users cannot connect.
- Every connection is checked: the collector asks the kernel who is connecting (peer credentials,
LOCAL_PEERPIDandLOCAL_PEERCRED).- When the app launched it, only that app process and user are accepted.
- When started by hand, only the
sudouser is accepted. - Rejections are logged with the process path.
- The app checks too: it connects only when the socket is a socket owned by you, mode 0600, not a link, and in a root-owned folder that nobody else can write. Otherwise it refuses and the HUD says why. So a fake collector run by an ordinary user cannot feed it data.
- Shutdown: the shutdown command also needs the per-launch token from
collector.token(mode 0600). - Client config: config lines sent by clients are only used as filters. The agent runs no commands based on them.
Secrets in command lines
New-process events carry the command line. It is forwarded to the app, shown, given to Claude and written to history. So Hostwatch masks secrets first, with one rule set (Secrets). It masks:
- Authorization and API-key headers, and cookies
name=valueand--name valuefor password, token, secret, API/access/private key, auth, session and signature, includingGITHUB_TOKEN=,?token=and--password- URL credentials:
user:pass@andtoken@ curl -u,security … -w,sshpass -pandmysql -p…- known token formats:
ghp_,github_pat_,glpat-,xox…,sk-,AKIA/ASIA,AIza,npm_,hf_ - JWTs and private key blocks
History shows a masked secret as ***. AI prompts show [REDACTED].
- Nothing unmasked goes to an AI: every prompt is masked again right before it's sent, whatever built it. This covers Ask AI, pattern suggestions, rule proposals and domain details, on-device or Claude. MCP output is masked too.
- Older history is masked too: once, in the background, finished history files are re-masked. Compressed ones are rewritten with a new index. Every file is also masked again before it's compressed.
- Limits: a secret in a shape no rule knows can still slip through. Environment variables are never forwarded.
What the collector can and can't do
- Network: to find connections, the collector runs
tcpdumpas root. It reads only DNS answers and TCP SYN headers, no payloads. It also reads macOS's connection statistics, read-only. Nothing is captured to disk. - Processes and files:
esloggeris a notify-only observer. It cannot deny execution.
#Reset Hostwatch
Reset lets you start over as on the first launch. Open Settings ▸ General ▸ Reset Hostwatch… and choose what to erase:
- Patterns, decisions and baseline:
~/Library/Application Support/Hostwatchand the baseline's state. That folder holds patterns, stories, domains, your rules, signing facts and the MCP token. - Settings: HUD, window, detection, AI, exclusions and history options (
local.hostwatch.appdefaults). - Event history and logs:
~/Library/Logs/Hostwatch. In a custom history folder, only Hostwatch'sactivity-*,metrics-*andprocesses-*files are erased.
Then Hostwatch:
- stops the collector
- removes the settings
- exits
- lets a small helper delete the files once the app has quit, so nothing is saved back
- opens again
Full Disk Access stays granted.
#Troubleshooting
| What you see | What to do |
|---|---|
| Nothing appears / "Collector off" | Start it from the menu bar icon. Check Help ▸ Collector Log. |
ERR_NOT_PERMITTED in the log | Full Disk Access is missing (see Built-in collector). Ad-hoc builds need the grant redone after every rebuild. |
| The firewall turned itself to Watch | Its web check through the relay failed twice. The status line in Settings ▸ Firewall says why. |
#Known limitations
- Containers: the firewall can't tell containers apart or name the program inside one. A container's connections are Docker's (or Podman's, OrbStack's…), judged per destination.
- Virtual machines: VMs with bridged or NAT networking aren't covered at all.
- Very short connections: they are caught by packet capture and by macOS's connection statistics. Only with the
nettopfallback, on a macOS without that framework, do they arrive ~2 s late, with a destination inferred from the command line. - Unconnected UDP: UDP sent without connecting (
sendto) has no destination to show. - Listening sockets: they aren't connections.
- File opens: they are high volume. The rate cap drops excess file events, so add exclusions for noisy apps.
esloggeroutput: the JSON parser is defensive but may need updates across macOS releases.