Burrow is a GUI that ships its own MIT engine, a fork of the
mo (Mole) CLI. Starting with 0.11.0, official
releases must pass Developer ID signing and Apple notarization before they can
publish. This page is the honest account of what
the app does, what touches the network, and how it handles admin rights, so you
can decide before you run it. The actual cleaning/scanning is done by that
bundled engine (MIT, © tw93 for the original); audit it too.
The tag-release workflow fails closed unless it can sign the app and every bundled executable with a Developer ID Application certificate, enable the hardened runtime, obtain secure timestamps, receive an accepted notarization result, staple the ticket, and pass Gatekeeper assessment. The external Homebrew cask is updated only after that verified artifact exists, and its live 0.11.1 definition preserves quarantine so Gatekeeper can verify the stapled ticket. Maintainer setup and release verification are in the macOS signing runbook.
The published 0.11.1 app was independently checked after download: its bundle
identifier is dev.caezium.Burrow, its Developer ID Team ID is YGSM2722TZ,
the hardened runtime and secure timestamp are present, stapler validate
succeeds, and Gatekeeper reports source=Notarized Developer ID.
The release also embeds Sparkle 2.9.4 with a checked-in Ed25519 public key.
After notarization, CI signs the update ZIP and appcast.xml, verifies both
signatures and the private/public key match, and keeps a new GitHub release in
draft until both assets exist. Sparkle verifies the signed feed and archive
again on the Mac before installing.
Version 0.11.0 was the first Sparkle-enabled release. The first real successor test then updated the installed signed app from 0.11.0 build 21 to 0.11.1 build 22 through Sparkle's native UI, without Terminal or Homebrew, and the relaunched copy passed strict signing, stapler, and Gatekeeper checks. Issue #281 was closed after that end-to-end verification.
Burrow 0.10.5 predates this release gate and used a coherent ad-hoc signature, not Developer ID or notarization; its Homebrew cask used a quarantine bypass. Older archives also predate the current distribution guarantee. Locally built copies still use an ad-hoc signature so macOS can bind Full Disk Access to a coherent development identity, but that is not a substitute for Developer ID or notarization and changes between builds.
This is the part people rightly scrutinize in cleaners. Burrow's model:
- Burrow installs no privileged/background helper and no XPC root service. There is nothing persistently running as root and nothing for another local process to connect to.
- When Clean or Optimize needs admin rights, macOS's own
authorization dialog asks for your password, and Burrow runs the
matching
mocommand for that single action, then exits. You see and approve every elevation. (SeeCommandRunner.runElevatedinmacos/Sources/TaskReport.swift.) - Honest caveat: official builds elevate the engine sealed inside the
Developer ID signed app. A source build can fall back to an external
mo; if it does, that executable is only as trustworthy as its install location (Homebrew prefixes are normally user-writable). Review the engine and the elevation path before granting admin, or skip the admin-only system caches.
- No account, no sign-in, no ads, no "upgrade to Pro." Your metrics, history, and file contents stay on the Mac — with one opt-in exception: pointing the optional AI "Explain" lens at a hosted endpoint sends the metrics fact sheet you're explaining to that endpoint (it's off by default and local-first; see below).
- Anonymous analytics + diagnostics (opt-out). Burrow uses
PostHog for product analytics and
Sentry for crash, hang, release-health, and sampled
performance diagnostics, so we can see how many
installs stay active, which versions to support, which features get used,
and where a launch or update stopped. What's sent: two random install ids
(one used by Burrow for PostHog and one by Sentry, neither derived from
hardware, a serial, or an account), the app version, exact macOS
version/build, CPU architecture, locale, fixed semantic
screen/feature/update events, crash stacks, and sampled fixed-name launch
spans/profiles. PostHog sizes, counts, and durations are bucketed into
ranges. What's never sent: screenshots, screen recordings, file names,
file contents, user paths, URLs, your metrics/history, or any account
identity. Crash events remove path-bearing image/frame fields; profiling is
limited to apps under
/Applicationsbecause profile envelopes bypass that event scrubber. Automatic network/file tracing is disabled. Your IP isn't stored — PostHog events carry$ip = "0"(and the project discards client IPs), and Sentry setssendDefaultPii = false. It's on by default; turn it off in Settings → Anonymous usage and both pipelines stop. The exact event list is in TELEMETRY.md; the client code ismacos/Sources/Telemetry.swiftandmacos/Sources/CrashReporter.swift. Both integrations are inert in source/dev builds — keys are injected only at release time, so a build from this repo phones neither home. The Windows app does the same thing (opt-out via Settings → Share crash reports & analytics) — its own separate Sentry project, and the shared macOS PostHog project taggedplatform: "windows"; client code iswindows/Services/AppTelemetry.cs, keys injected viaBURROWWIN_SENTRY_DSN/BURROWWIN_POSTHOG_API_KEY— see TELEMETRY.md. - The public website uses separate cookieless analytics. On the exact
burrow.henryzh.devproduction host, PostHog records page views/leaves, scroll depth, CLS/INP/LCP, and fixed download/Homebrew-copy events. It sets no browser identifier in client storage, creates no person profile, strips URL queries and fragments, honors Do Not Track, and disables replay, generic click capture, exceptions, console/network capture, surveys, and flags. Requests go directly to PostHog so content blockers remain effective; the exact fields and server-side daily-hash behavior are in TELEMETRY.md. - Telemetry stays off the UI thread. macOS PostHog delivery uses Burrow's own serial background transport rather than the SDK timer that previously ran on AppKit's main run loop. A local 64-event sanitized outbox retries one historical event at a time with bounded backoff, discards permanent HTTP rejections, and runs only while telemetry is enabled; an opted-out launch does not read it or contact PostHog.
- Local launch recovery journal. Burrow atomically stores a coarse launch phase plus app/OS versions under Application Support so it can avoid a status-item path that failed on the same macOS build. This local safety file contains no user content or hardware/account ID and is written even when telemetry is off; only redacted, run-ID-free fields are reported when the telemetry switch is on.
- Local-only surfaces:
- The MCP HTTP query server binds
127.0.0.1:9277(loopback only; on by default). It serves your local metrics to local MCP clients; it is not reachable off-device, and it sends no CORS grant, so web pages in your browser can't read it either. In the Windows preview, the Settings toggle disables REST endpoints but keeps the loopback/mcproute bound so the stdio bridge can continue to work. - The stdio MCP server (
Burrow --mcp) is a local subprocess. - History is a local SQLite file under
~/Library/Application Support/Burrow/.
- The MCP HTTP query server binds
- Other outbound paths:
- Burrow self-update check: when "Check for updates automatically" is on
(Settings → About, on by default), Sparkle makes an unauthenticated GET to
the signed
appcast.xmlGitHub Release asset after startup settles and about once a day. Automatic Sparkle startup waits until the menu-bar item has remained responsive for 30 seconds and then gets its own durable stability phase; an interrupted updater start pauses later automatic checks for that app/OS build while manual checks stay available. It sends no Burrow analytics or device profile. If an update exists, Sparkle presents its native UI and waits for approval before downloading or installing it. Fixed-name check/download/install milestones are sent through the same opt-out telemetry switch, without request URLs or a Sparkle device profile. Turn the toggle off to make checks fully manual; the menu and Settings buttons still work. - The Software → Updates tab runs
brew outdated, which contacts Homebrew's update feeds — the same checkbrewdoes for itself. It reads version info; it sends nothing about you. App version checks (Sparkle appcasts, App Store lookups) still happen only when you click "Check for updates". - The engine inside
Burrow.appnever self-updates because changing a file inside the bundle would invalidate its Developer ID seal. It updates only with a signed Burrow release. Source builds using an external engine keep a user-initiated engine updater in Settings. - The optional AI "Explain" lens (off by default) talks to
127.0.0.1(Ollama / LM Studio). If you configure a hosted OpenAI-compatible endpoint instead, the metrics summary being explained is sent to that endpoint with your API key.
- Burrow self-update check: when "Check for updates automatically" is on
(Settings → About, on by default), Sparkle makes an unauthenticated GET to
the signed
Open a GitHub issue or a private security advisory on the repo. Because Burrow can run privileged cleanup, security reports are taken seriously — please include the file and line if you can.