Skip to main content
Enforce installs client hooks that apply security controls before agent actions run. Unlike Detect (which discovers configurations), Enforce actively controls shadow MCP usage and, when full session scanning is enabled, policy-checks local tool inputs and outputs in real time.

How It Works

When Enforce is installed, it intercepts supported client hook events before execution:
  1. User invokes a tool — The AI assistant requests an MCP or local tool call
  2. Intercept — The call is captured before reaching the MCP server or before the local tool runs
  3. Policy evaluation — The call is checked against your organization’s policies
  4. Decision — The call is either:
    • Allowed — Proceeds normally
    • Blocked — Prevented and logged for security review
  5. Audit logging — MCP source decisions and enabled hook events are logged for visibility

Enforcement Paths

Enforce has two real-time paths:
  • Shadow MCP source enforcement — blocks MCP servers configured outside Runlayer unless the source is a Runlayer-managed proxy URL, a Runlayer-managed stdio command, or a remote URL on your allowlist. This path stays active once enforcement hooks are installed, even if full session scanning is disabled.
  • Local tool lifecycle enforcement — sends non-MCP tool inputs and outputs, such as shell commands and file reads, through the pre-tool and post-tool scanner pipeline. This path requires Full session scanning APIs and the target Hook client to be enabled under SettingsGeneral.

What Gets Intercepted

Enforce intercepts tool calls from shadow MCP servers — those configured directly in the client rather than through Runlayer. This includes MCP servers embedded in native client plugins (e.g. Claude Code marketplace plugins). The hook resolves MCP endpoints from installed plugin manifests and enforces the same source rules. Tool calls to Runlayer-managed MCPs (server, plugin, and skill proxy URLs) are recognized as managed and allowed through. All other remote MCP URLs are blocked unless they appear on the allowlist. For supported hook clients, Enforce also intercepts local non-MCP tools. These calls are evaluated by your scanner settings: organizations with no blocking scanner configuration allow by default; organizations with blocking scanner actions can block before execution or block untrusted output after execution.

Allowed remote MCP URLs (allowlist)

When Enforce hooks are installed, Enforce blocks remote (HTTP/S) MCP servers that do not point at your Runlayer instance. Workspace admins can add allowed remote MCP URLs under SettingsMDM Configuration, in the Allowed remote MCP URLs section (Save allowlist). Each entry is a base URL. A tool call’s MCP URL is allowed when:
  • The scheme, host, and port match the entry (default ports 443 / 80 for HTTPS / HTTP are treated the same as omitting the port).
  • If the entry has no path (or only /), any path on that origin is allowed.
  • If the entry includes a path (for example https://partner.example.com/mcp), the MCP URL’s path must match that prefix with a path boundary — /mcp and /mcp/stream match; /mcpbackup does not.
You can configure up to 50 allowed URLs. Stdio-based MCP commands are not controlled by this list; they must still use Runlayer-managed invocations (for example the Runlayer CLI with a server UUID).

Supported Clients

ClientmacOSWindows
Cursor
Claude Code
Codex
Hermes
Enforce uses client hooks, so this matrix is intentionally smaller than Detect’s scan matrix. Additional hook client support is in active development. Check SettingsGeneralHook clients for the latest tenant-enabled client list.

Deployment

Enforce extends the AI Watch .pkg / .msi that Detect already ships. A single signed aiwatch binary handles both: aiwatch for scanning and aiwatch hook for Enforce decisions. If you already deployed Detect via the .pkg / .msi, enabling Enforce is two extra fields in your Configuration Profile or MSI properties plus (on Windows) one extra Intune Remediation pair.
TLS trust: The MDM-deployed aiwatch binary (scan + aiwatch hook) verifies HTTPS through the OS trust store — macOS Keychain (login + System), Windows certificate stores (Current User + Local Machine). Corporate root CAs pushed via MDM, GPO, or Intune are honored automatically with no extra config. If your corporate root is not installed system-wide and hooks fail with CERTIFICATE_VERIFY_FAILED, see Troubleshooting for --ca-bundle, RUNLAYER_CA_BUNDLE, SSL_CERT_FILE, and REQUESTS_CA_BUNDLE overrides.

How the bootstrap flow works

The .pkg and .msi run an idempotent strict-ordered sequence:
  1. Enroll the device — exchange the MDM-pushed EnrollmentKey for a per-user API key, stored in the user’s keychain. Short-circuits if a credential already exists.
  2. Install hook configs — write Runlayer hook entries into the Cursor, Claude Code, Codex, and Hermes configuration files as the command string aiwatch hook --client <name>. Existing third-party hooks are preserved.
Strict ordering: step 2 hard-fails (exit 4) if step 1 didn’t leave a valid user credential. This is the guardrail against half-configured fleets. Per-platform triggers — the two steps fan out across different launchd / Intune surfaces because each step needs a different privilege context:
PlatformStep 1 (enroll) triggerStep 2 (hook install) triggerCadence
macOScom.runlayer.aiwatch.enroll.plist LaunchAgent (/Library/LaunchAgents/) — runs aiwatch enroll as the logged-in usercom.runlayer.aiwatch.bootstrap.plist LaunchDaemon (/Library/LaunchDaemons/) — runs aiwatch setup hooks install --mdm as root, writes Cursor and Codex enterprise configs plus console-user Claude Code and Hermes configsEnroll: RunAtLoad + hourly. Bootstrap: RunAtLoad + every 60 s for 10 min after .pkg install (fast-retry while the enroll agent catches up), then hourly.
Windowsscripts/bootstrap.ps1 at user logon (SCCM / GPO) — runs aiwatch.exe bootstrap --user as the logged-on user, enrolls, and writes user-scope hooks for installed clientsassert/detect.ps1 + assert/remediate.ps1 Intune Remediations pair — runs as SYSTEM, reasserts MDM-scope hook configs where supportedStep 1: once at first logon; step 2: hourly (Intune minimum)
Claude Code hooks are currently written to the console user’s settings location (~/.claude/settings.json / %USERPROFILE%\.claude\settings.json) rather than the enterprise managed-settings location, due to a Claude Code issue with hooks in managed/enterprise settings. Hermes has no native enterprise hook directory, so MDM scope writes the console user’s ~/.hermes/config.yaml / %USERPROFILE%\.hermes\config.yaml.
On macOS this means zero MDM-side scripts for Enforcelaunchd runs both units (user + root) automatically. On Windows the Intune Remediations pair is the SYSTEM-context entrypoint for step 2; scripts/bootstrap.ps1 (or aiwatch.exe bootstrap --user) covers step 1 in the user context. The SYSTEM-context scripts refuse user context and vice versa — keeping each step in the context where its target paths (user keychain vs. machine-wide config dirs) are actually reachable. macOS install timing. Enroll and bootstrap fire concurrently when the .pkg lands. To avoid the bootstrap daemon waiting a full hour when it loses that race, it fast-retries every 60 s for the first 10 min after .pkg install — so on a healthy device hooks are typically in place within ~60 s of the user logging in. The fast-retry window is bounded so devices that never enroll (e.g. wrong EnrollmentKey) don’t spam log stream; after 10 min the daemon falls back to the same hourly cadence as today and picks up enrollment as soon as it lands. Re-pushing the .pkg reopens the window.

MDM Deployment

Deploy Enforce across your organization directly from the Runlayer dashboard. Navigate to SettingsMDM Configuration and click Configure to launch the guided wizard — it walks you through picking your MDM platform, selecting Enforce mode, generating an EnrollmentKey, and downloading deployment artifacts. Then follow the guide for your MDM platform: macOS — the same signed + notarized .pkg and three MDM Configuration Profiles as Detect, with EnrollmentKey (+ optional Username / DeviceName) added to the tenant-config profile. No additional MDM-side scripts.

Jamf Pro

Parameterized via Jamf JSON Schema (no .mobileconfig editing).

Iru / Kandji

Find-and-replace on .mobileconfig, deploy via Blueprints.

SimpleMDM

Find-and-replace on .mobileconfig, deploy via device groups.

Mosyle

Find-and-replace on .mobileconfig, deploy via Custom Profiles.

Other macOS MDM

Any MDM with Custom App + Custom Profile support.
Windows — the same Intune .intunewin as Detect (additional AIWATCH_ENROLLMENT_KEY MSI property) plus the bootstrap Remediations pair shipped in the deployment package.

Intune

Win32 LOB app with AIWATCH_ENROLLMENT_KEY + Intune Remediations assert/ pair (SYSTEM) + per-user scripts/bootstrap.ps1 (logon).
For non-Intune Windows MDMs, run scripts/bootstrap.ps1 at logon (user context) and call aiwatch.exe setup hooks check/install --mdm from a SYSTEM-context recurring task.

Monitoring-only rollout (no blocking)

To roll Enforce out in observe-without-blocking mode, set the Enforcement MDM field to false. Hooks still register and forward events to Runlayer, but never block tool calls. Useful for a baseline phase before flipping enforcement on.
PlatformFieldMonitoring-only valueDefault
macOSEnforcement in com.runlayer.aiwatch Configuration Profile<false/>absent ⇒ enforce
WindowsAIWATCH_ENFORCEMENT MSI property (writes Enforcement REG_DWORD under HKLM\Software\Runlayer\AIWatch)0absent ⇒ enforce
This is the MDM equivalent of the --no-enforcement flag on runlayer setup hooks --install. Flip back to enforce by re-pushing the profile / MSI with the field unset or set to true / 1; aiwatch hook picks up the change on the next fire (no restart, no .pkg / .msi reinstall).

Manual installation (single-device)

For testing or individual device setup without an MDM, install the runlayer CLI and run the operator-facing hook installer. Step 1 — Install the Runlayer CLI:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install runlayer
Restart your terminal after installing so ~/.local/bin (macOS/Linux) or %USERPROFILE%\.local\bin (Windows) is on PATH. Step 2 — Log in to your Runlayer instance:
runlayer login --host https://your-runlayer-instance.com
Credentials are stored in your OS keychain when available, with a fallback to ~/.runlayer/config.yaml. Step 3 — Install Enforce:
runlayer setup hooks --install --yes --host https://your-runlayer-instance.com
FlagDescription
--installPerform the installation
--uninstallRemove hooks
--yesSkip confirmation prompts
--hostValidate this host exists in config before install
--clientInstall for a specific client (cursor, claude_code, codex, or hermes; default: all)
--event-hooks / --all-eventsRegister all hook events including pipeline (default: enforcement only)
--no-enforcementMonitoring only — register hooks but skip blocking enforcement
The MDM-deployed .pkg / .msi runs aiwatch setup hooks install automatically via aiwatch bootstrap. The runlayer setup hooks --install command above is the operator-facing path for non-MDM use — it uses the same hook endpoints and client hook shapes, but writes through the full CLI’s per-user installer.
Use --no-enforcement when you want visibility without blocking requests. Hooks still forward events where the corresponding event endpoints are enabled, but no policy enforcement is applied. This is useful for a rollout phase where you want to observe before enforcing. Uninstall:
runlayer setup hooks --uninstall --yes

Sessions telemetry

To collect detailed Sessions timelines, hooks must register the full event/session set, not just the shadow-MCP enforcement hooks. Local tool lifecycle scanning uses the enforcement hooks, but its backend scanner path is also gated by Full session scanning APIs and the target Hook clients under SettingsGeneral. Shadow MCP source blocking remains active without full session scanning.
  • Manual installs — add --event-hooks (or the --all-events alias) to the runlayer setup hooks --install invocation.
  • MDM deployments — the bootstrap installs the full hook set by default, controlled by the Sessions MDM field. Leave it unset (or true) for full Sessions telemetry; set it to false to install enforcement hooks only.
PlatformFieldEnforcement-only valueDefault
macOSSessions in com.runlayer.aiwatch Configuration Profile<false/>absent ⇒ all event/session hooks
WindowsAIWATCH_SESSIONS MSI property (writes Sessions REG_DWORD under HKLM\Software\Runlayer\AIWatch)0absent ⇒ all event/session hooks
Sessions is read at install time, so flipping it takes effect on the next bootstrap tick (the bootstrap re-installs idempotently). It is independent of Enforcement: Enforcement decides whether hooks block, Sessions decides which hooks get installed.

Troubleshooting

  1. Restart the AI client after installation — Cursor, Claude Code, Codex, and Hermes only read hook configs at process start.
  2. Confirm aiwatch bootstrap has run for the user:
    /usr/local/bin/aiwatch bootstrap --check   # macOS
    & "C:\Program Files\Runlayer\AIWatch\aiwatch.exe" bootstrap --check   # Windows
    
    Exit 0 = compliant. Exit 4 = no user credential (bootstrap hasn’t completed — check the LaunchAgent / Remediation logs). Exit 1 = hook configs drifted (the next bootstrap tick will rewrite them).
  3. Check that the client is supported (see table above).
The bootstrap step didn’t complete an enroll. Causes:
  • EnrollmentKey missing from MDM config — Verify on the device:
    defaults read /Library/Managed\ Preferences/com.runlayer.aiwatch.plist   # macOS
    Get-ItemProperty "HKLM:\Software\Runlayer\AIWatch"                       # Windows
    
  • Enroll LaunchAgent or bootstrap LaunchDaemon not loaded (macOS) — Check both:
    launchctl print "gui/$(id -u)/com.runlayer.aiwatch.enroll"
    sudo launchctl print system/com.runlayer.aiwatch.bootstrap
    
    If either returns Could not find service, the .pkg postinstall didn’t bootstrap it. Re-deploy the .pkg. (Exit 4 from the daemon with the agent loaded means the enroll agent hasn’t fired yet — wait for a user login or launchctl kickstart -k "gui/$(stat -f %u /dev/console)/com.runlayer.aiwatch.enroll". On a fresh .pkg install the daemon retries every 60 s for 10 min, so hooks normally land within ~60 s of enroll completing; after that window it falls back to the hourly tick.)
  • Wrong privilege context (Windows) — Each Windows script refuses the wrong context with exit 2. Map symptom → fix:
    ScriptRequired contextIntune toggle
    scripts/bootstrap.ps1 (per-user enroll + user hooks)logged-on userwire into a user-context GPO/SCCM logon script (or Intune user-context script) — not the Remediations slot
    assert/detect.ps1 + assert/remediate.ps1 (MDM-scope hook reassertion where supported)SYSTEMRun this script using the logged-on credentials = No
    bootstrap.ps1 exit 2 = “aiwatch bootstrap must run as the logged-on user, not SYSTEM”; assert/*.ps1 exit 2 = “aiwatch MDM hook ... must run as SYSTEM, not the logged-on user”.
aiwatch hook fell back to enrolling itself because the user credential was missing when an AI client fired the hook. The fallback is self-healing — the device will be fully provisioned after the first hook fire — but a non-zero rate of these events means the bootstrap pipeline isn’t running reliably on those devices.Investigate the enroll LaunchAgent + bootstrap LaunchDaemon (macOS) or the Intune Remediation / scripts/bootstrap.ps1 logon hook (Windows). Common causes: user keychain reset, the enroll LaunchAgent bootout’d by another script, or an Intune Remediation misconfigured for the wrong credential context.
  1. Check your Runlayer instance connectivity.
  2. Review the number of policies being evaluated.
  3. Contact Runlayer support if issues persist.

Shadow MCP Overview

Understanding the shadow MCP problem

Detect

Discover shadow servers via scheduled scans

Policies

Configure access control policies

Audit Logs

View intercepted tool calls