Auditing AI Apps Across a Mac Fleet: Build the Inventory Before You Try to Control It

Last time out, I argued that you cannot stop your team pasting company data into ChatGPT with a DNS block, and that the enforcement answer on macOS 27 is execution control. That post ended on a note I owe you the other half of: an allow list or a deny list is a list of names, and most fleets cannot produce the names.
So: before the enforcement conversation, the inventory conversation. What your existing tooling already knows about the AI apps on your Macs, what it quietly does not, and the cheapest way to close the gap.
The three questions an inventory has to answer
"What AI apps are installed?" is really three questions, and the tools answer them unevenly. What exists is the easy one — a name and a version string per device. Where it came from is harder and matters more: an app in /Applications that arrived through your MDM is a different risk from the same app in a user's home folder. And whether it is actually doing anything is the one nobody asks until an incident, because an installed-and-forgotten app is noise while a running agent with a configured MCP server is a data path.
Most of the disappointment with app inventory comes from answering the first question and assuming you answered all three.
Discovered apps is already running, and it is a census
If your Macs are in Intune, you already have an app inventory and you may not have looked at it. It lives under Apps > Monitor > Discovered apps, and for macOS it reports application name, platform, application version, device count and application publisher, with an Export button that drops the whole thing to CSV.
Two details decide how much you can trust it. First, ownership: on company-owned Macs Intune collects all apps installed on the device, while on personally owned Macs it collects only managed apps. If your BYOD population is where the shadow AI is, Discovered apps will not see it — and Microsoft adds that when you use the macOS Restricted apps setting, Intune "collects but doesn't store the full app inventory for personally owned devices."
Second, and this is the number to internalise: "In general, the report refreshes every seven days for each device, starting from its enrollment date." Seven days. That is a census, not a detector. A contractor can download an AI client, paste a quarter of forecast data into it and delete it inside one reporting window, and your inventory will never have contained the app at all.
For anything programmatic, the same data is on Graph at GET /deviceManagement/detectedApps, with DeviceManagementManagedDevices.Read.All, returning displayName, version, publisher, platform, deviceCount and sizeInByte. If you already have a Graph habit, the PowerShell workflow from the earlier post will diff last week's list against this week's.
Where the census stops
Look at that field list again. No install path. No signing authority. No first-seen or last-used timestamp. No indication the thing has ever been launched.
The install path is the omission that hurts, and Anthropic's own macOS deployment guide spells out why: install Claude Desktop into a user's ~/Applications and the user can update it themselves, while installing into /Applications requires administrator access and write permissions to the app bundle. Same app, same version string in your report, completely different control story.
Worth knowing about the neighbouring feature so you do not go hunting for it: Intune's Properties Catalog has grown an Application Properties category and even a Local AI Agent one, with install location and host process among its attributes — and it is Windows only. "This feature supports Windows devices only. On Android and Apple devices, device properties are collected automatically."
One shell script covers most of the gap
The macOS answer is a custom attribute. Intune's shell scripting needs macOS 12.0 or later, the script file must be under 1 MB, it runs as root unless you flip Run script as signed-in user, anything still running after 60 minutes is killed and reported as failed, and you get a frequency setting plus a retry count. For custom attributes specifically, "the result returned by the shell script must be 20KB or less," typed as String, Integer or Date.
One command does most of the work. system_profiler -json SPApplicationsDataType returns the full application list as JSON, with _name, version, path, obtained_from and signed_by per app — path answers the location question and signed_by gives you the certificate chain, the closest thing to provenance without a separate codesign pass. That output is far too large for a 20 KB attribute, so treat it as what you write to disk or ship to your own collector, and keep the attribute itself a summary:
#!/usr/bin/env zsh
# Intune custom attribute: named AI clients present, and where.
# Result must stay under 20 KB.
# Seed this list from your own Discovered apps CSV export.
apps=( "Claude.app" )
locations=( "/Applications" "/Applications/Utilities" )
for home in /Users/*; do
locations+=( "$home/Applications" "$home/Downloads" "$home/Desktop" )
done
found=()
for app in $apps; do
for dir in $locations; do
[ -d "$dir/$app" ] && found+=( "${app%.app}=$dir" )
done
done
if [ ${#found[@]} -eq 0 ]; then
print "none"
else
print -r -- "${(j:;:)found}"
fi
That is deliberately dumb. A name list against a path list, running on your schedule rather than Microsoft's seven-day clock, telling you the one thing the console will not: whether the copy sits in a managed location or a user's home folder. Build the name list from your own CSV export rather than from anyone's blog post, mine included — a mistyped bundle name fails silently and looks exactly like a clean fleet.
Defender now discovers local AI agents and their MCP servers
The genuinely new option: Defender for Endpoint has started discovering local AI agents and the MCP servers configured for them on onboarded devices, with no extra script or deployment — and macOS is supported, in preview. The inventory sits in the Defender portal under Assets > AI agents, on the Local agents tab, capturing agent name, vendor, version and model, the host process, trust and auto-approval settings, and each agent's configured MCP servers and endpoints.
That last part is the bit no app inventory has ever given you. Knowing an AI client is installed tells you a tool exists; knowing which MCP servers it has configured tells you what that tool can reach. For hunting, the data lands in the AgentsInfo table, filtered with | where Platform == "LocalAgents", alongside ExposureGraphNodes and ExposureGraphEdges for the relationships.
The price of admission is real: commercial cloud only, Defender for Endpoint Plan 2 at minimum, devices onboarded, and Defender Antivirus in active mode with real-time protection on and current platform and engine updates. If your Macs run Defender already this is close to free; if not, it is a licensing conversation rather than a scripting one.
Side by side
Intune Discovered apps | Custom attribute script | Defender local agent discovery | |
What it answers | Which apps exist, and on how many Macs | Anything a shell script can determine on the device | Which local AI agents and MCP servers are configured |
macOS support | Yes - all apps on company-owned, managed apps only on personally owned | Yes - macOS 12.0 or later | Yes, in preview |
Refresh | Every seven days from enrollment | Your chosen script frequency | Not stated in the docs |
Install path visible? | No | Yes | Host process reported |
Output limit | None stated | 20KB per attribute | None stated |
Extra licence | Included with Intune | Included with Intune | Defender for Endpoint Plan 2 minimum |
Getting data out | CSV export, or Graph /deviceManagement/detectedApps | Attribute value on the device record | Advanced hunting - AgentsInfo table |
What you do with the answer
Here is the part I would push back on if you brought it to me as a plan: do not go straight from inventory to block list. Apple's macOS 27 enterprise notes confirm that "device management can manage which apps or binaries are allowed to run," and Fleet's write-up of it describes matching binaries on CDHash, TeamID, SigningID and PathPrefix, with both allow and deny lists usable in one declaration — and no audit-only mode in Apple's schema. Apple's KB names no payload keys at all, so confirm the shape in your own MDM first. A control that enforces the moment it lands, driven by a list you assembled from a seven-day-old census, is how you take a design team's toolchain offline on a Tuesday morning.
The sequencing that works: inventory for a full cycle, sort what you find into sanctioned, tolerated and unacceptable, deploy the sanctioned ones through MDM so the home-folder copies have somewhere to go, and only then enforce — starting with a deny list for the genuinely unacceptable, not an allow list for everything else.
At ten thousand devices this stops being a tooling question and becomes an arithmetic one. A seven-day cadence across a fleet that size means your data averages three and a half days old, and an app on eleven Macs is a rounding error in a view that sorts by device count. The useful artefact is not the console at all — it is a weekly diff of the Graph export, filtered to apps you have never seen before, however few devices they are on. Ten installs of one unknown client is a signal; the same ten buried in a 4,000-row alphabetical list is not.
The practical takeaway
Turn on all three, in this order, and do not skip the middle one. Discovered apps costs nothing and gives you the shape of the problem this week — export it, then diff it weekly on Graph. A custom attribute script is an afternoon's work and answers what Discovered apps cannot: managed location or home folder. That distinction is what separates a policy you can enforce from one you can only announce. Defender's local agent discovery is the one I would plan a quarter around if you already hold Plan 2, because MCP server visibility is a category of information nobody had six months ago — just treat macOS preview status as preview status and validate it on a pilot ring before reporting numbers off it upward.
And resist the allow list until the inventory has been boring for a month. The fastest way to lose the AI governance argument inside your own company is to break something visible with a list you were not sure about.
Sources: Microsoft Learn — Discovered apps, memdocs — discovered-apps.md, Microsoft Learn — List detectedApps (Graph), Microsoft Learn — Use shell scripts on macOS devices in Intune, Microsoft Learn — Collect device properties with the Properties Catalog, Microsoft Learn — Discover local AI agents with Defender for Endpoint, defender-docs — discover-local-ai-agents.md, Apple Support — What's new for enterprise in macOS Golden Gate 27, system_profiler(8) man page, Programming by Stealth — Tidbit 7: jq to analyze macOS installed apps, Anthropic — Deploy Claude Desktop for macOS, Fleet — Binary authorization on macOS 27



Comments