> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Enforce

> Intercept MCP and local tool calls in real-time to enforce security policies

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

Enforce and Sessions events are attributed through the same device identity model as Detect. See [User and Identity Mapping](/shadow-ai/identity-mapping) for how AI Watch maps OS usernames to Runlayer users.

### Enforcement Paths

Enforce has two real-time paths:

* **Shadow MCP source enforcement** — blocks MCP servers configured outside Runlayer unless the source is allowed. A source is allowed when it is a Runlayer-managed proxy URL, a Runlayer-managed stdio command, a remote URL on your [remote-URL allowlist](/shadow-ai/enforce/policy#allowed-remote-mcp-urls), a local package on your [stdio package allowlist](/shadow-ai/enforce/policy#allowed-local-stdio-packages), or a [built-in MCP server](/shadow-ai/enforce/policy#built-in-mcp-servers) permitted for that client. 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, and denies any [built-in client tool](/shadow-ai/enforce/policy#blocked-built-in-tools) whose type is on your block list. This path requires **Full session scanning APIs** and the target **Hook client** to be enabled under **Settings** → **General**.

<Note>
  **Sessions telemetry and blocking are independent.** Enforce hooks forward session and event telemetry to Runlayer whenever `Sessions` is enabled — even with `Enforcement` off, which is the default monitoring-only posture. Actual blocking of shadow MCP sources and local tool calls happens only when `Enforcement=true` (`AIWATCH_ENFORCEMENT=1`). So you can run full Sessions visibility without blocking anything, then flip `Enforcement` on when you are ready. See [Monitoring-only rollout](#monitoring-only-rollout-no-blocking).
</Note>

### 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](/shadow-ai/enforce/policy#allowed-remote-mcp-urls).

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.

### Can users bypass approved plugins and skills?

Not at the MCP layer, once Enforce hooks are installed. How each artifact type is governed:

* **Approved (Runlayer-managed) plugins and skills** — [Plugins](/platform-plugins) bundle connectors behind a single Runlayer proxy URL, so every tool call still goes through your organization's [policies](/platform-policies) and access controls. During install, plugin manifests are rewritten to accept only Runlayer proxy URLs. Managed [skills](/platform-skills) are security-scanned on publish, with a configurable risk policy that blocks high-risk skills by default.
* **Unapproved local plugins** — MCP servers embedded in locally installed plugins are subject to the same shadow MCP source enforcement described above: blocked unless they resolve to a Runlayer-managed URL or an allowlisted remote URL.
* **Unapproved local skills** — Skills are instruction files, not tool endpoints, so there is no skill-specific blocking path; [Detect](/shadow-ai/detect#shadow-skills-discovery) discovers and risk-classifies shadow skills but never blocks them. The actions a shadow skill drives still run through tool calls, which are covered by shadow MCP source enforcement and — where enabled — local tool lifecycle scanning.

Without Enforce hooks installed, nothing is blocked on the device; [Detect](/shadow-ai/detect) still provides discovery and risk classification of shadow MCPs, skills, and plugins.

## Enforce policy

Once Enforce hooks are installed, the **Enforce policy** governs which unmanaged tool sources are allowed and which built-in client tools are blocked. Admins with the **Manage org settings** capability edit it under **Settings** → **MDM Configuration**. It has four independent, **fail-closed** dimensions — an allowlist of remote MCP URLs, an allowlist of local (STDIO) packages, blocked built-in tool types, and an API-only built-in MCP server allowlist. Anything Enforce cannot positively match to a Runlayer-managed source, an allowlist entry, or a permitted built-in is blocked.

<Card title="Enforce policy configuration" icon="sliders" href="/shadow-ai/enforce/policy">
  Configure the allowlists and built-in tool blocks: remote MCP URLs, local (STDIO) packages, built-in MCP servers, built-in tool types, and one-click promotion from Detect sightings.
</Card>

## Supported Clients

| Client                 | macOS | Windows |
| ---------------------- | :---: | :-----: |
| **Cursor**             |   ✓   |    ✓    |
| **VS Code**            |   ✓   |    ✓    |
| **Claude Code**        |   ✓   |    ✓    |
| **GitHub Copilot CLI** |   ✓   |    ✓    |
| **Codex**              |   ✓   |    ✓    |
| **Hermes**             |   ✓   |    ✓    |
| **Goose**              |   ✓   |    ✓    |

<Note>
  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.
</Note>

***

## Deployment

<Note>
  **Enforce is a capability flag on the same AI Watch `.pkg` / `.msi`** that runs Detect and Sessions. A single signed `aiwatch` binary handles scanning, `aiwatch hook` decisions, and session event hooks, all authenticated by one **organization API key** — no enrollment keys. If AI Watch is already deployed, enabling Enforce is setting `Enforcement` in your Configuration Profile (macOS) or the `AIWATCH_ENFORCEMENT` MSI property (Windows).
</Note>

<Note>
  **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](/shadow-ai/troubleshooting) for `--ca-bundle`, `RUNLAYER_CA_BUNDLE`, `SSL_CERT_FILE`, and `REQUESTS_CA_BUNDLE` overrides.
</Note>

### How the bootstrap flow works

When `Enforcement` or `Sessions` is set, the `.pkg` / `.msi` runs an idempotent hook bootstrap: it writes Runlayer hook entries into the Cursor, VS Code, Claude Code, GitHub Copilot CLI, Codex, Hermes, and Goose configuration files as the command string `aiwatch hook --client <name>`. Existing third-party hooks are preserved. The hooks authenticate to your tenant with the same organization API key carried in the tenant config — no per-device enrollment step.

Per-platform triggers:

| Platform    | Hook install trigger                                                                                                                                                                                                                                      | Cadence              |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| **macOS**   | `com.runlayer.aiwatch.bootstrap.plist` **LaunchDaemon** (`/Library/LaunchDaemons/`) — runs `aiwatch setup hooks install --mdm` as **root**, writing Cursor and Codex enterprise configs plus console-user VS Code, Claude Code, Hermes, and Goose configs | `RunAtLoad` + hourly |
| **Windows** | `AIWatchHooks` scheduled task — registered device-locally by the MSI, runs `aiwatch setup hooks install --mdm` as **SYSTEM**, asserts hook configs where supported (no Intune Remediations)                                                               | at-boot + hourly     |

<Note>
  VS Code, Claude Code, Hermes, and Goose hooks are currently written to console-user locations for MDM scope. VS Code hooks live in `~/.copilot/hooks/runlayer.json` / `%USERPROFILE%\.copilot\hooks\runlayer.json`. GitHub Copilot CLI managed hooks use the GitHub policy directory (`/etc/github-copilot/policy.d/runlayer.json` on macOS/Linux and `C:\ProgramData\GitHub\Copilot\policy.d\runlayer.json` on Windows). Claude Code hooks are written to `~/.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`. Goose hook plugins are user-level, so MDM scope writes `~/.agents/plugins/runlayer-hooks/hooks/hooks.json` / `%USERPROFILE%\.agents\plugins\runlayer-hooks\hooks\hooks.json`.
</Note>

The hook bootstrap is idempotent — repeat ticks re-write the same `command` strings and preserve third-party entries. Flip `Enforcement` off and re-push to stop blocking; if `Sessions` stays enabled, hooks remain installed for monitoring-only telemetry. Disable both `Enforcement` and `Sessions` for Detect-only deployment with no hook install. Changes are picked up on the next `aiwatch hook` fire (no `.pkg` / `.msi` reinstall).

### MDM Deployment

Use [Deploy AI Watch](/shadow-ai/deploy) for managed rollout. In the MDM Configuration wizard, turn on **Enforce policies**. The downloaded macOS profile sets `Enforcement`; the Windows install command sets `AIWATCH_ENFORCEMENT`.

<Card title="Deploy AI Watch" icon="download" href="/shadow-ai/deploy">
  Install the package once, then configure Detect, Enforce, and Sessions.
</Card>

For non-Intune Windows MDMs, 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` or leave it absent. Hooks still register and forward events to Runlayer when `Sessions` is enabled (the default), 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/>` or absent  | absent ⇒ monitoring-only |
| Windows  | `AIWATCH_ENFORCEMENT` MSI property (writes `Enforcement` REG\_DWORD under `HKLM\Software\Runlayer\AIWatch`) | `0` or absent         | absent ⇒ monitoring-only |

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 set to `true` / `1`; `aiwatch hook` picks up the change on the next fire (no restart, no `.pkg` / `.msi` reinstall).

For Detect-only deployment with no hooks, disable Sessions too (`Sessions=false` / `AIWATCH_SESSIONS=0`).

### 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:**

<CodeGroup>
  ```bash macOS/Linux theme={null}
  curl -LsSf https://astral.sh/uv/install.sh | sh
  uv tool install runlayer
  ```

  ```powershell Windows theme={null}
  powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
  uv tool install runlayer
  ```
</CodeGroup>

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:**

```bash theme={null}
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:**

```bash theme={null}
runlayer setup hooks --install --yes --host https://your-runlayer-instance.com
```

| 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`, `vscode`, `claude_code`, `github-copilot-cli`, `codex`, `hermes`, or `goose`; 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                                                                       |

<Note>
  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.
</Note>

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:**

```bash theme={null}
runlayer setup hooks --uninstall --yes
```

### Sessions and Enforce

To collect detailed [Sessions](/platform-sessions) timelines, hooks must register the full event/session set, not just the shadow-MCP enforcement hooks. Managed deployments control this with the `Sessions` package flag in [Deploy AI Watch](/shadow-ai/deploy). Manual installs can add `--event-hooks` (or the `--all-events` alias) to the `runlayer setup hooks --install` invocation.

Local tool lifecycle scanning uses Enforce 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 telemetry.

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Hooks not intercepting calls">
    1. **Restart the AI client** after installation — Cursor, VS Code, Claude Code, Codex, Hermes, and Goose only read hook configs at process start.
    2. Confirm the hook configs are installed:
       ```bash theme={null}
       /usr/local/bin/aiwatch setup hooks check --mdm   # macOS
       & "C:\Program Files\Runlayer\AIWatch\aiwatch.exe" setup hooks check --mdm   # Windows
       ```
       Exit 0 = compliant. Exit 1 = hook configs drifted (the next bootstrap tick will rewrite them).
    3. Confirm `Enforcement` is enabled in the tenant config:
       ```bash theme={null}
       defaults read /Library/Managed\ Preferences/com.runlayer.aiwatch.plist   # macOS
       Get-ItemProperty "HKLM:\Software\Runlayer\AIWatch"                       # Windows
       ```
       If `Enforcement` is `0` / `<false/>`, hooks register but never block — re-push the profile / MSI with `Enforcement` enabled.
    4. Check that the client is supported (see table above).
  </Accordion>

  <Accordion title="`setup hooks check` exits 1 (hook configs drifted)">
    The bootstrap hasn't (re-)written the hook configs yet. Causes:

    * **Bootstrap daemon not loaded (macOS)** — Check:
      ```bash theme={null}
      sudo launchctl print system/com.runlayer.aiwatch.bootstrap
      ```
      If it returns `Could not find service`, the `.pkg` postinstall didn't bootstrap it. Re-deploy the `.pkg`, or kick it: `sudo launchctl kickstart -k system/com.runlayer.aiwatch.bootstrap`.
    * **No hook-installing capability enabled** — the bootstrap installs hooks when `Enforcement=true` or `Sessions=true` (the default). For blocking, verify `Enforcement` is enabled with the `defaults read` / `Get-ItemProperty` command above.
    * **`AIWatchHooks` task not running (Windows)** — the MSI-registered `AIWatchHooks` task runs `aiwatch setup hooks install --mdm` as **SYSTEM** (it writes SYSTEM-scoped paths under `Program Files` / `ProgramData`). Check it exists and its last result: `Get-ScheduledTaskInfo -TaskPath "\Runlayer\" -TaskName "AIWatchHooks" | Select-Object LastRunTime, LastTaskResult`. Force a run with `Start-ScheduledTask -TaskPath "\Runlayer\" -TaskName "AIWatchHooks"`, or repair/reinstall the app if the task is missing.
  </Accordion>

  <Accordion title="Performance issues">
    1. Check your Runlayer instance connectivity.
    2. Review the number of policies being evaluated.
    3. Contact Runlayer support if issues persist.
  </Accordion>
</AccordionGroup>

***

## Related Resources

<CardGroup cols={2}>
  <Card title="Enforce policy configuration" icon="sliders" href="/shadow-ai/enforce/policy">
    Allowlists and built-in tool blocks, field by field
  </Card>

  <Card title="Shadow MCP Overview" icon="eye" href="/shadow-ai">
    Understanding the shadow MCP problem
  </Card>

  <Card title="Detect" icon="search" href="/shadow-ai/detect">
    Discover shadow servers via scheduled scans
  </Card>

  <Card title="Policies" icon="shield" href="/platform-policies">
    Configure access control policies
  </Card>

  <Card title="Audit Logs" icon="list" href="/platform-audit-logs">
    View intercepted tool calls
  </Card>
</CardGroup>
