macOS standalone deployment

Install the standalone DefenseClaw enterprise LaunchDaemons on Apple silicon Macs from the pkg or the payload tarball, check the result, and remove them.

The standalone macOS profile installs DefenseClaw machine-wide under /opt/cisco/defenseclaw, with LaunchDaemons labeled com.cisco.defenseclaw.* and a hidden service account, _defenseclaw. It is separate from the Cisco Secure Client module (/opt/cisco/secureclient/defenseclaw) and refuses to install beside it. Standard users cannot stop the daemons or change the config, and the hook guardian restores DefenseClaw's hooks if a user removes them. For the terms used on this page, see Concepts.

Deploying with an MDM?

This page installs on one Mac from an administrator shell. To deploy with Jamf Pro, Iru (formerly Kandji), Intune, Workspace ONE or another MDM, start with Install with an MDM. It uses the same pkg and the same config.

Requirements

RequirementDetail
macOSmacOS 13.0 or later. The pkg refuses older versions.
HardwareApple silicon only. The pkg is arm64-only, and releases build no Intel macOS binaries, so neither the pkg nor the tarball runs on an Intel Mac.
Privilegesroot, through sudo or an MDM agent. Every enterprise macos action, status included, needs root: a standard user gets not_root. Per-user commands such as defenseclaw-gateway status and audit export answer that the computer is managed and name the administrator command.
Other profilesNo Secure Client DefenseClaw module. The pkg's pre-install script refuses when /opt/cisco/secureclient/defenseclaw or a com.cisco.secureclient.defenseclaw* LaunchDaemon exists.
GatekeeperOnly the pkg can be Developer ID-signed and notarized. It is signed only when the release pipeline has Apple signing credentials, and notarized only when it also has notary credentials, so a release can be signed but not notarized. Releases currently ship it unsigned and hash-pinned by checksums.txt (see step 1 under Install). The tarball's binaries are never Developer ID-signed. On Macs that require notarized software, use the pkg and check it first with spctl --assess --type install --verbose=2 <pkg>.

Layout

All paths are under /opt/cisco/defenseclaw unless shown in full.

ItemPathOwner and mode
Binariesbin: defenseclaw-gateway, defenseclaw-hook, defenseclaw-sensor-helper, and defenseclaw-acp when presentroot, 0755
Managed OpenCode pluginshare/opencode/defenseclaw.jsroot, 0644
Configetc/config.yamlroot:_defenseclaw, 0640
Policiesetc/policiesroot:_defenseclaw, 0750
Secretsetc/secretsDirectory root:_defenseclaw 0750, files 0640
Target manifestetc/hook-guardian/targets.yaml, written by the enumeratorDirectory root:_defenseclaw, 0750
Runtime descriptoretc/managed-runtime.json, the non-secret settings that hooks readroot, 0644
Machine-policy summaryetc/machine-policy.jsonReadable by users
Lifecycle statelifecycle, including last-package-result.json, last-activation-failure.log, the last applied config and a rejected oneroot, 0700
Gateway dataruntime_defenseclaw, 0700 (the device identity key needs a private directory)
Authorization ledgerhook-guardian-state, with each enrolled user's latest AI Discovery scan in ai-discovery/root:_defenseclaw, 0750
Hook socketrun/hook.sockDirectory _defenseclaw, 0755
Logs/Library/Logs/Cisco/DefenseClaw, with the gateway's logs in gateway/root, 0755; gateway/ is _defenseclaw, 0750
LaunchDaemons/Library/LaunchDaemons/com.cisco.defenseclaw.*.plistroot, 0644

The daemons:

LabelRuns asWhat it does
com.cisco.defenseclaw.gateway_defenseclawPolicy engine and hook API. Binds 127.0.0.1:18970 and run/hook.sock.
com.cisco.defenseclaw.hook-guardianrootAlways running. Reconciles each user's hooks every minute (enterprise hooks watch --interval 1m), through a worker that runs as each user. The same worker runs each enrolled user's AI Discovery scan (Per-user scans). It does not repair machine policy, including the Claude Code version floor: ensure, repair and reconcile do.
com.cisco.defenseclaw.hook-enumeratorrootRewrites the target manifest every five minutes (in the default auto enrollment mode)
com.cisco.defenseclaw.sensor-helperrootAnswers the gateway's fixed AI Discovery requests
com.cisco.defenseclaw.applyrootRuns ensure --reason path when etc/config.yaml, etc/secrets or etc/policies changes
com.cisco.defenseclaw.verifyrootRuns verify every day at 03:17 local time

How a hook reaches the gateway:

starts as _defenseclaw
runs per tool call
asks over hook.sock
TrustedZ0 · Admin and OS (root)
UntrustedZ4 · User session (the user)
RestrictedZ2 · Gateway (_defenseclaw)
launchdstarts the daemons
AI agentruns as the user
defenseclaw-hookchecks the socket owner
Gatewayallows or blocks

List view for small screens. Use the expand button to open the drawing.

TrustedZ0 · Admin and OS (root)

  1. launchdstarts the daemons
    • starts as _defenseclawGatewayZ2

UntrustedZ4 · User session (the user)

  1. AI agentruns as the user
    • runs per tool calldefenseclaw-hook
  2. defenseclaw-hookchecks the socket owner
    • asks over hook.sockGatewayZ2

RestrictedZ2 · Gateway (_defenseclaw)

  1. Gatewayallows or blocks
macOS. launchd starts the gateway as _defenseclaw, and the gateway opens 127.0.0.1:18970 and run/hook.sock itself. Only _defenseclaw can write the run directory, inside the root-owned install tree, so a user cannot create a look-alike socket. The hook sends nothing until it has checked that root or _defenseclaw owns the socket, and the gateway learns the caller's uid from the kernel.

Unlike systemd on Linux, launchd does not hold the sockets for the gateway. launchd hands sockets over only through launch_activate_socket(3), which needs cgo, and release builds are built without cgo. The protected socket directory and the hook's owner check do the same job.

How the hooks stay in place:

writes
reads
starts, as the user
repairs hooks
PrivilegedZ1 · Services (root)
ProtectedZ3 · Admin-owned files
UntrustedZ4 · User session (the user)
Enumeratorlists the users
Guardianchecks every minute
Target manifesttargets.yaml
Per-user workerruns as the user
Agent configin the user's home

List view for small screens. Use the expand button to open the drawing.

PrivilegedZ1 · Services (root)

  1. Enumeratorlists the users
    • writesTarget manifestZ3
  2. Guardianchecks every minute
    • readsTarget manifestZ3
    • starts, as the userPer-user workerZ4

ProtectedZ3 · Admin-owned files

  1. Target manifesttargets.yaml

UntrustedZ4 · User session (the user)

  1. Per-user workerruns as the user
    • repairs hooksAgent config
  2. Agent configin the user's home
macOS. The enumerator lists the users to protect in etc/hook-guardian/targets.yaml. Every minute the guardian, which runs as root, starts a short-lived worker as each listed user, and the worker puts back DefenseClaw's hooks in that user's agent config. Root never writes a user's files with its own rights.

See Threat model for what each zone may do.

Install

Stage the config first, then install the pkg or the tarball. The lifecycle picks its config in this order: --config, then /opt/cisco/defenseclaw/etc/config.yaml, then a built-in default. The default runs observe mode with no connectors, so it protects nothing.

1. Get and check the release

Download from the same GitHub release:

  • defenseclaw-enterprise-<version>-darwin-arm64.pkg, or the tarball defenseclaw-enterprise-<version>-darwin-arm64.tar.gz;
  • checksums.txt and checksums.txt.bundle.

Check the signature on checksums.txt with cosign, then the file against it, then the pkg's own signature:

VERSION=1.0.0  # the release you deploy
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 " defenseclaw-enterprise-${VERSION}-darwin-arm64.pkg\$" checksums.txt | shasum -a 256 -c
pkgutil --check-signature "defenseclaw-enterprise-${VERSION}-darwin-arm64.pkg"

Releases currently ship the pkg unsigned: it is not Developer ID-signed or notarized, and pkgutil reports "no signature". The release notes say so when that is the case. An unsigned pkg is protected by its SHA-256 in checksums.txt, whose cosign signature the first command checks, so run both checks before every deployment and pin that SHA-256 in your MDM. An unsigned pkg does not satisfy Gatekeeper on Macs that require notarization.

2. Stage the config

This example protects Codex and Claude Code in observe mode. The lifecycle requires config_version: 8, deployment_mode: managed_enterprise, enterprise.profile: standalone and data_dir: /opt/cisco/defenseclaw/runtime. If you set gateway.api_bind or gateway.api_port, they must be 127.0.0.1 and 18970.

config.yaml
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:
    codex: {}
    claudecode: {}

Each key under guardrail.connectors turns on protection for that agent. See Choose the agents to protect and the settings reference.

AI Discovery (each enrolled user's inventory scan in ai-discovery/, and the skill, MCP and AI component records it feeds) is off unless the config sets it. A config you write is read as written, so add this block to turn it on:

ai_discovery:
  enabled: true

See Per-user scans. To read the inventory on the host, run the read-only discovery view as root:

sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise macos discovery
sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise macos discovery --user <account> --json

Skills and MCP servers are inventoried, not scanned: a managed deployment does not run the skill or MCP scanners. The inventory names each skill and MCP server per user, and its ai_component and per-entry skill and MCP records go to your observability destinations. The gateway's skill, MCP and tool-catalog API routes are not available on a managed host (they answer 501); use the discovery view above instead.

Put it where the lifecycle reads it. A config staged before the install is used as is; it does not count as an unmanaged layout:

sudo install -d -o root -g wheel -m 0755 /opt/cisco/defenseclaw/etc
sudo install -o root -g wheel -m 0640 config.yaml /opt/cisco/defenseclaw/etc/config.yaml

The lifecycle re-owns the file to root:_defenseclaw 0640 when it installs.

3a. Install the pkg

VERSION=1.0.0  # the release you deploy
sudo installer -pkg "defenseclaw-enterprise-${VERSION}-darwin-arm64.pkg" -target /
sudo cat /opt/cisco/defenseclaw/lifecycle/last-package-result.json

The pkg installs the binaries in /opt/cisco/defenseclaw/bin, and its post-install script runs enterprise macos ensure --from-package --reason package --json. That creates _defenseclaw, validates the config, lays out the files, writes the LaunchDaemons, starts them in order, verifies, and rolls back on any failure. A failed apply fails the pkg install, so installer and your MDM report it. The result is in last-package-result.json, and /var/log/install.log names its first error. Fix it, then install the pkg again (or let your MDM redeploy it). The pkg receipt is com.cisco.defenseclaw.enterprise, and a failed install records none. ensure --from-package also finishes the install, but it records no receipt either, so receipt-based inventory reports the Mac as not installed until the pkg installs successfully.

If you did not stage a config, the pkg installs the default, which protects nothing. Apply your config afterward. Either copy it to /opt/cisco/defenseclaw/etc/config.yaml, and the apply daemon runs ensure for you, or run ensure yourself and read the result:

sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise macos ensure \
  --from-package --config "$PWD/config.yaml" --json

3b. Or install from the tarball

The payload folder and its binaries must be owned by root and not writable by group or others:

VERSION=1.0.0  # the release you deploy
PAYLOAD="/var/root/defenseclaw-enterprise-${VERSION}"
sudo install -d -o root -g wheel -m 0700 "$PAYLOAD"
sudo tar -xzf "defenseclaw-enterprise-${VERSION}-darwin-arm64.tar.gz" \
  -C "$PAYLOAD" --no-same-owner --no-same-permissions
sudo chmod -R go-w "$PAYLOAD"
sudo "$PAYLOAD/defenseclaw-gateway" enterprise macos ensure --payload "$PAYLOAD" --json

ensure installs when nothing is installed, applies a payload that differs from the installed one, re-applies a changed config, repairs a deployment that fails verification, and otherwise does nothing. Add --config <absolute path> to install a config from another location, and --no-start to stage the deployment with the daemons stopped. If the Mac already has a hand-built DefenseClaw layout, the install stops with unmanaged_layout_present. Add --adopt-existing to back it up and take it over.

Exit codes:

CodeMeaning
0Success, or nothing to do
1Failure. A failed install, upgrade or repair is rolled back, and a refusal before any change leaves the computer as it was. For status and verify, 1 means the deployment is unhealthy.
2Invalid arguments
75Another lifecycle run holds the lock. Retry later.

4. Add the AI Defense key

Without a key, the local policy engine decides alone. To add Cisco AI Defense, set these keys in the config:

config.yaml (excerpt)
enterprise:
  profile: standalone
  inspection:
    ai_defense:
      enabled: true
      credential: ai-defense-api-key

Then store the key, before or after the install. After the install, run:

sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise secret set \
  --name ai-defense-api-key --from-file /var/root/ai-defense.key --json
sudo rm /var/root/ai-defense.key

Before the install, run the same command with the defenseclaw-gateway from the extracted tarball ($PAYLOAD/defenseclaw-gateway, step 3b). The key stays readable by root only, the command reports not_installed as a warning and exits 0, and the install gives the gateway its access, so a config that references the key installs at once.

See AI Defense key for rotation and removal.

Verify the install

sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise macos status --json
sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise macos verify --json
pkgutil --pkg-info com.cisco.defenseclaw.enterprise   # pkg installs only

status reports the state. verify also checks every file, permission, service and readiness check, and exits 1 when anything fails. Look for:

FieldHealthy value
oktrue
installed, installed_versiontrue and the version you deployed
readinessgateway, guardian, enumerator and sensor_helper all true
coverage_completetrue: the gateway, guardian and enumerator are ready
security_completetrue: coverage is complete, the sensor helper is ready, and there are no errors
enrollment.targetsThe number of per-user targets in the manifest. Connectors published through machine policy (Codex, Claude Code, Cursor, GitHub Copilot) have no per-user targets unless enterprise.enrollment.unenrolled_users is deny, so 0 is normal when you enable only those. For per-user connectors, 0 means nobody is enrolled yet.
errorsEmpty

When the config enables no agent under guardrail.connectors while there are users to enroll, status and verify warn no_connectors_enabled and security_complete is false. Otherwise neither field checks that the users you expect are enrolled, so check enrollment too. The enumerator enrolls accounts with a uid of 501 or higher by default. See Enrollment. To reconcile at once instead of waiting for the next minute, run sudo /opt/cisco/defenseclaw/bin/defenseclaw-gateway enterprise macos reconcile --json.

See Status and verify and Logs and events for monitoring.

Per-user installs

On a Mac with /opt/cisco/defenseclaw/etc/managed-runtime.json, the per-user product refuses to run:

Per-user actionWhat happens
install.shRefuses and changes nothing
defenseclaw upgradeRefuses
Per-user gatewayRefuses to start

Nothing migrates a per-user install automatically. Before you deploy, have each user remove their per-user install with defenseclaw uninstall --binaries --yes. Add --all to also delete ~/.defenseclaw. See Uninstall.

A per-user gateway that was already running keeps running until it is stopped. The install, status and verify name each such account with the per_user_gateway_running warning and the kill command that stops it.

Remove

GoalCommand
Stop and remove the deployment, its binaries, config, secrets, data, logs, lifecycle state and the _defenseclaw account. Each user keeps ~/.defenseclaw.defenseclaw-gateway enterprise macos uninstall --json
Also remove each enrolled user's ~/.defenseclaw and per-user binariesdefenseclaw-gateway enterprise macos uninstall --purge --json
Keep the config, secrets, data and logs (and the account) for a reinstalldefenseclaw-gateway enterprise macos uninstall --keep-state --json
Keep only the _defenseclaw accountAdd --keep-service-account

Run these as root from /opt/cisco/defenseclaw/bin. Uninstall removes each user's DefenseClaw hook registrations and DefenseClaw's own machine-policy entries first. Administrator entries in those files stay as they were. Each user keeps ~/.defenseclaw unless you purge; the result's kept: line names the accounts that still have ~/.defenseclaw or per-user binaries in ~/.local/bin. With --purge it also stops each enrolled user's per-user gateway and removes all of that user's ~/.defenseclaw, the empty agent folders DefenseClaw created in that home, the per-user binaries and launcher links in ~/.local/bin, and the DefenseClaw wheels earlier per-user installs left in ~/.cache/uv, and the result names every user it purged or left alone. See Uninstall.

A pkg has no uninstaller, so the lifecycle removes the binaries itself. For a pkg install it also runs pkgutil --forget com.cisco.defenseclaw.enterprise, so the pkg receipt is gone afterward.

launchd keeps a record for each removed label. After uninstall, launchctl print system lists the DefenseClaw labels as enabled, which is launchd's default, and no job stays loaded. macOS also keeps any privacy (TCC) decision about the gateway binary, such as a Full Disk Access entry, and uninstall cannot remove it. Remove the entry in System Settings > Privacy & Security > Full Disk Access. If an MDM PPPC profile granted the access, remove that profile.

Upgrades and changes

  • Upgrade. Install the newer pkg, or run ensure with the newer tarball. An older pkg is refused before any file changes unless the root-owned rollback marker /opt/cisco/defenseclaw/lifecycle/allow-downgrade exists; the lifecycle refuses an older tarball unless you pass --allow-downgrade. See Upgrade and Roll back.
  • Change the config. Replace /opt/cisco/defenseclaw/etc/config.yaml. The apply daemon runs ensure, which validates the new file and applies it, or rolls back. See Change the config.
  • Failures. See Lifecycle error codes.

The service account

_defenseclaw is a hidden account and group. The lifecycle creates it with dscl, using the highest free id from 300 to 499 (the range macOS reserves for daemons), with shell /usr/bin/false and home /var/empty. The gateway runs as _defenseclaw. Everything else runs as root.

Privacy prompts

The packaging ships no PPPC (Privacy Preferences Policy Control) profile. The guardian works only in hidden dot-directories inside each user's home, such as ~/.claude, ~/.codex and ~/.config. macOS privacy controls (TCC) do not protect those folders, so no profile is needed.

The per-user AI Discovery scan also walks the home for package manifests. Without a profile, macOS keeps it out of TCC-protected folders, and it skips them.

If your organization moves agent configuration into a TCC-protected folder, such as Desktop, Documents or iCloud Drive, deploy a PPPC profile that grants Full Disk Access (SystemPolicyAllFiles) to /opt/cisco/defenseclaw/bin/defenseclaw-gateway. That one binary runs the gateway, the guardian (enterprise hooks watch) and the enumerator. Identify it by its Developer ID code requirement, which needs a signed build.

AI Discovery Plane C (Endpoint Security)

AI Discovery's runtime detection has three planes: A watches for inference compute, B attributes network traffic to processes, and C records agent actions from kernel events (see Runtime planes). Plane C (ai_discovery.runtime.enable_host_plane: true with c in ai_discovery.runtime.planes) reads process and file events through Apple's eslogger. On a managed Mac the root sensor helper, /opt/cisco/defenseclaw/bin/defenseclaw-sensor-helper (com.cisco.defenseclaw.sensor-helper), runs eslogger. launchd starts the helper directly, so macOS treats the helper as the responsible process, and the helper is the binary that needs Full Disk Access. Without it, Endpoint Security refuses the client (ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED), Plane C reports running: false with that reason, and the runtime snapshot stays degraded. Planes A and B keep working.

Grant the access with a PPPC payload (com.apple.TCC.configuration-profile-policy) under SystemPolicyAllFiles:

<key>Services</key>
<dict>
  <key>SystemPolicyAllFiles</key>
  <array>
    <dict>
      <key>Identifier</key>
      <string>/opt/cisco/defenseclaw/bin/defenseclaw-sensor-helper</string>
      <key>IdentifierType</key>
      <string>path</string>
      <key>CodeRequirement</key>
      <string>(the designated requirement printed by codesign -dr - on that path)</string>
      <key>Allowed</key>
      <true/>
    </dict>
  </array>
</dict>

The code requirement needs a signed build. On an unsigned test build, grant Full Disk Access to that path by hand in System Settings > Privacy & Security > Full Disk Access. Then restart the helper with sudo launchctl kickstart -k system/com.cisco.defenseclaw.sensor-helper.

Machine policy

For Codex, Claude Code, Cursor, GitHub Copilot and OpenCode, DefenseClaw adds its hooks to the vendor's machine policy files under /etc and /Library/Application Support. The files are listed in Machine policy files. An MDM configuration profile for com.openai.codex or com.anthropic.claudecode outranks those files; export DefenseClaw's entries in plist form and add them to your profile, as Higher-precedence sources and Inspect, export and verify show.