How It Works
When Enforce is installed, it intercepts supported client hook events before execution:- User invokes a tool — The AI assistant requests an MCP or local tool call
- Intercept — The call is captured before reaching the MCP server or before the local tool runs
- Policy evaluation — The call is checked against your organization’s policies
- Decision — The call is either:
- Allowed — Proceeds normally
- Blocked — Prevented and logged for security review
- 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 Settings → General.
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 Settings → MDM 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/80for 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 —/mcpand/mcp/streammatch;/mcpbackupdoes not.
Supported Clients
| Client | macOS | Windows |
|---|---|---|
| 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 Settings → General → Hook 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:
- Enroll the device — exchange the MDM-pushed
EnrollmentKeyfor a per-user API key, stored in the user’s keychain. Short-circuits if a credential already exists. - 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.
launchd / Intune surfaces because each step needs a different privilege context:
| Platform | Step 1 (enroll) trigger | Step 2 (hook install) trigger | Cadence |
|---|---|---|---|
| macOS | com.runlayer.aiwatch.enroll.plist LaunchAgent (/Library/LaunchAgents/) — runs aiwatch enroll as the logged-in user | com.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 configs | Enroll: RunAtLoad + hourly. Bootstrap: RunAtLoad + every 60 s for 10 min after .pkg install (fast-retry while the enroll agent catches up), then hourly. |
| Windows | scripts/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 clients | assert/detect.ps1 + assert/remediate.ps1 Intune Remediations pair — runs as SYSTEM, reasserts MDM-scope hook configs where supported | Step 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.launchd 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 Settings → MDM Configuration and click Configure to launch the guided wizard — it walks you through picking your MDM platform, selecting Enforce mode, generating anEnrollmentKey, 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.
.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).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 theEnforcement 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.
| Platform | Field | Monitoring-only value | Default |
|---|---|---|---|
| macOS | Enforcement in com.runlayer.aiwatch Configuration Profile | <false/> | absent ⇒ enforce |
| Windows | AIWATCH_ENFORCEMENT MSI property (writes Enforcement REG_DWORD under HKLM\Software\Runlayer\AIWatch) | 0 | absent ⇒ enforce |
--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 therunlayer CLI and run the operator-facing hook installer.
Step 1 — Install the Runlayer CLI:
~/.local/bin (macOS/Linux) or %USERPROFILE%\.local\bin (Windows) is on PATH.
Step 2 — Log in to your Runlayer instance:
~/.runlayer/config.yaml.
Step 3 — Install Enforce:
| Flag | Description |
|---|---|
--install | Perform the installation |
--uninstall | Remove hooks |
--yes | Skip confirmation prompts |
--host | Validate this host exists in config before install |
--client | Install for a specific client (cursor, claude_code, codex, or hermes; default: all) |
--event-hooks / --all-events | Register all hook events including pipeline (default: enforcement only) |
--no-enforcement | Monitoring 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.--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:
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 Settings → General. Shadow MCP source blocking remains active without full session scanning.- Manual installs — add
--event-hooks(or the--all-eventsalias) to therunlayer setup hooks --installinvocation. - MDM deployments — the bootstrap installs the full hook set by default, controlled by the
SessionsMDM field. Leave it unset (ortrue) for full Sessions telemetry; set it tofalseto install enforcement hooks only.
| Platform | Field | Enforcement-only value | Default |
|---|---|---|---|
| macOS | Sessions in com.runlayer.aiwatch Configuration Profile | <false/> | absent ⇒ all event/session hooks |
| Windows | AIWATCH_SESSIONS MSI property (writes Sessions REG_DWORD under HKLM\Software\Runlayer\AIWatch) | 0 | absent ⇒ 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
Hooks not intercepting calls
Hooks not intercepting calls
- Restart the AI client after installation — Cursor, Claude Code, Codex, and Hermes only read hook configs at process start.
- Confirm
aiwatch bootstraphas run for the user: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). - Check that the client is supported (see table above).
`bootstrap --check` exits 4 (no user credential)
`bootstrap --check` exits 4 (no user credential)
The bootstrap step didn’t complete an enroll. Causes:
-
EnrollmentKeymissing from MDM config — Verify on the device: -
Enroll LaunchAgent or bootstrap LaunchDaemon not loaded (macOS) — Check both:
If either returns
Could not find service, the.pkgpostinstall 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 orlaunchctl kickstart -k "gui/$(stat -f %u /dev/console)/com.runlayer.aiwatch.enroll". On a fresh.pkginstall 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:
Script Required context Intune toggle scripts/bootstrap.ps1(per-user enroll + user hooks)logged-on user wire 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)SYSTEM Run this script using the logged-on credentials = No bootstrap.ps1exit 2 = “aiwatch bootstrap must run as the logged-on user, not SYSTEM”;assert/*.ps1exit 2 = “aiwatch MDM hook ... must run as SYSTEM, not the logged-on user”.
`aiwatch.lazy_enrollment_fallback_hit` events in the dashboard
`aiwatch.lazy_enrollment_fallback_hit` events in the dashboard
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.Performance issues
Performance issues
- Check your Runlayer instance connectivity.
- Review the number of policies being evaluated.
- Contact Runlayer support if issues persist.
Related Resources
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