SaneHosts Architecture

May 5, 2026 · View on GitHub

README · ARCHITECTURE · DEVELOPMENT · PRIVACY · SECURITY

Last updated: 2026-05-05

Purpose

SaneHosts is a native macOS app for managing /etc/hosts through curated profiles and blocklists. It provides safe activation/deactivation, backups, and DNS cache flushing without requiring Terminal usage.

Non-goals

  • No background network sync unless a user adds remote blocklist URLs.
  • No cloud accounts, no hosts/profile sync, and no upload of hosts entries or profile content.
  • Privacy-preserving product/website metrics are limited to anonymous product events and aggregate website analytics.
  • No direct editing of /etc/hosts without explicit user action.

System Context

  • Host app: SwiftUI UI + shared logic in SaneHostsPackage.
  • Privileged helper: XPC LaunchDaemon for root writes to /etc/hosts.
  • Fallback path: AppleScript with admin password if helper is unavailable.
  • Sparkle: Update checks via appcast.
  • No GitHub DMG: DMGs are hosted on Cloudflare R2, not in GitHub.

Architecture Principles

  • Prefer safe, reversible operations (backups, validation, warnings).
  • Write access is explicit and authenticated.
  • Separate system entries from user-managed entries.
  • Favor deterministic parsing and merging of hosts data.

Core Components

ComponentResponsibilityKey Files
ProfileStoreLoads/saves profiles, backups, system entriesSaneHostsPackage/.../ProfileStore.swift
HostsServiceRead/write /etc/hosts with helper + fallbackSaneHostsPackage/.../HostsService.swift
HostsParserParse/merge hosts contentSaneHostsPackage/.../HostsParser.swift
RemoteSyncServiceFetch remote blocklistsSaneHostsPackage/.../RemoteSyncService.swift
DNSServiceFlush DNS cacheSaneHostsPackage/.../DNSService.swift
AuthenticationServiceTouch ID gating for helper pathSaneHostsPackage/.../AuthenticationService.swift
Helper (XPC)Root write/flush/read operationsSaneHostsHelper/*

Data and Persistence

  • Profiles: ~/Library/Application Support/SaneHosts/Profiles/
  • Backups: ~/Library/Application Support/SaneHosts/Backups/ (max 3 per profile)
  • Blocklist cache: ~/Library/Application Support/SaneHosts/BlocklistCache/
  • System hosts: /etc/hosts (read and written via helper or AppleScript)

Key Flows

Profile Activation

  1. ProfileStore loads system entries + selected profile.
  2. HostsParser merges system entries + profile entries.
  3. HostsService writes /etc/hosts (helper first, AppleScript fallback).
  4. DNSService flushes cache; warning returned if flush fails.

Profile Deactivation

  1. ProfileStore keeps only system entries.
  2. HostsService writes system-only content to /etc/hosts.
  3. DNS cache is flushed.

Remote Blocklist Import

  1. RemoteSyncService fetches URLs.
  2. Parser normalizes and deduplicates entries.
  3. ProfileStore updates the profile and persists it.

State Machines

Profile Activation (Write Strategy)

stateDiagram-v2
  [*] --> Preparing
  Preparing --> HelperCheck
  HelperCheck --> HelperAuth: helper available
  HelperCheck --> AppleScript: helper unavailable
  HelperAuth --> HelperWrite: auth success
  HelperAuth --> Failed: auth cancelled/failed
  HelperWrite --> FlushDNS: write success
  AppleScript --> FlushDNS: write success
  AppleScript --> Failed: write failed
  FlushDNS --> Completed
  FlushDNS --> CompletedWithWarning: flush failed
StateMeaningEntryExit
PreparingBuild merged hosts contentactivateProfile()helper check
HelperCheckDetect helper availabilityHostsServicehelper auth / AppleScript
HelperAuthTouch ID authenticationAuthenticationServicewrite or fail
HelperWriteXPC helper writes fileHostsHelperConnectionflush DNS
AppleScriptAdmin password writeHostsServiceflush DNS / fail
FlushDNSAttempt DNS flushDNSServicecompleted
CompletedHosts updated successfullyflush okidle
CompletedWithWarningHosts updated, flush failedflush erroridle
FailedWrite failure or auth cancellederroridle

Profile Load

stateDiagram-v2
  [*] --> Loading
  Loading --> SystemLoaded: read /etc/hosts
  SystemLoaded --> ProfilesLoaded: read profiles
  ProfilesLoaded --> Ready
  Loading --> Failed
StateMeaningEntryExit
LoadingStart loadProfileStore.load()system load
SystemLoadedSystem entries parsedparseSystemHosts()profiles load
ProfilesLoadedProfiles read from diskloadProfiles()ready
ReadyUI can renderload() completeidle
FailedLoad error recordederroridle

Permissions and Privacy

  • Helper path uses Touch ID and privileged XPC (LaunchDaemon).
  • AppleScript fallback prompts for admin password.
  • Network access only for update checks, anonymous product events, checkout/license delivery, website analytics, and user-chosen blocklist/protection-level fetches.

Build and Release Truth

  • Single source of truth: .saneprocess in the project root.
  • Build/test: ./scripts/SaneMaster.rb verify (no raw xcodebuild).
  • Release: ./scripts/SaneMaster.rb release (delegates to SaneProcess release.sh).
  • DMGs: uploaded to Cloudflare R2 (not committed to GitHub).
  • Appcast: Sparkle reads SUFeedURL from SaneHosts/Info.plist (sanehosts.com).

Testing Strategy

  • Unit tests in SaneHostsPackage/Tests/.
  • Use ./scripts/SaneMaster.rb verify (workspace-aware).

Risks and Tradeoffs

  • Hosts file writes are sensitive; failures must be surfaced clearly.
  • Helper availability or auth failures can block activation.
  • Large blocklists can impact load/merge time; caching is required.