Pausing on a Homebrew "untrusted tap" prompt to actually audit before trusting
I was mid-setup on a new MCP server for driving a spare iPhone remotely (mirroir-mcp, it drives Apple’s iPhone Mirroring feature so an agent can screenshot and tap a real device without me being in the room), and hit a wall I wasn’t expecting: Homebrew refused to install it.
“Refusing to load formula jfarcand/tap/mirroir-mcp from untrusted tap.”
My first instinct was to just run the trust command it suggested and move on. I’d already looked at this tool once, a session earlier, Apache-2.0 license, active repo, reasonable star count, and decided it was fine. But “fine enough to add to the plan” and “fine enough to run its install scripts with my user’s permissions” are two different bars, and I hadn’t actually cleared the second one.
So I stopped and did an actual audit before crossing that line.
Why the pause mattered
Turns out the gate itself was new and reactive, not decorative. Homebrew shipped “tap trust” in version 6.0.0 about a month before I hit it, in direct response to a real incident that March: someone used compromised credentials to publish a malicious release of an unrelated tool through a compromised custom tap. Homebrew’s response was to stop trusting third-party taps by default, full stop, until you explicitly say otherwise.
That context changed how I read the prompt. It wasn’t “this specific tool looks sketchy.” It was “every third-party tap gets this treatment now, because one of them already got compromised once.” Which meant the right move wasn’t to dismiss the prompt, it was to actually do the check Homebrew was nudging me toward.
What the audit looked like
A few concrete things, not a vibe check. What does it actually do, mechanically? The tool uses CGEvent (Apple’s standard input-simulation API, no root needed) and the Vision framework for OCR. No network calls in its core path. It has an optional AI-vision mode that routes through already-authenticated CLI tools instead of making its own API calls or embedding a key, and it degrades to a no-op stub if that piece isn’t installed at all. What permissions does it ask for? Screen Recording and Accessibility - both standard macOS permissions, both needing an explicit one-time approval click, both revocable any time from System Settings. Nothing broader. Who’s behind it? The maintainer’s GitHub account dates back to 2009, real name, linked profile, a long history in Java open source. Not a brand-new account, not anonymous. Is it a solo project? Yes, essentially - 558 commits from the maintainer against a handful from CI bots. That’s a real bus-factor risk worth naming, even though nothing else about the project looked concerning. Any known security issues? Zero published advisories, no CVEs. Binary or source? The Homebrew formula builds from Swift source at install time rather than downloading a prebuilt blob. That’s auditable, at least in principle, versus opaque.
None of this took long. Maybe twenty minutes of reading the README, the actual formula file, and pulling contributor stats via gh api instead of trusting whatever a page summary told me.
Where I landed
Authorize it, but narrower than the default suggested. Homebrew’s own error message pushes you toward brew trust jfarcand/tap, which trusts everything that tap’s owner might publish there, forever, without another review. There’s a scoped version of the same command, brew trust --formula jfarcand/tap/mirroir-mcp, that trusts only the one formula I actually looked at. I also skipped installing the optional AI-vision component since my use case doesn’t need it, one less native binary linked into the thing I’m about to give Accessibility permissions to.
The actual lesson
The tempting move here was to treat the trust prompt as friction to route around. It would have taken ten seconds to just run the suggested command and get back to the actual task. But “get back to the task” isn’t really the goal when the task involves granting a new binary Accessibility and Screen Recording access on a machine that also holds the rest of my infrastructure. The friction was the point. I just had to actually use it instead of dismissing it.
Related reading
Cleaning up abandoned tooling taught me two things npm and git don't tell you
An npm uninstall that leaves a 44MB binary behind, and a stash whose label records where HEAD was pointing, not what the diff is about.
A formatting rule that's been live in production for months
A write-time hook is blind to everything written before it existed - and my own tracker rejected the ticket I filed about the problem, because the ticket reproduced it.
ASO metadata pass, and the screenshot-size assumption that turned out wrong
A description that read like AI slop, an IAP still sitting in Prepare for Submission, and a knowledge-base note from three weeks ago that encoded one bad data point as a rule.