Enterprise

Linux standalone deployment

Install the standalone DefenseClaw enterprise services on Linux from the deb or rpm package or the payload tarball, check the result, and remove them.

On Linux, DefenseClaw runs machine-wide in the standalone profile only. The lifecycle installs a fixed set of systemd units. systemd (PID 1) owns the gateway's sockets, so no user process can take them over. Standard users cannot stop the services 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 or configuration management?

This page installs on one computer from a root shell. To deploy with Intune, Ansible, Puppet, Chef, Salt or another tool, start with Install with an MDM. It uses the same packages and the same config.

Requirements

RequirementDetail
systemdsystemd 239 or later running as PID 1. The lifecycle refuses containers and WSL without systemd. Check with systemctl --version.
Architecturex86_64 (amd64) or arm64. Each release has a package and a tarball for both.
Package format.deb (Debian, Ubuntu) or .rpm (RHEL and derivatives). Hosts without a package manager use the payload tarball.
SELinuxSupported. After each change the lifecycle runs restorecon -R on /opt/defenseclaw and /etc/defenseclaw, and reports a selinux_relabel warning if that fails.
Privilegesroot. Only enterprise linux status works without root.
Other profilesNo Secure Client DefenseClaw layout at /opt/cisco/secureclient/defenseclaw. The lifecycle refuses with profile_conflict.

The systemd version decides how the gateway reads secrets:

systemdSecrets
247 or later/etc/defenseclaw/secrets is root:root 0700, and each file is 0600. The gateway receives them through LoadCredential=.
239 to 246/etc/defenseclaw/secrets is root:defenseclaw 0750, and each file is 0640, readable by the gateway's group

It also decides how much of the units' sandboxing applies. systemd ignores directives it does not know, so hosts older than systemd 250 run the units without some of them; RHEL 8 (systemd 239) runs without ProtectHostname, ProtectKernelLogs, ProtectClock, ProtectProc and ProcSubset. The Linux threat model lists each directive and the version it needs.

Layout

ItemPathOwner and mode
Binaries/opt/defenseclaw/bin: defenseclaw-gateway, defenseclaw-hook, defenseclaw-sensor-helper, and defenseclaw-acp when presentroot, 0755
Managed OpenCode plugin/opt/defenseclaw/share/opencode/defenseclaw.jsroot, 0644
Config/etc/defenseclaw/config.yamlroot:defenseclaw, 0640
Policies/etc/defenseclaw/policiesroot:defenseclaw, 0750
Secrets/etc/defenseclaw/secretsSee the table above
Target manifest/etc/defenseclaw/hook-guardian/targets.yaml, written by the enumeratorDirectory root:defenseclaw, 0750
Runtime descriptor/etc/defenseclaw/managed-runtime.json, the non-secret settings that hooks readroot, 0644
Machine-policy summary/etc/defenseclaw/machine-policy.jsonReadable by users
Lifecycle state/var/lib/defenseclaw-enterprise: lock, snapshots, deployment record, last-package-result.json, last-activation-failure.log, the last applied config and a rejected oneroot, 0700
Transaction snapshots on other filesystems/opt/defenseclaw/.lifecycle-snapshots, /etc/defenseclaw/.lifecycle-snapshots: only during a lifecycle transaction (or while a rollback waits for a retry) when /var is on its own filesystem. They hold the snapshot's hard links to the files there.root, 0700
Gateway data/var/lib/defenseclawdefenseclaw, 0700 (the device identity key needs a private directory)
Authorization ledger/var/lib/defenseclaw-hook-guardian, with each enrolled user's latest AI Discovery scan in ai-discovery/root:defenseclaw, 0750
Logs/var/log/defenseclaw: empty unless a jsonl destination writes there; the services log to the journal (Logs and events)defenseclaw, 0750
Hook socket/run/defenseclaw-hook/hook.sockDirectory defenseclaw, 0755. Socket 0666: every user may connect, and the gateway authorizes each caller by uid.

The gateway runs as the defenseclaw system account. The lifecycle creates it with systemd-sysusers, or useradd as a fallback, and reuses an existing account.

The units:

UnitRuns asWhat it does
defenseclaw-gateway.servicedefenseclaw, no capabilitiesPolicy engine and hook API. Type=notify with a 60 s watchdog and Restart=always. Config, binaries and ledger are read-only to it.
defenseclaw-gateway-api.socketPID 1Listens on 127.0.0.1:18970
defenseclaw-gateway-hook.socketPID 1Listens on /run/defenseclaw-hook/hook.sock
defenseclaw-hook-guardian.serviceroot, bounded capabilitiesAlways 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.
defenseclaw-hook-guardian-reconcile.servicerootOne immediate reconcile, on demand
defenseclaw-hook-enumerator.serviceroot, homes read-onlyRewrites the target manifest every five minutes (in the default auto enrollment mode)
defenseclaw-sensor-helper.serviceroot, acquisition capabilitiesAnswers the gateway's fixed AI Discovery requests
defenseclaw-enterprise-apply.pathPID 1Runs ensure --reason path when /etc/defenseclaw/config.yaml, /etc/defenseclaw/secrets or /etc/defenseclaw/policies changes
defenseclaw-enterprise-verify.timerPID 1Runs verify daily, with up to one hour of random delay

The package installs the units in /usr/lib/systemd/system. A tarball install writes them to /etc/systemd/system. Either way, host-specific drop-ins go in /etc/systemd/system/<unit>.d/.

inherited fds
spawns
user config
hook.sock, peer uid
TrustedZ0 · Admin and MDM (root)
RestrictedZ2 · Gateway (defenseclaw)
PrivilegedZ1 · Services (root)
UntrustedZ4 · User session (user)
Systemsystemdholds both sockets
Control planegateway.service
Systemhook-guardian
Systemper-user workeras the user
Agent runtimeAI agent
Connectordefenseclaw-hook
Standalone Linux. PID 1 binds the API and hook sockets before any user process runs and keeps them open while the gateway restarts, so a hook that connects during a restart waits for the real gateway.

See Threat model for what each zone may do.

Install

Stage the config first, then install the package or the tarball. The lifecycle picks its config in this order: --config, then /etc/defenseclaw/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:

  • the package, defenseclaw-enterprise-<version>-linux-<arch>.deb or .rpm, or the tarball, defenseclaw-enterprise-<version>-linux-<arch>.tar.gz;
  • checksums.txt and checksums.txt.bundle.

Check the signature on checksums.txt with cosign, then the files against it:

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
sha256sum --ignore-missing -c checksums.txt

When the release pipeline has a GPG key, each Linux package and tarball also has a detached signature (<file>.asc), and the release includes the public key, defenseclaw-enterprise-release-key.asc.

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: /var/lib/defenseclaw. 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: /var/lib/defenseclaw
policy_dir: /etc/defenseclaw/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.

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 root -m 0755 /etc/defenseclaw
sudo install -o root -g root -m 0640 config.yaml /etc/defenseclaw/config.yaml

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

3a. Install the package

VERSION=1.4.0  # the release you deploy
ARCH=amd64     # or arm64
sudo apt install "./defenseclaw-enterprise-${VERSION}-linux-${ARCH}.deb"
# RHEL and derivatives:
# sudo dnf install "./defenseclaw-enterprise-${VERSION}-linux-${ARCH}.rpm"
sudo cat /var/lib/defenseclaw-enterprise/last-package-result.json

The package's post-install script runs enterprise linux ensure --from-package --reason package --json. That creates the service account, validates the config, lays out the files, starts the units in order, verifies, and rolls back on any failure. The script never fails the package transaction, so the package manager reports success even when the lifecycle did not apply. Always check the result: "ok": true in last-package-result.json, or detect.sh --require-healthy from the MDM kit.

3b. Or install from the tarball

Use the tarball on hosts without dpkg or rpm. The payload folder and its binaries must be owned by root and not writable by group or others:

VERSION=1.4.0  # the release you deploy
ARCH=amd64     # or arm64
PAYLOAD="/root/defenseclaw-enterprise-${VERSION}"
sudo install -d -o root -g root -m 0700 "$PAYLOAD"
sudo tar -xzf "defenseclaw-enterprise-${VERSION}-linux-${ARCH}.tar.gz" \
  -C "$PAYLOAD" --no-same-owner --no-same-permissions
sudo chmod -R go-w "$PAYLOAD"
sudo "$PAYLOAD/defenseclaw-gateway" enterprise linux 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 services stopped.

A host installed from the package refuses a tarball upgrade (package_owned_binaries). Upgrade it with the package manager.

If the host already has a hand-built DefenseClaw layout, such as older units or a gateway binary that no deployment record owns, the install stops with unmanaged_layout_present. Add --adopt-existing to back up that layout to /var/lib/defenseclaw-enterprise 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. The deployment must be installed first, because the command needs the service account and runs ensure right after it writes the key:

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

See AI Defense key for rotation and removal.

Users who are enrolled

The enumerator finds accounts through NSS, so directory users (LDAP, SSSD, Active Directory) count as well as local ones. It skips:

  • accounts below UID_MIN from /etc/login.defs (1000 when the file does not set it), and local accounts above UID_MAX (60000 when unset). enterprise.enrollment.uid_min changes the minimum, and enterprise.enrollment.uid_max sets a maximum for local and directory accounts alike. Without uid_max, directory users with id-mapped uids above UID_MAX, common with SSSD and Active Directory, are enrolled;
  • accounts with a nologin shell, unless listed in include_users;
  • accounts whose home is outside /home and /var/home. Add roots with enterprise.enrollment.home_roots; include_users does not override this;
  • accounts in exclude_users or exempt_users, and accounts that the include_groups and exclude_groups filters leave out;
  • root, uid 65534 and nobody, always.

See Enrollment for the filters and for how removed users are handled.

Verify the install

sudo /opt/defenseclaw/bin/defenseclaw-gateway enterprise linux status --json
sudo /opt/defenseclaw/bin/defenseclaw-gateway enterprise linux verify --json
systemctl list-timers defenseclaw-enterprise-verify.timer

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. For per-user connectors, a newly added user shows up after the next enumerator cycle (up to five minutes), then the guardian reconciles.

The guardian is always running, so systemctl start on it does nothing. To reconcile at once, run:

sudo /opt/defenseclaw/bin/defenseclaw-gateway enterprise linux reconcile --json

See Status and verify and Health fields for monitoring.

User namespaces

On most distributions as shipped, Ubuntu 24.04 and RHEL 9 included, verify reports ok: true with one warning, unprivileged_user_namespaces. Where standard users can create a private user and mount namespace, an agent started inside one can be given its own view of /etc/codex/requirements.toml and the other machine-policy files, the runtime descriptor and /run/defenseclaw-hook, outside your enforcement. DefenseClaw cannot turn this off from user space (Residual risks). The warning names the host setting that closes it:

HostSetting
Ubuntu 24.04 and other AppArmor hosts where kernel.apparmor_restrict_unprivileged_userns is 1kernel.apparmor_restrict_unprivileged_unconfined=1, so an unconfined process cannot switch to an AppArmor profile that allows user namespaces (for example with aa-exec)
Any hostuser.max_user_namespaces=0, which turns user namespaces off for everyone but root
# Ubuntu 24.04: keep the setting across reboots, then load it.
echo 'kernel.apparmor_restrict_unprivileged_unconfined = 1' |
  sudo tee /etc/sysctl.d/60-defenseclaw-userns.conf
sudo sysctl --system

Weigh the trade-off first. The command sandboxes of Codex and Claude Code use bubblewrap, which needs user namespaces:

  • With user.max_user_namespaces=0 those sandboxes cannot start, and neither can other software that uses user namespaces, such as rootless containers.
  • Under the AppArmor restriction a sandbox runs only where an AppArmor profile allows user namespaces. On stock Ubuntu 24.04, where kernel.apparmor_restrict_unprivileged_userns=1 is already the default, Codex's bundled bubblewrap already fails (bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted), so its shell calls fail until the user chooses --sandbox danger-full-access or approves running outside the sandbox. Setting kernel.apparmor_restrict_unprivileged_unconfined=1 as well does not change that.

DefenseClaw's hooks inspect each tool call whether or not the agent's own sandbox runs.

Per-user installs

On a computer with /etc/defenseclaw/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.

Remove

GoalCommand
Stop and remove the deployment. Keep the config, secrets, data and logs.defenseclaw-gateway enterprise linux uninstall --json
Also remove the config, secrets, data, logs and lifecycle statedefenseclaw-gateway enterprise linux uninstall --purge --json
Also delete the defenseclaw accountdefenseclaw-gateway enterprise linux uninstall --purge --remove-service-account --json

Run these as root from /opt/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. With --purge it also removes each enrolled user's ~/.defenseclaw, except inert hook stubs. See Uninstall.

On a host installed from the package, uninstall leaves the package's binaries and units in place, so dpkg or rpm still lists defenseclaw-enterprise. Removing the package runs uninstall for you:

Package commandEffect
apt remove defenseclaw-enterpriseRuns uninstall, then removes the package files. The config and state stay.
apt purge defenseclaw-enterpriseAlso deletes /etc/defenseclaw, /var/lib/defenseclaw, /var/lib/defenseclaw-hook-guardian, /var/lib/defenseclaw-enterprise and /var/log/defenseclaw. The defenseclaw account stays.
dnf remove defenseclaw-enterpriseRuns uninstall, then removes the package files. RPM has no purge, so the config and state stay.

To remove everything from a package host, purge with the lifecycle first, while the binary still exists, then remove the package:

sudo /opt/defenseclaw/bin/defenseclaw-gateway enterprise linux uninstall \
  --purge --remove-service-account --json
sudo apt purge defenseclaw-enterprise
# RHEL and derivatives:
# sudo dnf remove defenseclaw-enterprise
# sudo rm -rf /var/lib/defenseclaw-enterprise

On RHEL, the package's removal script writes its result to /var/lib/defenseclaw-enterprise again, and nothing removes it, so delete that folder last. On Debian and Ubuntu, apt purge removes it.

On a tarball host, uninstall --purge --remove-service-account removes everything, including /opt/defenseclaw.

Upgrades and changes

  • Upgrade. Install the newer package with apt or dnf, or run ensure with the newer tarball. The lifecycle refuses an older release unless you pass --allow-downgrade, and rpm -U refuses an older package. See Upgrade and Roll back.
  • Change the config. Replace /etc/defenseclaw/config.yaml. The apply path unit runs ensure, which validates the new file and applies it, or rolls back. See Change the config.
  • Failures. See Lifecycle error codes.