Skip to content

fix(macos): make TronBrowser findable and launchable on a Mac - #140

Merged
ralyodio merged 1 commit into
mainfrom
fix/macos-silicon-browser-discovery
Oct 11, 2026
Merged

ralyodio merged 1 commit into
mainfrom
fix/macos-silicon-browser-discovery

Conversation

@mrprecioustech

Copy link
Copy Markdown
Contributor

The bug

On macOS TronBrowser ships no engine of its own — it drives a Chromium.app found on disk — so failing to find one is not a degraded mode: tron exits with "runs Ungoogled Chromium ONLY" and never opens a window.

The Homebrew cask does not leave the app where we looked. It moves Chromium.app to brew's appdir and keeps only a symlink in the Caskroom, so the four /Applications and ~/Applications paths we probed can all miss an app brew installed perfectly well. Nothing looked in the Caskroom — the one path that holds whatever appdir is, and the place the two architectures actually differ (/opt/homebrew on Apple Silicon, /usr/local on Intel; we hardcoded the brew binary at both prefixes but never the Caskroom).

Worse, the installer could not tell. ensure_browser() took brew install --cask exiting 0 as success and returned without checking the app had landed anywhere the launcher looks — so install.sh printed "Installed TronBrowser" and exited 0 with no browser present. The failure then surfaced at first launch as advice to install the thing the user had just installed. Re-running the installer could not repair it either: install --cask --force is a no-op while the install receipt is intact.

Reproduced

Found on an Apple Silicon Mac (M-series, macOS 26.6) in exactly this state:

TronBrowser v3.16.1 installed
brew list --cask ungoogled-chromium reports installed (154.0.8037.97-1.1)
…/Caskroom/…/Chromium.app symlink → /Applications/Chromium.app
/Applications/Chromium.app absent
mdfind -name Chromium.app nothing on the disk

brew reinstall --cask ungoogled-chromium restores it; install --force does not.

The fix

  • launcher — probe the Caskroom after the app paths, resolving brew's prefix from brew's own location so a relocated Homebrew works, with both defaults as fallback (brew is usually not on PATH in a GUI launch). Newest version wins; a dangling symlink reads as missing. A live Caskroom path is itself proof of the ungoogled cask, so it no longer needs the brew list check — which requires brew on PATH and keeps reporting the cask installed after the .app is gone.
  • install.sh — new mac_app_path() mirroring that search; ensure_browser() verifies through it after every attempt, escalates install --force to reinstall, falls back to the direct download, and says so loudly instead of exiting 0 on a broken install. The trust marker is written only for paths that prove the build is de-googled, so a bare /Applications/Chromium.app still has to pass the brew check.
  • tron-doctor — had no macOS awareness at all (no Darwin, Applications, brew or Caskroom anywhere), so it reported "No Ungoogled Chromium is installed" with a Flatpak hint on a perfectly healthy Mac. It now lists the app bundles, reads versions from Info.plist (never by running the binary — from a shell it SIGABRTs, which is why the launcher uses open -na), names a cask whose app was deleted with brew reinstall as the fix, and suggests brew rather than Flatpak.

Three more bugs found on the way

  • download_ungoogled_macos asked for x86-64-macos.dmg; upstream publishes x86_64-macos.dmg. Verified against the GitHub API: the no-Homebrew fallback could never work on Intel.
  • brand_macos_icon only looked at Chromium.app, so icon branding silently no-opped for the direct-download path, which installs Ungoogled Chromium.app.
  • The release JSON was fetched to a fixed /tmp path; now mktemp. The .app is copied with ditto rather than cp -R — the supported way to move a bundle without disturbing the signature arm64 code must carry. (Hardening: I could not prove cp -R was damaging signatures here.)

Tests

7 new tests across launcher.test.ts and doctor.test.ts: Caskroom discovery through brew's prefix, newest-version selection, cask-path-as-proof, the dangling-symlink state, the brew reinstall hint, Info.plist version reading, and brew-not-Flatpak advice. 6 of the 7 fail against the unpatched scripts; the dangling-symlink case is a regression guard that holds either way.

TRONBROWSER_MAC_SEARCH_ROOT aims the machine-wide half of the search at a fixture so they run the same on any host, with or without its own /Applications/Chromium.app. It grants nothing TRONBROWSER_BROWSER does not already grant, and is unset in normal use.

Full desktop suite: 81 passed. eslint clean on both changed test files (the 15 pre-existing errors in windows-pit-browser.mjs et al. are unchanged from main).

🤖 Generated with Claude Code

…g without it

On macOS TronBrowser ships no engine of its own — it drives a Chromium.app
found on disk — so failing to find one is not a degraded mode: `tron` exits
with "runs Ungoogled Chromium ONLY" and never opens a window.

The Homebrew cask does not leave the app where we looked. It MOVES
Chromium.app to brew's `appdir` and keeps only a symlink in the Caskroom, so
the four /Applications and ~/Applications paths we probed can all miss an app
brew installed perfectly well. Nothing looked in the Caskroom — the one path
that holds whatever `appdir` is, and the place the two architectures actually
differ (/opt/homebrew on Apple Silicon, /usr/local on Intel; we hardcoded the
brew *binary* at both prefixes but never the Caskroom).

Worse, the installer could not tell. ensure_browser() took `brew install
--cask` exiting 0 as success and returned without checking the app had landed
anywhere the launcher looks, so install.sh printed "Installed TronBrowser" and
exited 0 while the browser was absent. The failure then surfaced at first
launch as advice to install the thing the user had just installed. Re-running
the installer could not repair it either: `install --cask --force` is a no-op
while the install receipt is intact.

  * launcher: probe the Caskroom after the app paths, resolving brew's prefix
    from brew's own location so a relocated Homebrew works, with both defaults
    as fallback (brew is usually not on PATH in a GUI launch). Newest version
    wins; a dangling symlink reads as missing. A live Caskroom path is itself
    proof of the ungoogled cask, so it no longer needs the `brew list` check —
    which requires brew on PATH and keeps reporting the cask installed after
    the .app is gone.
  * install.sh: new mac_app_path() mirroring that search; ensure_browser()
    verifies through it after every attempt, escalates install --force to
    reinstall, falls back to the direct download, and says so loudly instead
    of exiting 0 on a broken install. The trust marker is written only for
    paths that prove the build is de-googled, so a bare
    /Applications/Chromium.app still has to pass the brew check.
  * tron-doctor: had no macOS awareness at all, so it reported "No Ungoogled
    Chromium is installed" with a Flatpak hint on a perfectly healthy Mac. It
    now lists the app bundles, reads versions from Info.plist (never by running
    the binary — from a shell it SIGABRTs), names a cask whose app was deleted
    with `brew reinstall` as the fix, and suggests brew rather than Flatpak.

Three more bugs found on the way:

  * download_ungoogled_macos asked for `x86-64-macos.dmg`; upstream publishes
    `x86_64-macos.dmg`. The no-Homebrew fallback could never work on Intel.
  * brand_macos_icon only looked at Chromium.app, so icon branding silently
    no-opped for the direct-download path, which installs "Ungoogled
    Chromium.app".
  * the release JSON was fetched to a fixed /tmp path; use mktemp. Copy the
    .app with ditto rather than cp -R, the supported way to move a bundle
    without disturbing the signature arm64 code must carry.

TRONBROWSER_MAC_SEARCH_ROOT aims the machine-wide half of the search at a
fixture so the new tests run the same on any host. It grants nothing
TRONBROWSER_BROWSER does not already grant, and is unset in normal use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

ThreatCrush Security Scan

52 finding(s)

HIGH/CRITICAL: 3 | MEDIUM: 28 | LOW: 21

Severity Rule Location
HIGH py-ssrf-outbound-request apps/desktop/launcher/tron-tor-helper:303
HIGH py-ssrf-outbound-request apps/desktop/launcher/tron-tor-helper:437
HIGH sh-remote-script-execution apps/web/public/install.sh:938
MEDIUM js-open-redirect apps/desktop/extensions/ai-sidebar/install-helper.js:156
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/media.js:34
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/media.js:57
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:237
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:266
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:336
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/options.js:306
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/sidepanel.js:78
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/sidepanel.js:166
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:77
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:227
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:569
MEDIUM js-unescaped-html-sink apps/web/public/app.js:29
MEDIUM js-unescaped-html-sink apps/web/public/dns.js:54
MEDIUM sh-remote-script-execution apps/web/public/install.sh:167
MEDIUM sh-remote-script-execution apps/web/public/install.sh:172
MEDIUM sh-remote-script-execution apps/web/public/install.sh:407
MEDIUM sh-remote-script-execution apps/web/public/install.sh:425
MEDIUM sh-remote-script-execution apps/web/public/install.sh:994
MEDIUM sh-remote-script-execution apps/web/public/install.sh:1045
MEDIUM js-unescaped-html-sink apps/web/public/settings.js:96
MEDIUM js-unescaped-html-sink apps/web/public/settings.js:168
MEDIUM js-dynamic-code-execution packages/sdk/src/mcp/tools.ts:125
MEDIUM js-dynamic-code-execution packages/sdk/src/page.ts:68
MEDIUM js-dynamic-code-execution packages/sdk/src/page.ts:73
MEDIUM sql-template-interpolation services/api/src/store/db.ts:116
MEDIUM js-dynamic-code-execution services/api/src/store/scanner.ts:44
MEDIUM sh-predictable-temp-path start.sh:25
LOW js-dynamic-code-execution apps/desktop/extensions/ai-sidebar/pit-proxy.test.js:8
LOW js-dynamic-code-execution packages/agent-runtime/src/analyze/form-script.test.ts:6
LOW js-unescaped-html-sink packages/agent-runtime/src/analyze/form-script.test.ts:26
LOW js-unescaped-html-sink packages/agent-runtime/src/analyze/form-script.test.ts:49
LOW js-dynamic-code-execution packages/browser-core/src/automation/extract-script.test.ts:6
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:16
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:45
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:57
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:80
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:99
LOW js-dynamic-code-execution packages/browser-core/src/automation/snapshot-script.test.ts:11
LOW js-unescaped-html-sink packages/browser-core/src/automation/snapshot-script.test.ts:62
LOW js-unescaped-html-sink packages/browser-core/src/automation/snapshot-script.test.ts:101
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:47
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:170
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:216
LOW js-dynamic-code-execution packages/sdk/src/mcp/automate.test.ts:261
LOW secret-generic-api-key packages/storage/src/config.ts:51
LOW secret-generic-credential packages/storage/src/config.ts:51

…and 2 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 4fc5102 into main Oct 11, 2026
12 checks passed
@mrprecioustech mrprecioustech changed the title fix(macos): find the Homebrew cask's Chromium.app, and stop installing without it fix(macos): make TronBrowser findable and launchable on a Mac Oct 12, 2026
@mrprecioustech

Copy link
Copy Markdown
Contributor Author

Second fix on this branch: there was no app

"I can't find TronBrowser in my apps"

Because it was never there. install.sh wrote a freedesktop .desktop file into $PREFIX/share/applications on every platform. macOS doesn't read those — only .app bundles are indexed. So a Mac install put nothing in Launchpad, Spotlight, Finder or the Dock, and typing tron in a terminal was the only way to start the browser — while the installer and README both said to "find 'TronBrowser' in your app menu".

The one macOS-facing icon code we had, brand_macos_icon, repaints the icon of the Chromium the browser drives — which is still named Chromium. So the closest thing to TronBrowser in Launchpad was an app called Chromium with our icon on it.

make_macos_app()

Builds ~/Applications/TronBrowser.app: a thin wrapper whose executable execs the tron CLI by absolute path (a GUI launch inherits almost no PATH), with an .icns built on-device via sips/iconutil and the installed version in its Info.plist. Built at install time rather than shipped in the release zip precisely because it must hardcode that path.

Verified end to end on an Apple Silicon Mac:

$ mdls -name kMDItemContentType -name kMDItemDisplayName ~/Applications/TronBrowser.app
kMDItemContentType = "com.apple.application-bundle"
kMDItemDisplayName = "TronBrowser"

$ mdfind "kMDItemDisplayName == 'TronBrowser*'"
/Users/…/Applications/TronBrowser.app

$ open -a ~/Applications/TronBrowser.app   # → Chromium on the TronBrowser profile

It deliberately declares no CFBundleURLTypes. That would list TronBrowser in Default web browser, but macOS delivers a chosen browser its URLs as a GURL Apple Event, which a shell-script executable cannot receive — the setting would look like it worked and drop every link. Absent beats broken. (A real default-browser entry needs a compiled executable; worth doing when the native fork lands.)

Also

  • the .desktop write is now Linux-gated, so it stops littering macOS with a file nothing reads
  • built on install, rebuilt on upgrade (for installs that predate it), removed by tron remove — our bundle only, never the user's Chromium
  • install.sh mac-app rebuilds it alone, alongside the existing ensure-* subcommands — for an install that predates this, or an app since trashed
  • png_to_icns() factored out of brand_macos_icon, which now carries a header distinguishing it from the new bundle
  • the installer's closing hint and the README now say Launchpad/Spotlight on macOS and "app menu" elsewhere, rather than promising the same thing on both
  • TB_NO_APP=1 skips it; TRONBROWSER_MAC_APP relocates it

Tests

7 new in apps/web/test/install-macos-app.test.ts, asserting through mdls/plutil — i.e. whether macOS accepts the bundle, not whether we wrote the files we meant to: app-bundle content type and display name, a lint-clean plist carrying the stripped version, the absolute-path exec, the absence of CFBundleURLTypes, TB_NO_APP=1, uninstall removing ours and sparing Chromium, and no .desktop file on macOS.

Also fixed a pre-existing macOS-only failure in install-automate.test.ts: /var is a symlink to /private/var, so the paths the launcher reported back were resolved while mkdtemp's were not, and the comparison could never match. Web + desktop suite: 108 passed, green on a Mac for the first time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants