How do you find and control shadow AI on a Mac fleet?

Jamf searches, extension attribute scripts, vendor managed preferences, endpoint agent payloads, and a staged rollout for Mac admins.

24 min read
In short: To find and control shadow AI on a Mac fleet, start with your MDM. A Jamf Pro Application Title search finds AI apps, and extension attributes that loop over each home folder find Claude Code, Codex, and Cursor state and MCP server names. Then deploy vendor controls as profiles: Claude Code managed settings in com.anthropic.claudecode, Cursor's AllowedTeamId, the Claude desktop app's forceLoginOrgUUID, and Codex requirements in com.openai.codex. Approve an endpoint agent's system extension and content filter with Apple's payloads, pilot before you enforce, and remember that personal Macs, local administrators, and cloud agent sessions sit outside MDM.

How do you find and control shadow AI on a Mac fleet?

To find and control shadow AI on a Mac fleet, start with the MDM you already run. In Jamf Pro, an advanced computer search finds installed AI apps, and extension attributes find the command-line agents and MCP servers that app inventory misses. Then deploy each vendor's own controls as configuration profiles: Claude Code managed settings in the com.anthropic.claudecode domain, Cursor's AllowedTeamId, and the Claude desktop and Codex sign-in pins. Approve an endpoint agent's system extension and content filter with Apple's payloads if you need per-connection evidence. Roll out to a pilot group before you enforce.

This guide is for the administrator who owns the Jamf, Iru (formerly Kandji), or Intune console and needs to report which AI tools run on the fleet and then control them. It builds on how to detect shadow AI, which compares detection methods across the organization, and stays on the Mac. For a product comparison, see Arcjet vs Jamf AI Governance.

Vendor documentation in this guide was reviewed on November 3, 2026. Vendors rename keys and change precedence between releases, so recheck the linked pages before a fleet-wide change.

How do you inventory AI apps with Jamf Pro?

Start with the inventory Jamf Pro already holds. In Jamf Pro, create an advanced computer search with the Application Title criterion, set the operator to is, and add one row per app, joined with or: Claude.app, ChatGPT.app, Codex.app, Cursor.app, and Ollama.app. Add the Application Version and Username display fields, save the search, and you have a list you can export to CSV. If developers keep apps outside the default locations, add custom search paths under Settings > Computer management > Inventory Collection > Software.

A renamed app bundle drops out of the search. On one Mac, search Spotlight by bundle ID instead, which finds an app wherever it sits.

App inventory has a large blind spot on developer Macs. Claude Code installs as a single binary under ~/.local/share/claude/versions/, the Codex CLI and Copilot CLI arrive through npm or Homebrew, and Copilot and Cline run as IDE extensions. None of them is an .app bundle in /Applications, so an Application Title search never lists them.

Which Jamf extension attributes find coding agents and MCP servers?

A Jamf Pro extension attribute with the Script input type runs a script during inventory collection and stores what the script prints between <result> and </result> tags. Jamf's extension attribute guidance tells you to return a result every time, so both of the following scripts print none when they find nothing. Jamf runs these scripts as root, not as the signed-in user, so a reference to ~ points at root's home. Each script loops over the home folders in /Users instead.

The first script reports which coding agent state directories exist for each local account. It checks for directories and reads no file contents:

#!/bin/bash
# Jamf Pro extension attribute: coding agent state directories per user.
# Reports directory names only. It reads no file contents.
result=""
for home in /Users/*/; do
home=${home%/}
user=${home##*/}
[ "$user" = "Shared" ] && continue
found=""
for dir in .claude .codex .cursor; do
[ -d "$home/$dir" ] && found="$found${found:+,}${dir#.}"
done
[ -n "$found" ] && result="$result${result:+; }$user: $found"
done
echo "<result>${result:-none}</result>"

On a Mac where one engineer uses Claude Code and Codex and another uses Cursor, the attribute reads alice: claude,codex; bob: cursor. A directory shows that the tool ran at least once under that account, not that it's in use today.

The second script reports MCP server names by agent. Coding agents keep MCP configuration in the same files as other account data. Anthropic's settings documentation says ~/.claude.json holds account details as well as MCP servers, and server entries can carry tokens in env blocks. So the script reads each file locally and prints only the server names, never the commands, URLs, arguments, or environment values:

#!/bin/bash
# Jamf Pro extension attribute: MCP server names per user and agent.
# Prints server names only, never commands, URLs, arguments, or env values.
JQ=/usr/bin/jq
if [ ! -x "$JQ" ]; then
echo "<result>no jq on macOS $(/usr/bin/sw_vers -productVersion)</result>"
exit 0
fi
CLAUDE='[.mcpServers // {}, (.projects // {} | .[] | objects | .mcpServers // {})]
| add | keys[] | "claude:" + .'
CURSOR='.mcpServers // {} | keys[] | "cursor:" + .'
CODEX='s/^\[mcp_servers\.\([^].]*\)\]$/codex:\1/p'
result=""
failed=""
# collect AGENT FILE COMMAND...: add server names, or record an unreadable file
collect() {
local agent=$1 file=$2 out
shift 2
[ -f "$file" ] || return 0
if out=$("$@" "$file" 2>/dev/null); then
names="$names$out"$'\n'
else
failed="$failed${failed:+,}$user:$agent"
fi
}
for home in /Users/*/; do
home=${home%/}
user=${home##*/}
names=""
collect claude "$home/.claude.json" "$JQ" -r "$CLAUDE"
collect cursor "$home/.cursor/mcp.json" "$JQ" -r "$CURSOR"
collect codex "$home/.codex/config.toml" /usr/bin/sed -n "$CODEX"
names=$(printf '%s' "$names" | /usr/bin/sort -u | /usr/bin/paste -sd, -)
[ -n "$names" ] && result="$result${result:+; }$user: $names"
done
[ -n "$failed" ] && result="incomplete, unreadable: $failed${result:+; }$result"
echo "<result>${result:-none}</result>"

The result looks like alice: claude:github,claude:staging-db,codex:linear; bob: cursor:figma. A JSON file that exists but doesn't parse, such as a ~/.claude.json caught mid-write, puts incomplete, unreadable: alice:claude at the front of the result instead of letting the Mac read as none. macOS has no built-in TOML parser, so the Codex line only matches table headers: a malformed config.toml is reported only when it can't be read, and otherwise yields whatever headers still match. Jamf's own sample uses /usr/bin/jq and marks it supported from macOS Sequoia 15.0, so on older releases the script reports the macOS version instead of failing silently. The structure of ~/.claude.json isn't a documented interface, so rerun the script on a test Mac after Claude Code updates.

Don't widen either script into a collector for whole files. Jamf administrators who can view inventory can read every extension attribute value, and ~/.claude.json holds the signed-in email address and organization UUID. To separate personal from company accounts, use the sign-in pins later in this guide. For what to do with the MCP server list, see restricting MCP servers in coding agents.

Use both attributes as Smart Computer Group criteria. Macs where the first attribute isn't none need coding agent controls, and they make a good pilot for the rollout procedure.

How do you confirm an AI app by bundle ID and Team ID?

Anyone can rename a bundle, but Apple assigns a Team ID to the developer account that signed the code, and a repackaged binary can't present it without that developer's signing key. First confirm that the bundle still matches its signature:

Terminal window
codesign --verify --strict --verbose=2 /Applications/Cursor.app

The command exits with status 0 and prints valid on disk and satisfies its Designated Requirement only when the bundle is intact. If it fails, stop there, because the signing details of a modified bundle aren't evidence. Then read who signed it:

Terminal window
codesign -dv --verbose=2 /Applications/Cursor.app 2>&1 |
grep -E '^(Identifier|TeamIdentifier)='

The following table lists the signing identifiers and Team IDs that Arcjet's detection rules match for common AI clients on macOS. For an app, the signing identifier is the bundle ID.

ClientSigning identifierTeam ID
Claude (desktop app)com.anthropic.claudefordesktopQ6L2SF6YDW
Claude Code (native binary)com.anthropic.claude-codeQ6L2SF6YDW
ChatGPTcom.openai.chat2DC432GLL2
Codex appcom.openai.codex2DC432GLL2
Codex CLIcodex2DC432GLL2
Cursorcom.todesktop.230313mzl4w4u92VDXQ22DGB9
Visual Studio Codecom.microsoft.VSCodeUBF8T346G9

Claude Code's setup documentation says that Anthropic PBC signs its macOS binaries and Apple notarizes them, and that the native installer keeps each release under ~/.local/share/claude/versions/. Run the two codesign commands against a file in that directory, not against the ~/.local/bin/claude launcher, which is a symbolic link. Visual Studio Code hosts GitHub Copilot and other extensions, so its signature identifies the editor, not the extension.

How do you collect the same data in Iru (Kandji) or Intune?

Both scripts work outside Jamf with small changes.

Iru, formerly Kandji, runs a Custom Script Library Item as root and records its standard output and standard error in the script's audit information (Iru documentation). Use either script as the audit script, and keep the exit code at 0 so that a Mac with agents installed doesn't read as a failed audit. Iru's documentation doesn't describe storing script output as a searchable inventory attribute.

Microsoft Intune has a closer equivalent. In the Intune admin center, go to Devices > By platform > macOS > Organize devices > Custom attributes for macOS and add a profile with the String data type (Microsoft Learn). The script must echo the value without Jamf's <result> tags, and the output must be 20 KB or less. Intune runs custom attribute scripts every 8 hours and shows the output in the Result column. Microsoft's documentation doesn't say which account runs a custom attribute script, so confirm on a pilot Mac that it can read other users' home folders.

How do you deploy Claude Code managed settings through MDM?

Claude Code reads managed settings from two places on a Mac. One is a configuration profile in the com.anthropic.claudecode managed preferences domain, which uses the same top-level keys as the JSON file, with nested settings as dictionaries and lists as arrays. The other is the file /Library/Application Support/ClaudeCode/managed-settings.json, with optional drop-in files in a managed-settings.d/ directory next to it. Claude Code reads the profile at startup and checks it for changes every 30 minutes, and it reloads the file when it changes.

The following plist sets the keys that most Mac fleets start with. It pins sign-in to your Claude organization, limits the models a developer can select, allows only the MCP servers you list, and runs only the hooks you deploy:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>forceLoginMethod</key>
<string>claudeai</string>
<key>forceLoginOrgUUID</key>
<string>ORG_UUID</string>
<key>availableModels</key>
<array>
<string>sonnet</string>
<string>opus</string>
</array>
<key>allowManagedMcpServersOnly</key>
<true/>
<key>allowedMcpServers</key>
<array>
<dict>
<key>serverUrl</key>
<string>https://api.githubcopilot.com/*</string>
</dict>
</array>
<key>allowManagedHooksOnly</key>
<true/>
<key>permissions</key>
<dict>
<key>disableBypassPermissionsMode</key>
<string>disable</string>
</dict>
</dict>
</plist>

Replace ORG_UUID with your Claude organization UUID. In Jamf Pro, add an Application & Custom Settings payload to a computer configuration profile, choose Upload, set the preference domain to com.anthropic.claudecode, and upload the file. Anthropic's MDM examples include the same kind of plist for a Jamf or Iru custom settings payload, and a complete .mobileconfig for MDMs that take a whole profile. Intune's Preference file profile takes the key-value pairs only, without the <plist> and outer <dict> tags (Microsoft Learn).

The following notes cover how each key behaves, including the edge cases to test before you deploy:

  • forceLoginOrgUUID takes one UUID or an array of UUIDs. Only a managed source enforces it, and an empty array or a value of the wrong type stops every Anthropic-account sign-in until you fix it. Anthropic advises also setting it in server-managed settings on claude.ai until every client runs Claude Code v2.1.299 or later.
  • availableModels limits what /model, --model, and a developer's own model key can select. A managed model on its own is only a default, and the Default picker option can still resolve outside the list unless you add enforceAvailableModels. For the hook-based alternative, see restricting models in coding agents.
  • allowManagedMcpServersOnly makes the managed allowedMcpServers list the only allowlist. Users can still add servers, but only matching ones load. Match remote servers by serverUrl and local servers by serverCommand. A serverName entry matches whatever name a developer types, so Anthropic doesn't treat it as a security control. For exclusive control, deploy /Library/Application Support/ClaudeCode/managed-mcp.json, which is a standalone file, so it needs a package or script rather than a profile (managed MCP).
  • allowManagedHooksOnly blocks user, project, and local hooks, and hooks from plugins you haven't force-enabled. With no managed hooks in the profile, only Agent SDK hooks and force-enabled plugin hooks run. To add Arcjet's hooks to the same profile, see securing Claude Code in the enterprise.

Precedence between managed sources

By default, Claude Code doesn't merge managed sources. It ranks server-managed settings first, then the MDM profile, then the managed settings file, and applies only the highest-ranked source that sets at least one policy key. It skips the rest without a warning (how Claude Code combines managed sources).

The ranking matters when two tools deliver Claude Code policy to the same Mac. If a Jamf AI Governance blueprint and a profile or file you wrote both reach a Mac, Claude Code can apply one and skip the other. To check, run /status in Claude Code on a pilot Mac. The Setting sources line names the source in use, such as Enterprise managed settings (plist), and Skipped sources lists what Claude Code found and ignored. To apply every source, set managedSourcesBehavior to "merge" in the highest-ranked source, which requires Claude Code v2.1.242 or later. The Jamf comparison describes the same check for a fleet that runs both products.

Login keys follow their own rule. Claude Code takes forceLoginOrgUUID and forceLoginMethod from the MDM profile or the managed settings file ahead of server-managed settings, and if a Mac has both a profile and a file, it ignores login keys in the file whenever the profile sets any policy key. Put the login keys in the profile.

Which other AI apps take managed preferences on macOS?

Cursor, the Claude desktop app, and OpenAI's Codex and ChatGPT desktop clients each publish macOS MDM controls. The following table summarizes what each vendor documents for pinning sign-in.

ClientDomain or payload typeSign-in key
Claude Codecom.anthropic.claudecodeforceLoginOrgUUID
Claude desktop appcom.anthropic.claudefordesktopforceLoginOrgUUID
Cursor

Payload type com.todesktop.230313mzl4w4u92

AllowedTeamId
Codex and the ChatGPT desktop appcom.openai.codex

allowed_chatgpt_workspaces inside requirements_toml_base64

Cursor ships a sample profile at /Applications/Cursor.app/Contents/Resources/app/policies/com.todesktop.230313mzl4w4u92.mobileconfig, and its PayloadType must match the bundle ID of the release channel (Cursor deployment patterns). The sample sets every policy in that version, and Cursor enforces any key you leave in it at its default value, so delete the keys you don't need. AllowedTeamId takes a comma-separated string of team IDs. Cursor signs out any other team or personal account immediately and refuses further sign-ins until an allowed team is used (Cursor identity and access).

The Claude desktop app reads the com.anthropic.claudefordesktop domain (Claude Help Center). Besides forceLoginOrgUUID, it documents isClaudeCodeForDesktopEnabled for the Code tab, isLocalDevMcpEnabled for local MCP servers, and secureVmFeaturesEnabled for Cowork. Claude Code sessions in the desktop app's Code tab and in local Cowork sessions also read the com.anthropic.claudecode profile, so a Mac with both apps needs both domains.

OpenAI documents one macOS domain, com.openai.codex, for Codex and the ChatGPT desktop app's runtime (OpenAI managed configuration). It takes base64-encoded TOML in two keys: config_toml_base64 for defaults and requirements_toml_base64 for requirements. To pin sign-in to your workspace, write a requirements.toml that contains the following:

allowed_login_methods = ["chatgpt"]
allowed_chatgpt_workspaces = ["WORKSPACE_UUID"]

Replace WORKSPACE_UUID with your ChatGPT workspace ID. Encode the file with base64 < requirements.toml | tr -d '\n', because OpenAI asks for no line wrapping, and put the output in requirements_toml_base64. Codex ignores these sign-in keys in cloud-managed requirements, so they have to be local, and it reads a changed preference at the next launch.

None of these settings covers a personal account on claude.ai or chatgpt.com in a browser. That takes the vendors' tenant headers, which a TLS-inspecting proxy inserts. For each header, see blocking personal AI accounts on work laptops.

Which MDM payloads does an endpoint agent need on macOS?

Profiles configure only the AI tools that read them. To see which Mac reached which AI service, or to stop an app with no managed preferences, you deploy an endpoint agent, such as an EDR sensor, an on-device secure web gateway, or a network observer. On current macOS, these agents ship a system extension, and macOS needs an administrator to approve it unless an MDM profile approves it first. The following Apple payloads cover the usual cases, from Apple's Device Management reference:

PayloadPayloadTypeWhat it approvesKey fields
System Extensionscom.apple.system-extension-policy

Loading the vendor's system extension without administrator approval

AllowedSystemExtensions (Team ID to bundle IDs), NonRemovableSystemExtensions

Web Content Filtercom.apple.webcontent-filterA network content filter from that system extension

FilterType set to Plugin, PluginBundleID, FilterSockets, FilterDataProviderBundleIdentifier, FilterDataProviderDesignatedRequirement

Privacy Preferences Policy Controlcom.apple.TCC.configuration-profile-policy

Full Disk Access, through the SystemPolicyAllFiles service

Identifier, IdentifierType, CodeRequirement, Allowed or Authorization

Managed login itemscom.apple.servicemanagement

The vendor's launch daemons and agents, which macOS then enables and allows automatically

Rules, each with RuleType such as Label or TeamIdentifier, and RuleValue

Apple's System Extensions payload maps Team IDs to the bundle IDs of the extensions you allow. Prefer that over AllowedTeamIdentifiers, which approves every extension the team signs. In the content filter payload, FilterType must be Plugin, because macOS supports no other value, and Apple requires FilterDataProviderBundleIdentifier and FilterDataProviderDesignatedRequirement when FilterSockets is true. Read a designated requirement from the signed binary with codesign -d -r - PATH. An Endpoint Security client usually needs Full Disk Access, and a content filter usually doesn't. Apple marks the per-app Privacy Preferences keys deprecated from macOS 27 in favor of the Privacy key in the declarative com.apple.configuration.app.settings configuration, so check which form your MDM produces.

Each vendor publishes its own Team ID, bundle IDs, and designated requirements, and most ship a ready-made profile. Use the vendor's values, and install the profile before the package.

What does Jamf AI Governance add if you run Jamf Pro and Jamf Protect?

If your Macs run Jamf Pro and Jamf Protect, Jamf AI Governance does much of this guide's inventory and vendor configuration, and it's included in Jamf for Mac (Jamf pricing). Jamf Protect supplies the telemetry, and Jamf's launch post says that without the Protect agent, AI Governance "has no signal to work with."

From that telemetry, AI Governance inventories AI apps, CLI harnesses, tool invocations, and running MCP servers. Its policy builder writes each vendor's own enterprise settings, such as model selection, file system access, MCP servers, and tool use, and publishes them as Jamf blueprints that users can't edit. The launch post names Claude Code, Claude Cowork, and OpenAI Codex, and Jamf's press release adds Claude Desktop. Cursor and GitHub Copilot are planned.

If you already run both products, turn AI Governance on before you write Claude Code profiles by hand, and then use /status to confirm that only one source delivers Claude Code policy. For a full comparison with Arcjet, see Arcjet vs Jamf AI Governance.

How do you roll out shadow AI controls on a Mac fleet?

The following procedure moves from inventory to enforcement in stages. Vendor managed settings have no dry-run mode: a profile that pins sign-in signs out a developer on a personal plan the next time Claude Code reads it, so the pilot group stands in for a dry run.

  1. Inventory the fleet. Run the Application Title search, deploy the two extension attributes, and wait one inventory cycle. Record each app, agent, and MCP server by Mac and user, and confirm the signature of anything unexpected with codesign.
  2. Build a pilot group. Create a Smart Computer Group of 10 to 20 Macs where the first extension attribute isn't none, and include the heaviest users the inventory found. Tell the pilot users which accounts and servers the profiles will allow, and which they'll refuse.
  3. Deploy managed settings to the pilot only. Scope the Claude Code, Cursor, Claude desktop, and Codex profiles to the pilot group. Install Arcjet hook policies in dry run, and deploy any endpoint agent without a block policy so that it only records. Run /status on two pilot Macs to confirm the profile is the source in use.
  4. Enforce. After a week without unresolved breakage, add the lockdown keys, switch hook policies from dry run to live, set block policies for the providers you don't approve, and widen each profile's scope in stages to the rest of the fleet.
  5. Measure. Rerun the extension attributes weekly and watch the count of Macs with unapproved MCP servers fall. Compare blocked connections and sessions without hook decisions against the inventory, and look for Macs that dropped out of the profile scope.

Expect some workarounds after step 4. A developer whose personal Claude plan stops working may switch to a browser tab or a different agent, which is why the measurement step watches connections and extension attributes rather than only profile compliance.

What can't MDM controls reach?

The controls in this guide apply only to managed Macs, and three gaps remain.

Personal Macs aren't managed. A developer who clones a repository on a personal laptop takes it outside every profile, extension attribute, and endpoint agent here. The controls for that case sit with source code access, such as which devices can sign in to your code host and identity provider.

Local administrators can tamper. Anthropic's documentation says a developer who is an administrator on the Mac can edit the managed source itself, which is why MDM tooling redeploys profiles and files on a schedule. A root user can also stop processes or install a tool that reads no managed preferences. Automated Device Enrollment with removal disabled, and profiles with PayloadRemovalDisallowed, make removal harder, but neither stops a root user from editing files. On Macs where these controls matter, remove standing administrator rights from developer accounts and grant them for a limited time when a task needs them.

Cloud agent sessions don't run on the Mac. Claude Code on the web, Codex cloud tasks, the GitHub Copilot cloud agent, and Cursor's cloud agents run on the vendor's infrastructure. Anthropic says a session in an Anthropic-hosted environment doesn't read a device's MDM profile or file, so its policy has to come from server-managed settings, and OpenAI says local execution restrictions don't automatically apply to its cloud computers. Committed repository hooks reach some cloud agents. Your endpoint agent sees none of that traffic.

Where does Arcjet fit on a Mac fleet?

Arcjet covers two layers of this guide: the endpoint agent and the coding agent hooks. The Arcjet endpoint agent for macOS is a Network Extension content filter with a root launchd service that owns reporting and credentials. You deploy its signed package and profile through your MDM, including Jamf, and the profile uses the system extension, managed login item, and content filter payloads described earlier, with no Full Disk Access. Devices enroll with short-lived tokens, and closing the menu bar app or logging out doesn't stop managed reporting.

The agent records each connection from a Mac to an AI service, identifies the signed app that made it, and attributes it to the person behind the local account at that moment. It doesn't decrypt TLS, install a root certificate, or read prompts, so pinned apps and command-line agents keep working. An administrator maps local accounts to people, including synced Jamf users, and shared or unidentified accounts show as Unknown user. In the Arcjet Console, Discovery leads from People to Devices to AI usage. Endpoint provider policies set Allow or Block per provider for a group of devices, and each decision lands in Arcjet Activity. The same agent runs on Windows, so a mixed fleet reports in one place.

For coding agents, Arcjet enforces one policy in the hooks that Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Muse Code already fire, and you install those hooks in the same managed settings profiles this guide describes. Each starter policy, such as the MCP allowlist, can run in dry run first. For setup, see the Arcjet coding agents guide.

Arcjet has limits on a Mac fleet. A connection record can't tell a personal Claude account from a company one on the same hostname, so keep the vendor sign-in pins and tenant headers. A local model makes no connection, Macs without the agent are out of scope, and a connection from an AI app can be an update check rather than a prompt. Arcjet doesn't inspect what someone types into a browser tab, so if browser prompt content is your main concern, a browser DLP product fits better. If your fleet is all Macs on Jamf Pro and Jamf Protect and your developers use only the tools AI Governance configures, start with AI Governance and add Arcjet only when you need connection records, Windows, or a policy decision on each agent action. For pricing and availability of the endpoint agent, book a demo.

Frequently asked questions

Can Jamf Pro find Claude Code on a Mac?

Not with an Application Title search, because Claude Code installs as a binary under ~/.local/share/claude/versions/ rather than an app in /Applications. Use a script extension attribute that loops over /Users and reports whether ~/.claude exists for each account, and confirm the binary's signature with codesign: Anthropic's Team ID is Q6L2SF6YDW.

Which preference domain does Claude Code use on macOS?

com.anthropic.claudecode, with the same top-level keys as managed-settings.json. Claude Code also reads /Library/Application Support/ClaudeCode/managed-settings.json, but by default it applies only the highest-ranked managed source, and a configuration profile ranks above the file. Run /status to see which source is in use.

How do you deploy Claude Code managed settings with Jamf?

Add an Application & Custom Settings payload to a computer configuration profile, upload a plist, and set the preference domain to com.anthropic.claudecode. Keys such as forceLoginOrgUUID, availableModels, allowManagedMcpServersOnly, and allowManagedHooksOnly go at the top level of the plist.

Do Kandji and Intune support the same scripts?

Mostly. Iru, formerly Kandji, runs a Custom Script as root and records its output in the audit information. Intune's custom attributes for macOS run a script every 8 hours and store its echoed output, without Jamf's result tags, up to 20 KB.

Can a local administrator remove these controls?

A local administrator can edit or delete a managed settings file, stop processes, or install a tool that reads no managed preferences. Non-removable MDM enrollment and profiles make removal harder. Removing standing administrator rights from developer accounts is the stronger control.

Does an MDM profile apply to Claude Code on the web or Codex cloud tasks?

No. Cloud agent sessions run on the vendor's infrastructure and don't read the Mac's profile or files. Use server-managed settings for Claude Code cloud sessions, and test each vendor's cloud controls separately.

AI runtime security in your code

Protect your AI agent workflows with Arcjet

Add the Arcjet hooks to the same Claude Code profile your MDM already deploys, and publish a starter policy in dry run on the pilot group.