Repository navigation
Make macOS FMA app copies readable by all users - #56605
Closed
allenhouchins wants to merge 1 commit into
Closed
allenhouchins wants to merge 1 commit into
allenhouchins wants to merge 1 commit into
Conversation
Fleet installs as root, and `cp -R` keeps the archive's modes, so a vendor archive with owner-only modes (Sourcetree) left the bundle readable only by root. The generated copy now runs `chmod -R go+rX` on the app and rolls back like a failed copy if that fails. Applied to the 11 custom install scripts that carry the generated copy block, and to every macOS output that uses it.
allenhouchins
temporarily deployed
to
Docker Hub
October 26, 2026 18:38 — with
GitHub Actions
Inactive
1 of 2 tasks
1 of 2 tasks
allenhouchins
added a commit
that referenced
this pull request
Oct 26, 2026
…56618) **Related issue:** Follow-up to #56532. Sourcetree is fixed on its own in #56604. ## Summary Fleet runs macOS FMA install scripts as root, and `cp -R` keeps the vendor zip's permissions. The OpenRCT2 and Screen Studio zips each store one file as `-rw-------`. Installed under `/Applications`, that file is readable only by root. Fleet still reports the app as installed, because osquery runs as root. This adds a custom install script for each app. Each one is the current generated script plus `sudo chmod -R go+rX "$APPDIR/<App>.app"` after the copy, the same as the Sourcetree fix in #56604. If the chmod fails, the script removes the copy, restores the previous version and exits 1, like a failed copy. ### Effect of the owner-only file To test this, I removed read access to each file for a regular user before the app's first launch, then launched the app. - **OpenRCT2 0.5.6, `Contents/Info.plist`:** the app launches. LaunchServices registers it with no bundle ID, name or version. `osascript -e 'application id "io.openrct2.OpenRCT2" is running'`, `tell … to quit` and `open -b io.openrct2.OpenRCT2` all fail, so the FMA's quit-before-update and relaunch steps don't find the app. - **Screen Studio 4.1.0-5005, `Contents/Resources/app.asar.unpacked/bin/arm64/tf-restormer.safetensors`:** the app launches. The file is the TF-Restormer model that the bundled `screen-studio-speech` helper loads for its speech-restoration audio step. A user-run helper can't open it. I couldn't trigger that render outside the app to see the failure end to end. I tested two other apps the same way and saw no effect, so they're left unchanged: - **Fastmail:** `embedded.provisionprofile` is owner-only. The profile check at launch still passes. - **Elgato Game Capture HD:** `CodeResources` is `-rw-r-----` and is readable by admins through the `admin` group. The generator-wide version of this fix (#56605) is closed for now. # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [ ] QA'd all new/changed functionality manually Done locally, without root: - Ran each new script against the real vendor zip (SHA-256 matches the output), with `$APPDIR` pointed at a temp folder and `sudo` removed. Both exit 0. Every file and folder in both installed bundles is readable by others (symlinks aside). Screen Studio passes `codesign --verify --deep --strict`. The OpenRCT2 release isn't signed at all, so there's no signature to check. - Regenerated the outputs with `go run ./cmd/maintained-apps --slug <app>/darwin`. The version, URL and SHA-256 are unchanged. Only the install script and its ref change. Not yet done: an install as root on a real host. CI's darwin validator installs both apps as root. ## AI **AI:** Claude Code (claude-opus-5-5) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Bug Fixes** * Improved Homebrew installations for OpenRCT2 and Screen Studio. The apps are closed before updates and reopened afterward if they were running. * Installations now handle failed updates more safely by restoring the existing app when replacement fails. Installed apps are also accessible to non-root users. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 of 2 tasks
1 of 2 tasks
allenhouchins
added a commit
that referenced
this pull request
Nov 6, 2026
**Related issue:** Resolves #57915 ## Summary Fleet runs macOS FMA install scripts as root, and `cp -R` keeps the source bundle's permissions. The Novabench 6.2.1 DMG stores `Novabench.app` owner-only: all 54 folders in the bundle are `drwx------`, and `Contents/MacOS/Novabench`, `Info.plist`, both `CodeResources` files and the bundled `com.novabench.service.pkg` are `-rwx------` or `-rw-------`. Installed under `/Applications`, the bundle is readable only by root, so users can't open the app. Fleet still reports it as installed, because osquery runs as root. This adds a custom install script for Novabench. It's the current generated script plus `sudo chmod -R go+rX "$APPDIR/Novabench.app"` after the copy, the same as #56604 (Sourcetree) and #56618 (OpenRCT2, Screen Studio). If the chmod fails, the script removes the copy, restores the previous version and exits 1, like a failed copy. This fixes only Novabench. The generator-wide version (#56605) is closed. # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [ ] QA'd all new/changed functionality manually Done locally, without root: - Checked the bundle's permissions in the cask's DMG (`https://novabench.com/files/novabench.dmg`, 6.2.1, mounted read-only). - Ran the new script against that DMG with `$APPDIR` pointed at a temp folder and `sudo` and `osascript` stubbed. It exits 0. Every folder and file in the installed bundle is readable by others, and the bundle passes `codesign --verify --deep --strict`. - Ran it again over an existing install with `chmod` made to fail. It exits 1 and restores the previous version. - Regenerated the output with `go run ./cmd/maintained-apps --slug=novabench/darwin`. The version, URL and SHA-256 are unchanged. Only the install script and its ref change. - `go test ./ee/maintained-apps/... ./cmd/maintained-apps/...` passes. Not yet done: an install as root on a real host. CI's darwin validator installs the app as root. ## AI **AI:** Claude Code (claude-opus-5-5) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Bug Fixes** * Novabench installation now attempts to close the app before updating it and relaunches it afterward if it was running. * If installation fails, the previous app is restored when available, rather than leaving a partial installation. * Installed app permissions allow non-administrator users to access and run Novabench. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issue: Follow-up to #56532. The Sourcetree-only fix is #56604.
Summary
Fleet runs macOS FMA install scripts as root. The generated copy step,
sudo cp -R "$TMPDIR/X.app" "$APPDIR", keeps the archive's permissions. When a vendor archive stores owner-only permissions, only root can read the installed app. Fleet reports it as installed, because osquery runs as root, but end users can't open it. Homebrew never hits this because the user who installs owns the files.What changes
if ! sudo cp -R "$TMPDIR/X.app" "$APPDIR" || ! sudo chmod -R go+rX "$APPDIR/X.app"; then <rollback>.go+rXadds read permission for group and others. It adds execute only on folders and on files that are already executable. It never removes permissions or adds write. If the chmod fails, the script removes the copy, restores the previous version and exits 1, like a failed copy. An app users can't open counts as a failed install.Which apps are affected today
I read the file list inside 294 of the 299 zip-based macOS FMAs. For the other 5, the download host doesn't allow partial downloads or returns 404.
Contents/Info.plistis owner-only.app.asar.unpackedis owner-only.embedded.provisionprofileis owner-only.CodeResourcesis-rw-r-----.DMG installs use the same copy step. I didn't scan DMGs.
How the outputs were regenerated
A full ingest would pull in newer upstream versions and skips frozen outputs. Instead, I made the same one-line substitution in each
darwin.jsonand recomputed thesha256[:8]ref. The script only rewrote files whose unchanged JSON re-encoded byte for byte, so formatting matches Go's encoder. For seven samples, the real ingester produced identical files: Sourcetree, OpenRCT2, Screen Studio, Fastmail, Elgato Game Capture HD, YACReader (two apps) and 010 Editor (a DMG).Merge order
#56604 adds a Sourcetree custom install script. If it merges first, I'll rebase this PR and remove
sourcetree-install.shand the input'sinstall_script_path, because the generated script does the same thing.Checklist for submitter
Changes file added for user-visible changes in
changes/,orbit/changes/oree/fleetd-chrome/changes.See Changes files for more information.
Input data is properly validated,
SELECT *is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.The chmod uses the same escaped app path as the existing
mv,cpandrmlines.Testing
Added/updated automated tests
TestInstallScriptAppReadableByUsersruns the generated script on an owner-only bundle and checks the installed permissions. It also checks that a failed chmod restores the previous app. Both cases fail without this change.QA'd all new/changed functionality manually
Done locally, without root:
$APPDIRpointed at a temp folder. It exits 0. Every file and folder in the installed bundle is readable by others (symlinks aside),codesign --verify --deep --strictpasses andspctl -aaccepts it.chmod -Rdoesn't follow symlinks out of the bundle and doesn't fail on broken links.Not yet done: an install as root on a real host. CI's darwin validator installs every changed app as root. Like #56457, that run may also surface failures that already exist on
mainand are unrelated to this change.AI
AI: Claude Code (claude-opus-5-5)