Repository navigation
v3.0.0 - #88
Merged
Merged
v3.0.0#88
Conversation
- Introduced `PendingJamfUpdate` model to handle pending updates from Jamf. - Created `PendingJamfUpdatesManager` to manage fetching and processing of Jamf updates. - Updated `AppStateManager` to include Jamf updates management. - Enhanced `ApplicationsInfoManager` to retrieve installed Jamf applications. - Modified `AppCard` to load icons for Jamf applications asynchronously. - Updated UI components (`PendingUpdatesCard`, `PatchingProgressCard`) to display Jamf updates. - Added `InfoHelp` view for contextual help with icons. - Enhanced `Preferences` to include settings for Jamf integration. - Improved handling of application icons and action texts for installed apps.
…mageUtilities for cleaner code
…ling and enhance CircularProgressWithWave for smoother wave animation
…tting consistency
…te evaluation logic
- Implement JamfInfo and JamfInfoManager for managing Jamf data. - Introduce async functions to fetch last check-in, last inventory update, and Jamf URL. - Update AppStateManager to handle Jamf ID and integrate JamfInfoManager. - Create CompactJamfInfoCard for displaying Jamf information in the UI. - Refresh Jamf info on relevant state changes in CardGrid and TrayMenu.
…r any non-commercial microsoft cloud.
Fix indents
Code quality, security and macOS 27 fixes
Fleet mode: self-service apps, compliance and notifications
Merge the unreleased Fleet changes and the never-released 2.4.0 into 3.0.0, with breaking changes first: privileged settings only from administrators, automatic Fleet mode on Macs with orbit, and the SMAppService helper. Add the code quality and macOS 27 fixes. Set the app's version to 3.0.0 in Info.plist, which build.zsh reads, and in MARKETING_VERSION, which Xcode builds use.
The pre-release workflow failed with exit code 65 after the Fleet merge. FleetIconCatalog used regex literals, which Xcode 26 only accepts in Swift 5 mode with a compiler flag the project doesn't set; use NSRegularExpression. - Build releases with Xcode 26.6 instead of 26.0.1 on the macos-26 runner - Show xcodebuild's output when archiving, so failures in CI include the actual error
Xcode 26 couldn't infer .orangeLight in a foregroundStyle ternary, since foregroundStyle takes any ShapeStyle. Name the Color type explicitly.
Compliance was a card on Home competing with the device cards for space. It now has a sidebar page carrying the failing-checks badge, with a status hero, severity-tinted cards whose resolution is expanded by default, and passing checks compressed into a grid. The Home banner and the tray card route there via supportcompanion://compliance. The Apps cards reserved worst-case height for states most never enter, and repeated "Not installed" under a header already saying Available. They now size to content, badge only informative states, and use a tinted capsule rather than a grid of solid buttons. Search is capped and shares a row with the filter chips, which now follow the branded accent instead of the system one.
computeUpdates matched a patch to any Self Service policy and then judged it on dates and versions alone, so apps merely offered in Self Service were reported as pending updates. GIMP showed as a pending patch on a Mac without it installed: the policy advertises 3.2.4 in its description while the patch is 3.2.0, and with no install date and no deadline the version mismatch alone marked it due. Skip policies that are not installStatus 4, matching what getInstalledJamfAppsFromStore already does. Not installed also no longer counts toward upToDateCount, consistent with the fallback counters below it.
The gradient only survived one of the four appearance modes; tinted discards it and dark dropped it to 0.7 opacity with a lighten blend. A solid fill with a white glyph holds its silhouette at menu bar and Dock sizes, and lets the system supply the specular. The README opener described the app as empowering end-users without saying what it shows, which is also what the repo description and cask desc say.
The embedded web view was sized from its own content while the page reflowed to whatever size it was given, so each layout pass changed the size that drove the next one. SwiftUI reported the resulting dependency cycles and eventually crashed in StackLayout.makeChildren. The view now returns the size it is offered from sizeThatFits, so nothing asks the web view how large it wants to be. Its navigation delegate was also being replaced. WebViewState is the delegate and defers its loading-state updates to the main queue to avoid writing observable state mid-update; the coordinator that replaced it wrote synchronously. The coordinator is gone and the deferred delegate stays in place. The loading indicator is hidden rather than removed, so a page load cannot change the stack's children during its own layout. Removes WebView.swift, whose WebViewWrapper had no remaining references.
The URL was built by interpolating the action name, so a name containing &, # or a space produced a URL that did not round-trip and the app either ran the wrong action or none at all. Built with URLComponents instead.
Every restriction on what could run as root was enforced in the app. The helper's interface was a pair of methods that ran any command or script, so code running in the app process was equivalent to root, and any build we have ever signed satisfied the helper's client check — keeping an older copy on disk was enough to reach it. The helper now decides. Client validation: the connecting app must be 3.0.0 or later, signed with the hardened runtime, and running from /Applications/SupportCompanion.app. The signature is verified before anything is read out of the signing information, and the caller's uid comes from the audit token rather than from the client. Interface: named operations instead of commands. For a privileged action the app sends only the action's name and the helper looks the command up in administrator-managed preferences itself. EnableElevation is re-checked in the helper rather than only hiding a button. executeScript, which had no callers, is gone. Elevation: the deadline and the demotion timer live in a root-owned file under /var/db, so quitting the app or deleting a preference no longer leaves a user an administrator. Demotion happens whether or not the app runs, including catching up after the Mac was off. Several users can be elevated at once. While a window is open the admin group is watched, by name and by UUID, and rights granted to anyone else are taken back. EnforceAdminAllowlist and PermanentAdmins close what remains: a user with root can delete the state file, so a missing deadline must not mean there is nothing to do. With an allowlist set, an administrator the allowlist cannot explain is demoted, which makes deleting the state the losing move. Off by default, refused outright when PermanentAdmins is absent, and members that cannot be resolved to an account are reported, not removed. Deployment: the helper can be delivered with a background-tasks declaration, which puts it somewhere root cannot touch and stops an elevated user killing the process that will demote them. SkipHelperInstall keeps the package from installing a competing copy. DDM/make_ddm_assets.zsh builds the assets. The package now replaces the helper on every install and fails the install if it does not end up running, since an old helper no longer merely lags the app — it cannot serve it at all. Uninstalling demotes anyone still elevated before removing the helper that would have done it.
The installed and available versions sat in their own stack below the icon row, which left them detached from the title they describe and put a gap between the two blocks. They now sit under the title inside the same row, with the icon centred against the group.
Extracted by Xcode for strings the Fleet compliance and app catalog work already referenced. 81 new keys, English only — the Japanese, Norwegian, Swedish, German and French localizations do not cover them yet.
…ing state is reset
Fleet 4.92 ships fleet_desktop.sso_enabled, so the sign-in that was previously
deferred can now be done in the app instead of sending the user to a browser.
Sign-in runs in a web view: the app asks Fleet where to send the user, plants
Fleet's handshake cookie in the web view's cookie store, and shows the identity
provider's own page. Only the session Fleet issues at the end reaches the app.
That cookie is HttpOnly, so it is read from WKHTTPCookieStore rather than from
the page, and kept in the keychain scoped to the Fleet host that issued it. The
session is restored on the way into the first gated request rather than at
startup: a request that got ahead of the restore would come back sso_required,
and that answer discards the stored session.
Fleet's /device/{token}/desktop endpoint sits outside the SSO gate and still
reports how many policies fail, so the sidebar badge, the compliance cards and
the notification stay truthful while signed out. Everything Fleet withholds
until sign-in now collapses to a single Sign In action rather than a column of
Unknown.
FleetNotifySignIn (default off) notifies the user when a sign-in is needed, at
most once a day, leading with the failing count where there is one. The tray
cards route through supportcompanion://fleetsignin, because the sheet belongs to
the main window and presenting it from the menu bar popover does nothing when no
window is open.
Lets a standard user install software they have downloaded, without handing them an admin password, when an administrator has allowlisted it. The helper owns the decision. It re-reads the policy and the allowlist before acting, inspects the package or disk image itself, and only then stages and installs. Nothing the app says about an installer is taken on trust, so a tampered client cannot talk the helper into running something the policy does not allow. EnableUserInstalls is off by default, and the settings that gate it are read only when an administrator set them. Installers can be opened from the app, from Finder's context menu, or by double-clicking when the app is registered for the type. Where the catalog already offers the same application, the user is pointed at the approved version instead, and where the policy refuses an installer outright they can elevate and run it themselves if elevation is available to them. handleElevation takes an optional completion handler so an install can carry on once rights are granted, and AppStateManager now uses the shared ElevationManager rather than its own instance, so the countdown the UI reads is the one the install flow started.
…ebar The string catalog gains German, French, Japanese, Norwegian and Swedish for the 85 strings that were still English only. The patching progress wave takes the configured accent colour instead of the system one, so it matches the rest of a branded install. The dark mode toggle and the sidebar rows get their spacing and highlight tidied up. Date.relativeDescription replaces the formatted(.relative:) calls in the Fleet cards, which produced "in 0 seconds" for a date that had just passed. Sharpens the CHANGELOG wording on the privileged settings and helper version checks, which described the fix rather than what it prevented.
Install control
…uration profiles and improve clarity in handling SkipHelperInstall settings.
…cements, including MDM profile requirements and user install features.
…Norwegian, and Swedish
A pending Zoom update was never reported. Jamf lists a patch title as a policy of its own beside the policy that actually installed the app, under different names: "Zoom Client for Meetings" with installstatus 0, and "zoom.us" with installstatus 4. computeUpdates took an exact name match unconditionally, so it picked the listing rather than the installation, and the installed-app check added in 318d483 then discarded the patch. Matching now scores every plausible policy and prefers the one with the better evidence of an installation. A flat "is it installed" test cannot separate these two: both names reduce to the token "zoom", so both resemble zoom.us.app equally well, and only the install record tells them apart. That evidence no longer comes from installstatus alone, which is wrong in both directions. Apparency is installed but its policy reports 0, which hid its patch; Brave reports 4 with the bundle long since deleted, which produced exactly the phantom update 318d483 set out to prevent. The Mac now gets the veto and Jamf the tie-break, with installstatus trusted outright only when the application directories cannot be read at all — an empty index means the lookup failed, not that the Mac has no applications. Also stops a patch installed from here flipping back to Update. Self Service+'s store is its own cache and only changes when it runs, so re-deriving the list straight after an install read a store that still offered the patch, and clearing the spinner left the row showing its default label. RecentlyInstalledPatches holds it back until the store agrees, or until thirty minutes have passed so an install that silently did nothing reappears rather than hiding for good. This mirrors installedVersionsAwaitingInventory on the Fleet side, which covers the same lag. refreshSelfService now waits for the store's modification date to change instead of sleeping two seconds and hoping, and asks Self Service+ to quit before resorting to SIGKILL, which could cut off the write it is trying to read. A Self Service+ window the user has open is left alone: quitting something somebody is reading to refresh a cache is not a good trade, and the suppression above covers correctness instead. computeUpdates had no test coverage. It takes its installed-app index as a parameter so the rules can be exercised without touching the disk.
The Apps page listed every installed application in one flat grid. The only thing on it anybody has to act on is an available update, and that was findable only by scrolling until a card turned up with an Update button on it. Updates Available now comes first, then Installed, each with a count, and a search field narrows both. Which apps have an update comes from the active updates manager rather than from a check on the configured mode, through a new installedAppName on PendingUpdate. Jamf needs it because the two names routinely differ, and any other mode that starts reporting it gets the section without this view changing. Constants.Apps aliases the Fleet catalog's wording rather than adding keys of its own. The text is word for word the same, and a second set would mean translating "Installed" into five languages twice and keeping both in step.
The helper refuses any client not signed with the hardened runtime, because a client without it can be injected into and used as a proxy. Nothing in the project asked for it, though: hardening was applied only by build.zsh when packaging a release, with --options runtime. An app built and run from Xcode therefore had no runtime flag at all and every privileged operation failed with "Client is not signed with the hardened runtime". verifyClientPolicy already skips its install-path check under #if !DEBUG, for the same reason that a debug build runs from DerivedData, but the runtime flag had no such allowance. ENABLE_HARDENED_RUNTIME was set on the helper target only. Setting it on the app as well means a debug build satisfies the helper the same way the shipped app does, rather than by relaxing what the helper accepts. That alone breaks xcodebuild test, and the error does not say so plainly: the hardened runtime brings library validation with it, which refuses to load code signed by a different team, and the test bundle signs ad hoc so it has no team to compare. Tests fail to load before any of them runs. SupportCompanion-Debug.entitlements carries com.apple.security.cs.disable-library-validation and is used by the Debug configuration only; Release keeps SupportCompanion.entitlements unchanged. The cost is real and worth stating: it makes the debug build's hardened flag hollow, which is the very property the helper is checking for. That is acceptable for a build that only ever runs on a developer's own Mac, and the release build keeps the full guarantee. Signing the test bundle with the app's identity instead would preserve the property, but Xcode refuses non-ad-hoc signing without DEVELOPMENT_TEAM, and the only way to supply it is to commit a particular team into this file — which would break every other contributor.
Updated app icon in README and added a new image.
NSWindow holds its delegate weakly, so the WindowDelegate constructed inline in presentAsWindow was released the moment it was assigned, and windowWillClose never fired. Nothing reaches it today: the window is borderless, so it has no close button and performClose: will not act on it, and both buttons in ReasonInputView call closeWindow() themselves. But it was the safety net for any other way the window might go away, and presentAsWindow guards on window == nil, so a close that skipped closeWindow() would leave that property set and the elevation reason prompt unable to open again until the app restarted. The manager now holds the delegate, and closeWindow clears its state before closing rather than after. Once the delegate is actually retained, close() sends windowWillClose synchronously, which routes straight back into closeWindow(); with window still set, that second pass would fall through the guard and close the window twice.
No workflow ran the test suite. build.zsh only archives, so all three went straight from importing certificates to building, signing and notarizing a package nothing had tested. The test step runs before the certificates are imported, so a failure stops the run before it spends time on notarization. It overrides the signing settings because the Debug configuration expects a Developer ID certificate the runner does not have, while the tests need none. Debug is also the only configuration that relaxes library validation, without which the hardened runtime refuses to load the ad-hoc signed test bundle into the app. action-gh-release creates the tag at the repository's default branch when target_commitish is unset. Prereleases are dispatched from dev, so every tag it has written points at main's head rather than the commit that was built: all five v3.0.0.* tags and four of the v2.4.0.* ones resolve to 8586e93, from last November. No shipped package was affected, but checking out a release tag does not give you the source it was built from.
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.
Support Companion 3.0.0
Full detail in CHANGELOG.md.
Highlights
Fleet mode. Self-service app catalog, device compliance with per-policy
remediation steps, host details, and in-app Fleet Desktop SSO sign-in. Selected
automatically on Macs running orbit when no other mode matches.
Jamf patch reporting. Pending application patches from Self Service+ surface
in the tray menu and the app, reported against the policy that installed the
app rather than by name matching.
User installs. A standard user can install an administrator-allowlisted
.pkgor.dmgwithout holding admin rights. The helper re-reads the policyand re-derives the installer's facts before acting, so the confirmation sheet is
presentation only.
Elevation is enforced by the helper, not the app. Deadlines and timers moved
into a root-owned directory, so demotion happens whether or not the app is
running — including catching up on a deadline that passed while the Mac was off.
Admin rights granted to other accounts during a window are revoked, more than
one user can be elevated at once, and reasons are logged where the user cannot
edit them.
Declarative helper deployment. With
com.apple.configuration.services.background-tasks(macOS 15+, supervised) thehelper and its launchd job land in a managed directory that even root cannot
write, and the job cannot be unloaded — so an elevated user cannot stop the
process that will demote them. Recommended wherever
EnableElevationis used.Also: Apps page grouped by available updates and searchable, elevation
countdown next to the tray icon, migration to the Observation framework, macOS
27 fixes (battery health, temperature, time to full, tray popover behaviour),
and translations for German, French, Japanese, Norwegian and Swedish.
Breaking changes — action required
Read the CHANGELOG's Breaking changes section before deploying. In short:
IsPrivileged,RequirePrivilegedActionAuthenticationand every elevationkey are ignored anywhere else — including
/Library/Preferences, becauseonly root can write it and that is exactly the problem. If you deploy these
with
defaults write, move them to a profile. Ignored keys are logged, soa setting that appears to have stopped working can be confirmed from the log.
hardened runtime, and must run from
/Applications/SupportCompanion.app.failing the install if it does not end up running. A new app talking to an
old helper is not merely stale — every privileged operation fails.
other mode matches. Set
Modeexplicitly to keep a different one.EnforceAdminAllowlistdefaults to off. Before turning it on,PermanentAdminsmust list every account that should keep admin rights, including management and
break-glass accounts and anything granted rights by Platform SSO — anything not
on the list is demoted within five minutes.
Testing
update matching, catalog matching and SSO.
(
target_commitish), rather than at the default branch.Documentation
The wiki is rewritten for this release and goes live with it: new pages for
Fleet mode, Admin elevation, User installs, Deploying the helper, Modes,
Troubleshooting and a full preference reference.
Closes #78, closes #80