EnterpriseInstall with an MDM

Deploy with Jamf Pro

Install, configure, detect, repair, upgrade and remove the standalone DefenseClaw enterprise profile on Macs with Jamf Pro packages, policy scripts and an extension attribute.

Template

This recipe is a template, validated by simulating the MDM execution context. It has not been run in a live tenant.

This recipe deploys the standalone profile to Macs with Jamf Pro. Jamf Pro installs the DefenseClaw package from your distribution point, and two scripts from the MDM kit (the scripts in packaging/mdm) keep the deployment configured and healthy. Every recipe follows the same contract: see Install with an MDM for exit codes, detection and how config and keys are delivered.

Prerequisites

  • Macs with Apple silicon on macOS 13 or later. The package refuses other hardware and older releases.
  • No Cisco Secure Client DefenseClaw deployment on the Mac. The package refuses to install beside it.
  • DefenseClaw ships no privacy (PPPC) profile. See macOS requirements for when you need one.
  • A cloud distribution point, if you upload the package through the Jamf Pro web interface (Uploading a Package to Jamf Pro).
  • Your administrator config. See Where the config lives and Choose the agents to protect. The default config protects no agent.
  • cosign on the workstation where you verify the release.

Get and verify the release

On an administrator workstation, download the package and the signed checksum list, check the signature, then check the package's SHA-256:

VERSION=1.4.0   # the release you deploy
BASE=https://github.com/cisco-ai-defense/defenseclaw/releases/download/$VERSION
PKG=defenseclaw-enterprise-$VERSION-darwin-arm64.pkg
curl -fsSL -O "$BASE/checksums.txt" -O "$BASE/checksums.txt.bundle" -O "$BASE/$PKG"
cosign verify-blob --bundle checksums.txt.bundle \
  --certificate-identity "https://github.com/cisco-ai-defense/defenseclaw/.github/workflows/release.yaml@refs/heads/main" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com checksums.txt
grep "  $PKG\$" checksums.txt | shasum -a 256 -c -

You also need three scripts from the MDM kit. They are in packaging/mdm/macos in the source tree and in the defenseclaw-enterprise-<version>-darwin-arm64.tar.gz release archive:

ScriptUse in Jamf Pro
defenseclaw-enterprise.shPolicy script that runs ensure
detect.shExtension attribute
uninstall.shRemoval policy script

How Jamf Pro runs scripts and packages

BehaviorJamf ProSource
IdentityScripts run as root.Jamf 100 course, lesson 25
ArgumentsJamf Pro reserves the first three positional parameters for its own values. The values you enter in a policy start at $4, and the Jamf Pro API stores parameter4 through parameter11.Scripts, Create a script (API)
OrderA script's priority (Before or After) orders it against the policy's other payloads. Scripts in the same policy run in alphanumeric order of their names.Scripts, Running Scripts Using a Policy
OutputPolicy logs keep script output and are truncated after 25 KB.Running Scripts Using a Policy
RetryWith Once per computer, Automatically re-run policy on failure retries up to 10 times. Other frequencies run again on their schedule.Execution Frequency for Policies
Package integrityJamf Pro calculates a checksum when you upload a package. The security settings choose when computers validate it after download.Uploading a Package, Security Settings
Extension attributesA script extension attribute runs each time the computer submits inventory. Jamf Pro stores the text between <result> and </result>.Extension Attribute Input Types

Add one line to each kit script

The kit scripts accept only their own flags. Any positional argument makes defenseclaw-enterprise.sh and uninstall.sh stop with exit code 2 and the error mdm_invalid_arguments, before they do anything. detect.sh exits 2 and prints nothing on standard output, so an extension attribute without the line below reports an empty value. Because Jamf Pro always passes positional values, add this line at the top of the settings block of every kit script you upload to Jamf Pro. It discards the values Jamf Pro passed, so the script uses only its settings block:

set -- # Jamf Pro passes positional values; this script takes its settings from this block

Do not pass the settings as Jamf Pro parameters. Parameters reach the script as command-line arguments, which other processes on the Mac can read.

Prepare the scripts

Create each script in Settings > Computer management > Scripts > New (Adding a Script to Jamf Pro).

DefenseClaw stage config

This script holds your administrator config. It writes the config to a root-only staging folder, which the ensure script reads. On a Mac with no DefenseClaw config yet, it also writes the config to the layout path /opt/cisco/defenseclaw/etc/config.yaml, so the package applies it on the first install. It ignores the values Jamf Pro passes. Set its priority to Before.

#!/bin/sh
# DefenseClaw: stage the administrator config (Jamf Pro, priority Before).
set -eu
PATH=/usr/bin:/bin:/usr/sbin:/sbin
stage=/Library/Management/DefenseClaw
layout=/opt/cisco/defenseclaw/etc
umask 022
mkdir -p "$stage"
tmp=$(mktemp "$stage/.config.yaml.XXXXXX")
cat >"$tmp" <<'DEFENSECLAW_CONFIG'
config_version: 8
deployment_mode: managed_enterprise
data_dir: /opt/cisco/defenseclaw/runtime
policy_dir: /opt/cisco/defenseclaw/etc/policies
enterprise:
  profile: standalone
gateway:
  api_bind: 127.0.0.1
  api_port: 18970
guardrail:
  enabled: true
  mode: observe
  connectors:
    claudecode: {}
    codex: {}
DEFENSECLAW_CONFIG
chown root:wheel "$tmp"
chmod 0600 "$tmp"
mv -f "$tmp" "$stage/config.yaml"
if [ ! -e "$layout/config.yaml" ]; then
    mkdir -p "$layout"
    tmp=$(mktemp "$layout/.config.yaml.XXXXXX")
    cat "$stage/config.yaml" >"$tmp"
    chown root:wheel "$tmp"
    chmod 0640 "$tmp"
    mv -f "$tmp" "$layout/config.yaml"
fi
echo "DefenseClaw config staged"

Replace the YAML between the markers with your config. Never put the Cisco AI Defense key in it: the standalone profile rejects an inline API key, and script bodies are not secret storage.

DefenseClaw ensure

Upload defenseclaw-enterprise.sh and edit its settings block. Leave the source empty: the package is installed by the install policy, and the script re-applies the installed deployment with the staged config. Set its priority to After.

set -- # Jamf Pro passes positional values; this script takes its settings from this block
DC_ACTION=ensure
DC_CONFIG_FILE="/Library/Management/DefenseClaw/config.yaml"

The wrapper refuses a config file that is not owned by root or that another account can write, anywhere on its path (mdm_untrusted_input). The stage script creates the folder and the file with the right owner and mode.

DefenseClaw uninstall

Upload uninstall.sh and add the same set -- line to its settings block. Set DC_PURGE=1 only if you also want to remove the config, credentials, state and logs.

Install

Upload the package in Settings > Computer management > Packages > New. On the package's Limitations tab you can require Apple silicon and macOS 13 or later (Package Settings).

Create a policy in Computers > Policies > New (Deploying a Package Using a Policy):

SettingValue
NameDefenseClaw install
TriggerRecurring Check-in
Execution frequencyOnce per computer, with Automatically re-run policy on failure
Scripts payloadDefenseClaw stage config, priority Before
Packages payloadThe DefenseClaw package, action Install
Maintenance payloadUpdate inventory
ScopeThe Smart Group DefenseClaw needs install (see Detect and report)

The package's postinstall step runs defenseclaw-gateway enterprise macos ensure --from-package. If the lifecycle fails, it has already rolled back, and the postinstall step fails the package install, so the policy log shows the failure. The result is kept in /opt/cisco/defenseclaw/lifecycle/last-package-result.json.

Keep it applied

Create a second policy that converges the Mac every day. It applies config changes and repairs drift, and does nothing when the Mac already matches:

SettingValue
NameDefenseClaw ensure
TriggerRecurring Check-in
Execution frequencyOnce every day
Scripts payloadDefenseClaw stage config, priority Before; then DefenseClaw ensure, priority After
ScopeThe Smart Group DefenseClaw installed

The ensure script runs enterprise macos ensure --reason mdm with the staged config. It prints one lifecycle result document, which the policy log keeps, and appends it to /Library/Logs/Cisco/DefenseClaw/mdm-wrapper.log. To run it at once on a test Mac, give the policy a custom trigger and run sudo jamf policy -event <trigger> -verbose (Getting More Information Using the jamf binary).

The deployment also watches its own files. When the config, credentials or policies at the layout path change, a launchd job runs ensure, and a daily job runs verify.

Deliver the AI Defense key

The key is optional. Without it the local policy engine decides alone. Jamf Pro parameters reach the script as command-line arguments, which other processes can read, so do not put the key in a script, a parameter, a package or a configuration profile.

Store it after the first install, on standard input, from your secrets tooling or an administrator session on the Mac:

read -rs KEY && printf '%s' "$KEY" | sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise secret set --name ai-defense-api-key --from-stdin --json; unset KEY

Then turn on AI Defense in the config and let the ensure policy apply it:

enterprise:
  profile: standalone
  inspection:
    ai_defense:
      enabled: true
      credential: ai-defense-api-key

See Deliver the AI Defense key and AI Defense key.

Detect and report

Create a computer extension attribute in Settings > Computer management > Extension attributes > New, with Data Type String and Input Type Script (Manually Creating a Computer Extension Attribute). Paste detect.sh with these settings:

set -- # Jamf Pro passes positional values; this script takes its settings from this block
DC_MIN_VERSION="1.4.0"
DC_REQUIRE_HEALTHY=1
DC_FORMAT="jamf"

It asks the installed, root-owned gateway for its status and, with DC_REQUIRE_HEALTHY=1, runs verify. It always exits 0 and prints one of these values:

ValueMeaning
The installed version, such as 1.4.0Installed, at least DC_MIN_VERSION, and verify passes
not-installedNo managed deployment, or its gateway is not root-owned
outdatedOlder than DC_MIN_VERSION
unhealthyverify reported problems

Build Smart Groups on the attribute:

Smart GroupCriteria on the extension attribute
DefenseClaw needs installnot-installed or outdated
DefenseClaw installeda version, outdated or unhealthy, for example matches regex ^([0-9]|outdated|unhealthy). A blank value means the Mac has not reported the attribute yet, so do not treat it as installed.
DefenseClaw unhealthyunhealthy

The value changes only when the Mac submits inventory. For the full detection contract, see Detection.

Upgrade, roll back and change the config

TaskWhat to do
UpgradeUpload the new package. Set DC_MIN_VERSION in the extension attribute to the new version, so older Macs join DefenseClaw needs install. Create a new install policy with the new package, or replace the package and flush the policy's logs: a Once per computer policy does not run again on a computer that has a log entry. The package's ensure --from-package upgrades in place and keeps the config and state.
Roll backFollow Roll back: run a script that creates the root-owned rollback marker (touch /opt/cisco/defenseclaw/lifecycle/allow-downgrade), then deploy the earlier package the same way. Without the marker the package refuses to downgrade.
Change the configEdit the YAML in DefenseClaw stage config. The ensure policy applies it at its next run. The lifecycle validates the new config before it replaces the running one, and rolls back if applying it fails.

See Upgrades, rollback and config changes.

Uninstall

Jamf Pro's own package uninstall works only for packages indexed with Jamf Admin, which Jamf Pro 11.7 removed (Policy Payload Reference). Remove DefenseClaw with a policy that runs DefenseClaw uninstall. It runs enterprise macos uninstall, which stops the services and removes DefenseClaw's hooks and machine-policy entries, and the lifecycle forgets the package receipt. On a Mac without the deployment it prints a no-op result and exits 0. Remove the Mac from the install and ensure policies first, and remove /Library/Management/DefenseClaw if you no longer need the staged config.

Logs

WhereWhat
Jamf Pro: Computers > Policies > the policy > LogsStatus and script output for each computer, truncated after 25 KB (Viewing and Flushing Logs for a Policy)
The Mac's jamf.logThe Jamf agent's own log (Getting Additional Logging Information)
/Library/Logs/Cisco/DefenseClaw/mdm-wrapper.logEach run of defenseclaw-enterprise.sh and uninstall.sh that gets past its argument checks, with its result (root, 0600). detect.sh does not log.
/opt/cisco/defenseclaw/lifecycle/last-package-result.jsonThe package's own ensure result
/Library/Logs/Cisco/DefenseClaw/lifecycle.log, verify.logThe automatic apply and daily verify jobs

See Logs and Status and verify.

Exit codes in Jamf Pro

The kit scripts and the lifecycle use the Linux and macOS codes. Read the result document in the policy log for errors[].code.

CodeMeaningWhat to do in Jamf Pro
0Success, or nothing to doNothing
1The action failed and rolled back, or the script refused an inputRead errors[].code in the policy log; see MDM wrapper error codes
2Invalid arguments or settings, such as a missing set -- lineFix the script settings; a retry gives the same result
75Another lifecycle run or the installer holds a lockLet the next scheduled run retry

The extension attribute always exits 0; its value carries the result. For the full table, see Exit codes.