Releases: mangetoncompost/torshield
Release list
v0.8.2
v0.8.1
Notifications
System notifications via macOS notification center (no plugin, no permission required):
- Connected - fires after Tor bootstraps successfully, shows the exit node IP
- Connection Failed - fires if Tor does not start within 30s (Tor not installed, network blocked)
- Disconnected - fires after all protections are torn down
- New Identity - fires after manual identity rotation, shows new exit node IP
- Identity Rotated - fires on automatic rotation (5/15/30 min), shows new exit node IP
v0.8.0
Security
Critical
- dnsmasq config moved to
/etc/dnsmasq-torshield.conf(root:wheel) via new ts_helper verbswrite-dnsmasq-conf/rm-dnsmasq-conf. Previously written to~/.config/opsec/dnsmasq.conf(user-writable), allowing a local attacker to inject arbitrary dnsmasq directives (dhcp-script,addn-hosts) executed as root on next connect. gen_iconbinary always recompiled from embedded source, never reused from cache. Previously the compiled binary persisted in~/.config/opsec/gen_iconwithout integrity verification - binary planting attack.
High
- TOCTOU in
ensure_helper()closed: compile inside atempdir()(mode 0700) that stays alive until the osascriptcpcompletes. Previously theNamedTempFilewas dropped before clang ran, releasing the path reservation without O_EXCL protection. - dnsmasq killed via validated PID from pid-file (
/bin/kill <pid>), notpkill -f <user-path>. The previous pattern matched all process cmdlines as root; theroot("kill", ...)call was also silently ignored (bare name not in ts_helper whitelist). - Watchdog verified by content comparison before skipping reinstall. File-existence check alone allowed a replaced watchdog LaunchDaemon script (persistent root shell) to persist undetected.
Medium
~/.config/opsec/restricted to mode 0700 (was 0755 - world-readable). All secrets (torrc, HMAC key, SAFECOOKIE cookie, hostname backups) now inaccessible to other local users.HOMEunset fallback usesgetpwuid(getuid())instead of/tmp(world-listable).- Tor binary resolved via absolute path (
/opt/homebrew/bin/torfirst). Previously relied on PATH - a rogue binary earlier in PATH could intercept traffic whiletor_ready()returned true.
Low
- pf interface name validated (alphanumeric only, default
en0).
UI
Menu redesigned to professional English labels: Connect / Disconnect, New Identity, Identity Rotation, Kill Switch, MAC Address Randomization, DNS Leak Protection, Fingerprint Resistance, Advanced (was Dev / Scripts), [OK] / [!] dependency status.
v0.7.0
Second security audit. All findings confirmed with reference sources before implementation.
Critical
- pf kill switch rule order fixed:
block allwas beforepassrules - withquick, pass was never reached (man pf.conf). Tor could not connect to relays; the kill switch was silently broken. - pf anchor file now written via ts_helper (new
write-pf-anchorverb with O_NOFOLLOW):/etc/pf.anchors/is root:wheel 755, direct write was silently failing, no rules were ever loaded.
High
- Removed
pass in quick tcp: accepted externally-initiated connections through the kill switch. Stateful tracking handles return packets automatically. - Tor relay ports 443, 9001, 80 added to pf pass rules: only 9050 (local SOCKS) was allowed, Tor could not bootstrap.
/bin/killabsolute path in ts_helper:execv("kill")fails with ENOENT (execv does not search PATH). dnsmasq was never killed by PID.hex_decode()panic fixed: odd-length input from a malformed Tor response crashed TorShield, triggering the watchdog to flush pf and expose the real IP.- IPv6 disabled before MAC spoof:
ifconfig down/uptriggers NDP Router Solicitations that may expose the fe80:: link-local (RFC 4861 s.6.3.7).
Medium
- No outbound request at startup:
reqwest::no_proxy()was sending the real IP to api.ipify.org before Tor was active. Replaced bylocal_real_ip()viaipconfig getifaddr(no network request). - ts_helper.c bundle integrity check: on-disk source compared against the copy embedded in the binary at compile time. Tampered source is discarded to prevent LPE via recompilation.
- torrc permissions set to 600: was 644, allowing local modification of ExcludeExitNodes or injection of HiddenServiceDir.
- Captive portal blocked:
captiveagentbypasses the SOCKS5 proxy (Apple system daemon).captive.apple.comblocked in/etc/hostswhen the kill switch is active. - mDNS hostname anonymized: LocalHostName/ComputerName/HostName set to neutral values at enable, restored at disable. Prevents hostname/model identification on local networks (Fingerprint.com: 65% first-name identification rate).
helper_ok()usessymlink_metadata()(lstat):metadata()follows symlinks, a symlink at ts_helper pointing to a legitimate SUID binary would have passed the check.
Firefox
- DNS prefetch disabled:
network.dns.disablePrefetchFromHTTPSdefaults to false since Firefox 127 (arkenfox #1860) - prefetch lookups bypass the SOCKS5 proxy. Addednetwork.prefetch-next,browser.send_pings,media.navigator.enabled.
v0.6.0
Security
-
SAFECOOKIE on Tor control port : authentication now uses SAFECOOKIE (spec 193, HMAC-SHA256 challenge/response) instead of plain COOKIE. The cookie file is never sent in cleartext over the TCP socket.
-
Config integrity : torshield.json is signed with HMAC-SHA256 using a key stored in the macOS Keychain. Any external alteration is detected at load time - config resets to defaults instead of being silently applied.
-
Secure PRNG : rand_bytes() replaced by getrandom::fill() which calls getentropy(2) directly on macOS. The previous clock fallback produced predictable MAC addresses on /dev/urandom failure.
-
ts_helper SUID - tee removed : /usr/bin/tee was in the SUID whitelist (GTFOBins: arbitrary root file write). Replaced by an internal write-pf-conf verb with /etc/pf.conf hardcoded in C and O_NOFOLLOW on open().
-
ensure_helper() - symlink attack : helper binary now compiled in /tmp with an unpredictable random name (O_CREAT|O_EXCL), symlink check post-compilation before elevation.
-
pf anchor - table placement : table <apple_relay> moved from anchor file into /etc/pf.conf. Tables in anchors cause silent boot failures on macOS.
-
user.js strip() : exact prefix match on user_pref("...") lines only - no longer removes comments or third-party prefs.
-
CanvasBlocker downloaded via Tor : XPI now fetched through socks5h://127.0.0.1:9050.