A high-level orientation. The user-facing pitch is in README.md; the source-tree map is in privacycommand/README.md. This doc sits between them — what the boxes are, why they're separate, and how data moves between them.
The deeper design docs referenced from the project README.md
One-line model. A SwiftUI app drops a
.appbundle onto a pure-Swift analyzer library, optionally launches the inspected app under a privileged XPC helper for dynamic monitoring, and optionally ships a guest agent into a macOS VM to do the same work in isolation.
Maturity note. Despite the project being six commits old, the codebase is substantial: ~26k LOC of Swift across ~100 files. The analyzer (
Sources/privacycommandCore/Analysis/) has 29 detector files; monitoring has 11; the app target has 59 SwiftUI files. The code is largely there; the docs aren't. This file is part of fixing that.
┌──────────────────────────────────────────────────────────────────────┐
│ privacycommand.app │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ privacycommand │ │ privacycommandCore │ pure Swift, │
│ │ (SwiftUI + AppKit)│───▶│ (analyzer) │ no AppKit │
│ └─────────┬──────────┘ └────────────────────┘ │
│ │ ▲ │
│ │ XPC │ analyze(bundleAt:) │
│ ▼ │ │
│ ┌──────────────────────┐ │ │
│ │ privacycommandHelper │ privileged: fs_usage, sfltool, │
│ │ (root, SMAppService) │ pfctl. Validates clients by Team ID. │
│ └──────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
┌──────────────────────────┐ ┌────────────────────────┐
│ auditctl (CLI) │ │ privacycommandGuestAgent│
│ smallest end-to-end smoke│ │ runs in a macOS VM, │
│ test for the analyzer │ │ ships observations back│
│ │ │ via shared protocol │
└──────────────────────────┘ └────────────────────────┘
▲ ▲
└────── Sources/privacycommandGuestProtocol/ ────┘
(zero-dep wire format)
Each target is intentional:
| Target | Path | Role |
|---|---|---|
privacycommandCore |
privacycommand/Sources/privacycommandCore/ |
Pure-Swift analyzer. AppKit-free. Runs from CLI, tests, GUI, and helper without dragging UI deps into builds that don't need them. |
privacycommand (app) |
privacycommand/Sources/privacycommand/ |
SwiftUI app target. Views + view-models only. |
privacycommandHelper |
privacycommand/Sources/privacycommandHelper/ |
Privileged XPC service installed via SMAppService.daemon. Minimal API surface — currently 4 Swift files (main, HelperToolService, CodeSignValidator, FsUsageRunner). The source-tree README also references PfctlKillSwitch.swift for the network kill switch, but that file isn't committed yet. Validates clients by Team ID on connect. |
privacycommandGuestProtocol |
privacycommand/Sources/privacycommandGuestProtocol/ |
Wire format shared between host and guest agent. Lives in its own zero-dependency target so the agent can build without compiling Core. |
privacycommandGuestAgent |
privacycommand/Sources/privacycommandGuestAgent/ |
The binary that runs inside a macOS VM and ships observations back to the host. |
auditctl |
privacycommand/Sources/auditctl/ |
Tiny CLI that calls StaticAnalyzer().analyze(bundleAt:) and pretty-prints the result. Fastest smoke test you can write. |
The split is enforced by Swift Package Manager — Package.swift declares each as a separate target with an explicit dependency graph. You can't accidentally pull AppKit into Core because Core's manifest doesn't depend on it.
Three layers of signal, each with a different cost:
| Layer | Where | Privilege |
|---|---|---|
| Static — entitlements, code-signing, notarization (stapler/spctl/SHA-256), URL schemes, document types, hard-coded domains, embedded launch agents, third-party SDK fingerprints (LaunchDarkly, Firebase, Mixpanel, AdMob, …), feature flags / trial-state strings, secrets and license-key names, anti-analysis signals, dylib hijacking surface, Privacy Manifest cross-check | Sources/privacycommandCore/Analysis/ (29 detector files: StaticAnalyzer, EntitlementsReader, MachOInspector, BundleSigningAuditor, NotarizationDeepDive, SDKFingerprintDetector, SecretsScanner, RPathAuditor, AntiAnalysisDetector, PrivacyManifestReader, …) |
None. Runs on the user's data without ever touching Apple-granted entitlements. |
| Dynamic — file events, network destinations, child processes, pasteboard / camera / microphone / screen-recording activity, USB device interactions, resource usage | Sources/privacycommandCore/Monitoring/ (11 files: DynamicMonitor, LiveProbeMonitor, NetworkMonitor, ProcessTracker, USBDeviceMonitor, ResourceMonitor, DeviceUsageProbe, VMHostDetection, GuestObservationStream, …) |
Helper required for fs_usage-based file events; Background Task Management audit also goes via the helper to skip the admin prompt. |
App Store cross-reference — Mac App Store privacy labels fetched from apps.apple.com, displayed next to the static-analysis findings |
Sources/privacycommandCore/Analysis/AppStoreLookup.swift + AppStorePrivacyLabelFetcher.swift |
None. Network call is keyed on bundle ID, never user data. |
The privacy-stance contract: all analysis runs locally. The inspected app's contents never leave the machine. The only outbound traffic is bounded — DNS reverse lookups for destinations the inspected app contacts, App Store privacy-label lookups against itunes.apple.com/apps.apple.com, and Sparkle appcast fetch from privacykey.github.io.
User drags App.app onto privacycommand
│
▼
ContentView → AnalysisCoordinator (view-model)
│
▼
StaticAnalyzer().analyze(bundleAt:) ─── runs purely in-process
│
▼
StaticReport (Codable) ─── feeds Dashboard, Static, Telemetry, Background-tasks tabs
│
▼
[Optional] Dynamic monitoring [Optional] VM mode
│ │
▼ ▼
HelperToolService over XPC Guest agent in VM
├── FsUsageRunner (file events) ├── runs same analyzer locally
├── BackgroundTaskAuditor (sfltool) └── ships observations via
└── pf-anchor kill switch (planned — privacycommandGuestProtocol
referenced in source-tree README
as PfctlKillSwitch.swift but not
yet committed; see WIP doc)
│
▼
Live observations stream into the Monitoring tab
│
▼
Reporting (JSON / HTML / PDF) ─── Sources/privacycommandCore/Reporting/
Two distinct entitlement surfaces, both deliberately small.
App (Resources/privacycommand.entitlements):
- App Sandbox: OFF — the app launches arbitrary inspected apps and shells out.
- Hardened Runtime: ON.
com.apple.security.network.client: ON — DNS reverse lookups, App Store privacy-label fetches, Sparkle appcast.- No
allow-jit, nodisable-library-validation, no Apple-granted entitlements.
Helper (Resources/privacycommandHelper.entitlements):
- App Sandbox: OFF — must spawn
/usr/bin/fs_usage,/usr/bin/sfltool,/sbin/pfctl. - Hardened Runtime: ON.
com.apple.developer.service-management.managed-by-main-app: ON — required forSMAppService.daemonlifecycle.
The helper rejects clients whose Team ID doesn't match its own — see CodeSignValidator.swift. This is the load-bearing piece of the trust model: the helper runs as root, accepts XPC connections only from binaries signed with the same Team ID.
privacycommand can analyze apps you'd rather not run on your bare-metal machine by booting a macOS VM (VirtualBuddy, UTM, or Parallels), installing the guest agent inside, and treating the VM as a remote worker.
- Wire format:
privacycommandGuestProtocol/GuestProtocol.swift— a singleCodableschema both sides import. - Host side:
privacycommandGUI ships requests across the VM boundary and renders observations as they come in. - Guest side:
privacycommandGuestAgent/main.swiftlistens for commands, runs the sameprivacycommandCoreanalyzer locally inside the VM, and replies. - Walkthroughs per VM platform:
privacycommand/docs/GUEST_AGENT.md.
The VM mode reuses the analyzer; it doesn't reimplement it. Hardened-runtime entitlements landed on the guest helper today (commit e48fee6) — that's what unlocks distributing it as a signed binary.
Every finding is exportable as JSON, HTML, or PDF. Live in Sources/privacycommandCore/Reporting/. The PDF exporter is the one place AppKit creeps into otherwise-pure code, so it lives in the app target (privacycommand/Sources/privacycommand/Reporting/), not Core. JSON and HTML export are AppKit-free and live in Core.
Run reports are persisted on disk for diffing across audits — Sources/privacycommandCore/Persistence/. The format is the canonical Codable types from privacycommandCore/Models/ (e.g., StaticReport, AppStoreInfo). Run search index lives alongside in Sources/privacycommandCore/Search/.
| Channel | How updates work |
|---|---|
| Direct download | DMG with Sparkle 2 in-app updater. Auto-checks off by default; user opts in via Settings → Updates. |
| Homebrew cask | brew upgrade --cask privacycommand. privacycommand detects Cask installs and disables Sparkle's installer to stay out of brew's way — see Sources/privacycommandCore/Updates/. |
The appcast feed lives on gh-pages at https://privacykey.github.io/privacycommand/appcast.xml, signed with EdDSA. The Sparkle keypair is per-app, never shared with another product — leaking one shouldn't compromise another product's update channel. Full release flow in docs/RELEASES.md.
Sources/privacycommandCore/KnowledgeBase/ is intended as the single source of truth for "what does this finding mean?" — the goal is that every detector has a paired KB entry with a plain-English explanation. Today the directory contains a single Swift file (PrivacyKeyDatabase.swift actually lives in Analysis/, alongside the detectors that read from it) — most detector explanations are inline string constants today rather than centralised entries.
Tests/privacycommandCoreTests/ holds the analyzer test suite. Run with swift test from privacycommand/. Static analysis is straightforward to test against a corpus of .app bundles; dynamic monitoring needs more thought (the helper-required tests can't run in CI without SMAppService permissions).
The smallest end-to-end smoke test is auditctl /System/Applications/Calculator.app — exits non-zero if StaticAnalyzer fails to parse anything.
| Question | Doc |
|---|---|
| Source-tree map (contributor-facing) | privacycommand/README.md |
| Privileged helper bundling + signing + verification | privacycommand/HELPER.md |
| Guest agent walkthroughs | privacycommand/docs/GUEST_AGENT.md |
| Build workflow (Xcode + SPM) | privacycommand/BUILDING.md |
| Release pipeline + secrets | docs/RELEASES.md |
Last reviewed: 29 April 2026.