Skip to content
Last updated

Deploy Capsule Security hooks for AI coding assistants across your fleet using Palo Alto Cortex XDR or Cortex XSIAM as the delivery channel. This guide covers deployment for Cursor, Devin (Windsurf), GitHub Copilot, Claude Code, and Codex on both Windows and macOS using the Agent Script Library and the Run Endpoint Script action.

Cortex XDR is not an MDM. It has no policy engine that applies a configuration continuously to every device that joins your fleet - what it has is a one-shot response action that runs a script on the endpoints you select. Everything in this guide is built on that action, so read Key Considerations before planning a rollout.

This guide is about using Cortex XDR to push Capsule hooks to endpoints. It is unrelated to the Palo Alto Cortex XDR Integration guide, which connects your Cortex tenant to Capsule as a read-only data source for device inventory and shadow-AI discovery. The two can be used together or independently.

Prerequisites

  • Access to the Cortex XDR or Cortex XSIAM console
  • On Cortex XDR, a Cortex XDR Pro per Endpoint license - script execution is unavailable on a Prevent tenant. Palo Alto states no equivalent license gate for Cortex XSIAM
  • Cortex XDR agent 7.1 or later on every target endpoint
  • The Agent Script Library permission at View/Edit, with the Run High Risk Scripts and Script Configurations action checkboxes. Companion permissions: Action Center at View (without it the Script Library page is unreachable) and Agent Administrations at View (required to select endpoints)
  • subprocess present in the supported-modules list shown in the New Script dialog. Cortex parses every uploaded script and rejects any that imports a module outside that list, and the list is only visible in the dialog itself. Open the supported modules link there and confirm subprocess is listed before you build a rollout on the wrapper pattern below
  • Script execution still enabled on the target agents. It is disabled permanently if the agent was installed with RESTRICT_SCRIPT_EXECUTION=1 or RESTRICT_RESPONSE_ACTIONS=1, or was later disabled through Endpoint Administration. Re-enabling requires reinstalling the agent
  • A Capsule Security account with admin access

How Cortex Runs These Scripts

Three different things in Cortex are called "scripts"

Only the first one reaches an endpoint.

FeatureWhere it runsUse it here?
Agent Script Library (Investigation & Response > Response > Action Center)On the endpoint, via the agent. Python 3.7 onlyYes - this is the fleet channel
Live Terminal > Command Line (Investigation & Response > Response)On the endpoint, interactively. PowerShell 5.0 or later on Windows, the shell on macOSFor testing one machine, see below
Automation > Scripts (Investigation & Response > Automation)On the Cortex server, in a Docker container. JavaScript, Python, or PowerShellNo - it never touches an endpoint

The Agent Script Library accepts Python 3.7 scripts only. You cannot upload a .ps1 or a .sh file to it, and there is no language selector in the upload dialog. Capsule's deployment artifacts are PowerShell and bash, so each one travels inside a short Python wrapper that writes it to a file on the endpoint and executes it.

Scripts run as SYSTEM on Windows and as root on macOS. Environment variables and per-user paths are therefore not the signed-in user's, and HKEY_CURRENT_USER is unreachable.

The Python wrapper

Paste the product's PowerShell or bash script from the sections below in place of the placeholder. Both wrappers print the wrapped script's output so it appears under Standard output in the results table, run the child process with empty stdin so it never hangs waiting for input, print the exit code, and raise on a non-zero exit so Cortex marks the endpoint Failed. A script that expects input reads nothing and ends early, and it can still exit 0, so a green result alone does not prove anything was installed.

Windows (capsule-deploy-windows.py):

import os
import subprocess

SCRIPT = r"""
<PASTE THE PRODUCT'S WINDOWS POWERSHELL SCRIPT HERE>
"""

script_path = "C:/Windows/Temp/capsule-deploy.ps1"

with open(script_path, "w", encoding="utf-8-sig") as handle:
    handle.write(SCRIPT)

returncode = 1
try:
    result = subprocess.run(
        ["powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script_path],
        stdin=subprocess.DEVNULL,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        timeout=540,
    )
    print(result.stdout.decode("utf-8", "replace"))
    returncode = result.returncode
except subprocess.TimeoutExpired as error:
    if error.output:
        print(error.output.decode("utf-8", "replace"))
    print("Capsule: script did not finish within 540 seconds")
finally:
    os.remove(script_path)

print("Capsule: exit code %d" % returncode)
if returncode != 0:
    raise SystemExit("Capsule deployment failed with exit code %d" % returncode)

macOS (capsule-deploy-macos.py):

import os
import subprocess

SCRIPT = r"""
<PASTE THE PRODUCT'S MACOS BASH SCRIPT HERE>
"""

script_dir = "/var/tmp/capsule"
script_path = script_dir + "/capsule-deploy.sh"

os.makedirs(script_dir, exist_ok=True)
os.chmod(script_dir, 0o700)

with open(script_path, "w", encoding="utf-8") as handle:
    handle.write(SCRIPT)
os.chmod(script_path, 0o700)

returncode = 1
try:
    result = subprocess.run(
        ["/bin/bash", script_path],
        stdin=subprocess.DEVNULL,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        timeout=540,
    )
    print(result.stdout.decode("utf-8", "replace"))
    returncode = result.returncode
except subprocess.TimeoutExpired as error:
    if error.output:
        print(error.output.decode("utf-8", "replace"))
    print("Capsule: script did not finish within 540 seconds")
finally:
    os.remove(script_path)

print("Capsule: exit code %d" % returncode)
if returncode != 0:
    raise SystemExit("Capsule deployment failed with exit code %d" % returncode)

The wrapped script is held in a Python raw triple-quoted string, so it must not itself contain the """ sequence. Capsule's configuration files and companion scripts do not. Every payload in this guide is embedded in the script you upload, so the wrapper downloads nothing and needs no network access from the endpoint.

Both wrappers import only os and subprocess. Cortex rejects a script that imports a module outside its supported list, and that list is shown only in the New Script dialog - follow its supported modules link and confirm subprocess is listed before you upload.

Uploading the script

  1. Navigate to Investigation & Response > Response > Action Center > Agent Script Library (the grid is called Scripts Library in Cortex XSIAM)
  2. Click New Script and drag in the .py wrapper file. Cortex parses it for unsupported modules and indentation problems, and offers an in-product editor if it finds either
  3. Complete the General step with the Name, Description, Platform, and Timeout values given in each product section. Set Outcome to High Risk: these scripts write files and change system configuration, which is Palo Alto's own definition of a high-risk script, so the running role needs the Run High Risk Scripts permission
  4. On the Input step choose Just run, which executes the script line by line. No entry point or parameters are needed
  5. Leave the Output step empty. Progress is reported through stdout

You can also upload the script and its definitions together as a single-line JSON manifest. The manifest must contain no newline or tab characters or validation fails:

{"name":"Capsule Security - Cursor Hooks (Windows)","description":"Deploys the Capsule Security hooks configuration for Cursor","outcome":"High Risk","platform":"Windows","timeout":600,"entry_point":"","entry_point_definition":{}}

Set platform to Windows or macOS. A script with both targets takes "Windows,macOS", but the payloads here are OS-specific, so upload one script per OS.

Running the action

  1. Navigate to Investigation & Response > Response > Action Center and click New Action
  2. Select Run Endpoint Script and pick the script you uploaded. You can also right-click the script in the Scripts Library and choose Run, which opens the Action Center with the script already populated
  3. Select the target endpoints. Cortex displays only the endpoints eligible for the action, based on the script's platform value, and tells you which agents in your scope will be skipped. Narrow the endpoint list first using Group Names, Operating System, OS Version, Agent Version, Tags, or Active Directory groups and organizational units, then select the rows
  4. Review the summary and click Done

Live Terminal (single endpoint)

Before running a fleet action, test the same PowerShell or bash on one machine. Live Terminal runs it directly, with no Python wrapper and no upload.

  1. Navigate to Inventory > Endpoints > All Endpoints, right-click the endpoint, and select Security Operations > Initiate Live Terminal
  2. Select Command Line. This runs PowerShell 5.0 or later on Windows and the shell on macOS
  3. Paste the product's script, or upload it as a file, and press Shift + Enter to run it. Chain multiple commands with &&

A payload that works in Live Terminal can still fail under the Agent Script Library, which runs as SYSTEM or root with no user session, so always run the wrapped script against one endpoint through Run Endpoint Script before you start a fleet wave. Live Terminal is one endpoint per session with no multi-select and no API, so it is a test and troubleshooting tool, not a deployment channel. On Windows it needs Traps 6.1 or later, Windows 7 SP1 or later, and the WinCRT patch KB 2999226. On macOS it needs Cortex XDR agent 7.0 or later and macOS 10.12 or later. On either, the endpoint must have reported activity within the last 90 minutes.


Cursor

Deploy the Cursor hooks configuration with a Run Endpoint Script action.

Step 1: Download the Configuration

  1. Log in to the Capsule Security portal
  2. Navigate to Settings > Integrations
  3. Locate the Cursor integration
  4. Download the hooks.json file

Cortex XDR Deployment

Upload one script per OS, following Uploading the script, then run it as described in Running the action.

Windows - Name Capsule Security - Cursor Hooks (Windows), platform Windows:

{"name":"Capsule Security - Cursor Hooks (Windows)","description":"Deploys the Capsule Security hooks configuration for Cursor","outcome":"High Risk","platform":"Windows","timeout":600,"entry_point":"","entry_point_definition":{}}

Wrap this PowerShell in the Windows wrapper, replacing the placeholder with the contents of hooks.json:

$hooksDir = "C:\ProgramData\Cursor"
$hooksFile = "$hooksDir\hooks.json"

$payload = @'
<PASTE YOUR HOOKS.JSON CONTENT HERE>
'@

if (-not (Test-Path $hooksDir)) {
    New-Item -ItemType Directory -Path $hooksDir -Force | Out-Null
}

[System.IO.File]::WriteAllText($hooksFile, $payload, [System.Text.UTF8Encoding]::new($false))

Write-Output "Capsule: wrote Cursor hooks configuration to $hooksFile"
exit 0

macOS - Name Capsule Security - Cursor Hooks (macOS), platform macOS:

{"name":"Capsule Security - Cursor Hooks (macOS)","description":"Deploys the Capsule Security hooks configuration for Cursor on macOS","outcome":"High Risk","platform":"macOS","timeout":600,"entry_point":"","entry_point_definition":{}}

Wrap this bash in the macOS wrapper, replacing the placeholder with the contents of hooks.json and keeping the CAPSULE_EOF marker lines unchanged and unindented:

#!/bin/bash
set -e
HOOKS_DIR="/Library/Application Support/Cursor"
mkdir -p "$HOOKS_DIR"
cat > "$HOOKS_DIR/hooks.json" << 'CAPSULE_EOF'
<PASTE YOUR HOOKS.JSON CONTENT HERE>
CAPSULE_EOF
chmod 644 "$HOOKS_DIR/hooks.json"
echo "Capsule: wrote Cursor hooks configuration to $HOOKS_DIR/hooks.json"
exit 0

Verification

After deployment, restart Cursor on target devices, then:

  1. Open Cursor Settings
  2. Navigate to the Hooks tab
  3. Confirm the hooks are listed and enabled

Uninstall

Cortex cannot take a .ps1 or a .sh file, so wrap the removal script below in the same Python wrapper and upload it as a second library script, for example Capsule Security - Cursor hooks removal.

Upload and run a second script that deletes the file this deployment wrote. Each delete is conditional on the file still carrying Capsule's /cursor/hooks/events marker, so a device where someone replaced the file with their own hooks keeps it. That marker check is the only thing standing between the delete and a file you own, because nothing is copied aside first.

$path = 'C:\ProgramData\Cursor\hooks.json'
if ((Test-Path -LiteralPath $path) -and ((Get-Content -LiteralPath $path -Raw) -like '*/cursor/hooks/events*')) {
    Remove-Item -LiteralPath $path -Force
    Write-Output "Capsule: removed $path"
}
exit 0
#!/bin/bash
FILE="/Library/Application Support/Cursor/hooks.json"
if grep -qF '/cursor/hooks/events' "$FILE" 2>/dev/null; then
    rm -f "$FILE"
    echo "Capsule: removed $FILE"
fi
exit 0

Devin (Windsurf)

Deploy the Devin (Windsurf) hooks configuration with a Run Endpoint Script action. Cascade merges hook configurations from the system, user, and workspace levels (Cascade hooks documentation), so deploying to the system-level path applies the Capsule hooks to every user on the device, and they cannot be disabled by end users.

Step 1: Download the Configuration

  1. Log in to the Capsule Security portal
  2. Navigate to Settings > Integrations
  3. Locate the Devin (Windsurf) integration
  4. For Windows endpoints, select Windows and extract the downloaded capsule-windsurf-managed.zip (contains hooks.json and the CapsuleWindsurfHook.ps1 companion script)
  5. For macOS endpoints, select Mac and download the hooks.json file

Cortex XDR Deployment

Upload one script per OS, following Uploading the script, then run it as described in Running the action.

Windows - Name Capsule Security - Devin (Windsurf) Hooks (Windows), platform Windows:

{"name":"Capsule Security - Devin (Windsurf) Hooks (Windows)","description":"Deploys the Capsule Security hooks configuration for Devin (Windsurf)","outcome":"High Risk","platform":"Windows","timeout":600,"entry_point":"","entry_point_definition":{}}

The hooks.json invokes the companion script by path, so both files must be written. Replace each placeholder in the installer below with the contents of the matching file from the archive, then copy the whole installer, start to finish, and paste it into the Windows wrapper's own placeholder - upload the resulting wrapper script, not the installer by itself:

$ErrorActionPreference = "Stop"

function Write-CapsuleFile($path, $content) {
    $parentDir = Split-Path -Path $path -Parent
    if (-not (Test-Path -Path $parentDir)) {
        New-Item -ItemType Directory -Path $parentDir -Force | Out-Null
    }
    [System.IO.File]::WriteAllText($path, $content, [System.Text.UTF8Encoding]::new($false))
    Write-Output "Capsule: wrote $path"
}

$hookScript = @'
<PASTE YOUR CapsuleWindsurfHook.ps1 CONTENT HERE>
'@
Write-CapsuleFile 'C:\ProgramData\Capsule\WindsurfHooks\CapsuleWindsurfHook.ps1' $hookScript

$hooksJson = @'
<PASTE YOUR HOOKS.JSON CONTENT HERE>
'@
Write-CapsuleFile 'C:\ProgramData\Windsurf\hooks.json' $hooksJson

exit 0

macOS - Name Capsule Security - Devin (Windsurf) Hooks (macOS), platform macOS:

{"name":"Capsule Security - Devin (Windsurf) Hooks (macOS)","description":"Deploys the Capsule Security hooks configuration for Devin (Windsurf) on macOS","outcome":"High Risk","platform":"macOS","timeout":600,"entry_point":"","entry_point_definition":{}}

Wrap this bash in the macOS wrapper, replacing the placeholder with the contents of hooks.json and keeping the CAPSULE_EOF marker lines unchanged and unindented:

#!/bin/bash
set -e
HOOKS_DIR="/Library/Application Support/Windsurf"
mkdir -p "$HOOKS_DIR"
cat > "$HOOKS_DIR/hooks.json" << 'CAPSULE_EOF'
<PASTE YOUR HOOKS.JSON CONTENT HERE>
CAPSULE_EOF
chmod 644 "$HOOKS_DIR/hooks.json"
echo "Capsule: wrote Devin (Windsurf) hooks configuration to $HOOKS_DIR/hooks.json"
exit 0

Verification

Hooks are loaded at application start, so after deployment, close Devin Desktop completely on target devices and reopen it, then:

  1. Start a Cascade session and run a simple task to generate activity
  2. In the Capsule portal, navigate to Inventory > Agents and confirm the agent appears

Uninstall

Cortex cannot take a .ps1 or a .sh file, so wrap the removal script below in the same Python wrapper and upload it as a second library script, for example Capsule Security - Windsurf hooks removal.

Upload and run a second script that deletes the files this deployment wrote. Each delete is conditional on the file still carrying Capsule's /windsurf/hooks/events marker. CapsuleWindsurfHook.ps1 and the directories holding it are Capsule's own, and those directories are removed once empty, innermost first. C:\ProgramData\Windsurf is the product's own directory and stays.

$config = 'C:\ProgramData\Windsurf\hooks.json'
$hookScript = 'C:\ProgramData\Capsule\WindsurfHooks\CapsuleWindsurfHook.ps1'

foreach ($path in @($config, $hookScript)) {
    if ((Test-Path -LiteralPath $path) -and ((Get-Content -LiteralPath $path -Raw) -like '*/windsurf/hooks/events*')) {
        Remove-Item -LiteralPath $path -Force
        Write-Output "Capsule: removed $path"
    }
}

foreach ($dir in @('C:\ProgramData\Capsule\WindsurfHooks', 'C:\ProgramData\Capsule')) {
    if ((Test-Path -LiteralPath $dir) -and -not (Get-ChildItem -LiteralPath $dir -Force)) {
        Remove-Item -LiteralPath $dir -Force
    }
}
exit 0
#!/bin/bash
FILE="/Library/Application Support/Windsurf/hooks.json"
if grep -qF '/windsurf/hooks/events' "$FILE" 2>/dev/null; then
    rm -f "$FILE"
    echo "Capsule: removed $FILE"
fi
exit 0

GitHub Copilot

Deploy the GitHub Copilot hooks configuration with a Run Endpoint Script action. GitHub Copilot reads its hooks from a per-user directory, and Cortex runs every script as SYSTEM or root, so the scripts below iterate over the user profiles present on the device.

Step 1: Download the Configuration

  1. Log in to the Capsule Security portal
  2. Navigate to Settings > Integrations
  3. Locate the GitHub Copilot integration
  4. Download the capsule-hooks.json file

Cortex XDR Deployment

Upload one script per OS, following Uploading the script, then run it as described in Running the action.

Windows - Name Capsule Security - GitHub Copilot Hooks (Windows), platform Windows:

{"name":"Capsule Security - GitHub Copilot Hooks (Windows)","description":"Deploys the Capsule Security hooks configuration for GitHub Copilot in VS Code","outcome":"High Risk","platform":"Windows","timeout":600,"entry_point":"","entry_point_definition":{}}

Wrap this PowerShell in the Windows wrapper, replacing the placeholder with the contents of capsule-hooks.json:

$payload = @'
<PASTE YOUR CAPSULE-HOOKS.JSON CONTENT HERE>
'@

$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
$userProfiles = Get-ChildItem "C:\Users" -Directory | Where-Object { $_.Name -notin @('Public', 'Default', 'Default User') }

foreach ($userProfile in $userProfiles) {
    $hooksDir = Join-Path $userProfile.FullName "AppData\Roaming\Code\User\hooks"
    if (-not (Test-Path $hooksDir)) {
        New-Item -ItemType Directory -Path $hooksDir -Force | Out-Null
    }
    [System.IO.File]::WriteAllText((Join-Path $hooksDir 'capsule-hooks.json'), $payload, $utf8NoBom)
}

Write-Output "Capsule: wrote GitHub Copilot hooks configuration for $($userProfiles.Count) user profile(s)"
exit 0

macOS - Name Capsule Security - GitHub Copilot Hooks (macOS), platform macOS:

{"name":"Capsule Security - GitHub Copilot Hooks (macOS)","description":"Deploys the Capsule Security hooks configuration for GitHub Copilot on macOS","outcome":"High Risk","platform":"macOS","timeout":600,"entry_point":"","entry_point_definition":{}}

Wrap this bash in the macOS wrapper, replacing the placeholder with the contents of capsule-hooks.json and keeping the CAPSULE_EOF marker lines unchanged and unindented:

#!/bin/bash
PAYLOAD=$(cat << 'CAPSULE_EOF'
<PASTE YOUR CAPSULE-HOOKS.JSON CONTENT HERE>
CAPSULE_EOF
)

for USER_HOME in /Users/*/; do
    USERNAME=$(basename "$USER_HOME")
    if [ "$USERNAME" = "Shared" ] || [ "$USERNAME" = ".localized" ]; then
        continue
    fi
    HOOKS_DIR="$USER_HOME/Library/Application Support/Code/User/hooks"
    mkdir -p "$HOOKS_DIR"
    printf '%s\n' "$PAYLOAD" > "$HOOKS_DIR/capsule-hooks.json"
    chmod 644 "$HOOKS_DIR/capsule-hooks.json"
    chown "$USERNAME" "$HOOKS_DIR/capsule-hooks.json"
    echo "Capsule: wrote $HOOKS_DIR/capsule-hooks.json"
done
exit 0

Verification

After deployment, restart VS Code on target devices, then:

  1. Right-click in the Chat view and select Diagnostics
  2. Confirm the hooks are loaded and enabled

Uninstall

Cortex cannot take a .ps1 or a .sh file, so wrap the removal script below in the same Python wrapper and upload it as a second library script, for example Capsule Security - GitHub Copilot hooks removal.

Upload and run a second script that deletes the files this deployment wrote, across the same user profiles. Each delete is conditional on the file still carrying Capsule's /github-copilot/hooks/events marker. Leave the hooks directory in place: it is Copilot's, not one Capsule created.

$userProfiles = Get-ChildItem "C:\Users" -Directory | Where-Object { $_.Name -notin @('Public', 'Default', 'Default User') }

foreach ($userProfile in $userProfiles) {
    $path = Join-Path $userProfile.FullName "AppData\Roaming\Code\User\hooks\capsule-hooks.json"
    if ((Test-Path -LiteralPath $path) -and ((Get-Content -LiteralPath $path -Raw) -like '*/github-copilot/hooks/events*')) {
        Remove-Item -LiteralPath $path -Force
        Write-Output "Capsule: removed $path"
    }
}
exit 0
#!/bin/bash
for USER_HOME in /Users/*/; do
    FILE="$USER_HOME/Library/Application Support/Code/User/hooks/capsule-hooks.json"
    if grep -qF '/github-copilot/hooks/events' "$FILE" 2>/dev/null; then
        rm -f "$FILE"
        echo "Capsule: removed $FILE"
    fi
done
exit 0

Claude Code

Deploy the Claude Code managed settings configuration with a Run Endpoint Script action. On Windows every Capsule hook command in managed-settings.json invokes an external dispatcher by path, so deployment writes two files - managed-settings.json to C:\Program Files\ClaudeCode\ and capsule-hook.ps1 to C:\Program Files\ClaudeCode\hooks\. macOS writes the one settings file. Both platforms write each file wholesale, replacing whatever is at that path.

Step 1: Download the Configuration

  1. Log in to the Capsule Security portal
  2. Navigate to Settings > Integrations
  3. Locate the Claude Code integration
  4. Click Install and select your target platform
  5. For Windows endpoints, download the capsule-claude-code-windows.zip archive Capsule generates: it contains managed-settings.json and the hooks\capsule-hook.ps1 dispatcher it invokes
  6. For macOS endpoints, download the managed-settings.json file

Cortex XDR Deployment

Upload one script per OS, following Uploading the script, then run it as described in Running the action.

Windows - Name Capsule Security - Claude Code Hooks (Windows), platform Windows:

{"name":"Capsule Security - Claude Code Hooks (Windows)","description":"Deploys the Capsule Security managed settings for Claude Code","outcome":"High Risk","platform":"Windows","timeout":600,"entry_point":"","entry_point_definition":{}}

Every Capsule hook command in managed-settings.json runs powershell -NoP -Exec Bypass -File "C:\Program Files\ClaudeCode\hooks\capsule-hook.ps1", so both files must be written - deploying managed-settings.json alone would leave every hook pointing at a script that was never installed.

Never paste capsule-hook.ps1 into the Windows wrapper on its own, and never upload or run it by itself. It is Claude Code's hook dispatcher, not an installer: it expects a hook call on stdin, so run any other way it either waits for input until Cortex's timeout or, given no input, exits 0 without writing anything, and the endpoint reports success with neither file installed. It belongs only inside the installer below, which writes it to disk and never runs it.

Build the script in this order:

  1. Extract the capsule-claude-code-windows.zip archive from Step 1. It holds managed-settings.json and hooks\capsule-hook.ps1
  2. In the installer below, replace <PASTE YOUR managed-settings.json CONTENT HERE> with the full contents of managed-settings.json, and <PASTE YOUR hooks\capsule-hook.ps1 CONTENT HERE> with the full contents of hooks\capsule-hook.ps1. Replace only the placeholder lines, and keep the $content0 = @', $content1 = @' and '@ lines around them unchanged. PowerShell requires each closing '@ to start at column 0, with nothing before it on its line
  3. Copy the whole installer, start to finish, and paste it into the Windows wrapper's <PASTE THE PRODUCT'S WINDOWS POWERSHELL SCRIPT HERE> placeholder
  4. Upload the resulting wrapper script, not the installer by itself, and run it

This is the installer Capsule's own Windows distribution generates, with the two file contents left as placeholders:

$ErrorActionPreference = "Stop"
function Write-CapsuleFile($path, $content, $label) {
  $content = $content.Replace("`r`n", "`n")
  $parentDir = Split-Path -LiteralPath $path
  if (-not (Test-Path -LiteralPath $parentDir)) {
    New-Item -ItemType Directory -Path $parentDir -Force | Out-Null
  }
  $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
  [System.IO.File]::WriteAllText($path, $content, $utf8NoBom)
  Write-Output "Capsule: wrote $label to $path"
}
try {
  $content0 = @'
<PASTE YOUR managed-settings.json CONTENT HERE>
'@
  Write-CapsuleFile 'C:\Program Files\ClaudeCode\managed-settings.json' $content0 'Claude Code managed settings'
  $content1 = @'
<PASTE YOUR hooks\capsule-hook.ps1 CONTENT HERE>
'@
  Write-CapsuleFile 'C:\Program Files\ClaudeCode\hooks\capsule-hook.ps1' $content1 'Claude Code hooks/capsule-hook.ps1'
  exit 0
} catch {
  Write-Error "Capsule: failed to write managed hook files. $_"
  exit 1
}

macOS - Name Capsule Security - Claude Code Hooks (macOS), platform macOS:

{"name":"Capsule Security - Claude Code Hooks (macOS)","description":"Deploys the Capsule Security managed settings for Claude Code on macOS","outcome":"High Risk","platform":"macOS","timeout":600,"entry_point":"","entry_point_definition":{}}

Wrap this bash in the macOS wrapper, replacing the placeholder with the contents of managed-settings.json and keeping the CAPSULE_EOF marker lines unchanged and unindented:

#!/bin/bash
set -e
SETTINGS_DIR="/Library/Application Support/ClaudeCode"
mkdir -p "$SETTINGS_DIR"
cat > "$SETTINGS_DIR/managed-settings.json" << 'CAPSULE_EOF'
<PASTE YOUR MANAGED-SETTINGS.JSON CONTENT HERE>
CAPSULE_EOF
chmod 644 "$SETTINGS_DIR/managed-settings.json"
echo "Capsule: wrote Claude Code managed settings to $SETTINGS_DIR/managed-settings.json"
exit 0

Verification

On Windows, a successful run's Standard output shows two Capsule: wrote ... lines, one per file:

Capsule: wrote Claude Code managed settings to C:\Program Files\ClaudeCode\managed-settings.json
Capsule: wrote Claude Code hooks/capsule-hook.ps1 to C:\Program Files\ClaudeCode\hooks\capsule-hook.ps1

A green run without both lines means the wrong script was pasted into the wrapper: upload the installer above, wrapped, and run it again.

After deployment, restart Claude Code on target devices, then:

  1. Run /hooks in Claude Code to confirm all hooks are listed
  2. Start a session and verify events appear in the Capsule Security portal

Uninstall

Cortex cannot take a .ps1 or a .sh file, so wrap the removal script below in the same Python wrapper and upload it as a second library script, for example Capsule Security - Claude Code hooks removal.

managed-settings.json is Claude Code's enterprise policy file. This deployment wrote the whole file, so the reversal deletes the whole file - but only while it still carries a Capsule marker, so a file an admin has since rewritten with their own policy is left alone. The Windows removal takes both files and matches on capsule-hook.ps1 as well as capsulesecurity, because every Capsule entry in managed-settings.json runs the dispatcher and the Capsule endpoint lives inside that script rather than in the settings file.

$paths = @(
    'C:\Program Files\ClaudeCode\managed-settings.json',
    'C:\Program Files\ClaudeCode\hooks\capsule-hook.ps1'
)
foreach ($path in $paths) {
    if ((Test-Path -LiteralPath $path) -and ((Get-Content -LiteralPath $path -Raw) -match 'capsulesecurity|capsule-hook\.ps1')) {
        Remove-Item -LiteralPath $path -Force
        Write-Output "Capsule: removed $path"
    }
}
exit 0
#!/bin/bash
FILE="/Library/Application Support/ClaudeCode/managed-settings.json"
if grep -qF 'capsulesecurity' "$FILE" 2>/dev/null; then
    rm -f "$FILE"
    echo "Capsule: removed $FILE"
fi
exit 0

If you merged Capsule's hooks into a managed-settings.json your organization already owned, do not run this. Edit the file to drop only the Capsule hook entries instead.


Codex

Deploy the Codex managed configuration with a Run Endpoint Script action. Codex reads a managed requirements.toml from /etc/codex/requirements.toml (macOS) or %ProgramData%\OpenAI\Codex\requirements.toml (Windows); because the configuration is managed, the Capsule hooks are trusted by policy, apply to every user on the device (including users created later), and cannot be overridden or removed by users. No per-user trust approval is required.

Step 1: Download the Configuration

  1. Log in to the Capsule Security portal
  2. Navigate to Settings > Integrations
  3. Locate the Codex integration
  4. For Windows endpoints, select Windows and extract the downloaded capsule-codex-managed.zip (contains requirements.toml and the CapsuleCodexHook.ps1 companion script)
  5. For macOS endpoints, select Mac and download the requirements.toml file

Cortex XDR Deployment

Upload one script per OS, following Uploading the script, then run it as described in Running the action.

Windows - Name Capsule Security - Codex Managed Configuration (Windows), platform Windows:

{"name":"Capsule Security - Codex Managed Configuration (Windows)","description":"Deploys the Capsule Security managed configuration for Codex","outcome":"High Risk","platform":"Windows","timeout":600,"entry_point":"","entry_point_definition":{}}

The managed configuration invokes CapsuleCodexHook.ps1 by path, so both files must be written - deploying requirements.toml alone would leave every hook pointing at a script that was never installed. Replace each placeholder in the installer below with the contents of the matching file from the archive, then copy the whole installer, start to finish, and paste it into the Windows wrapper's own placeholder - upload the resulting wrapper script, not the installer by itself:

$ErrorActionPreference = "Stop"

function Write-CapsuleFile($path, $content) {
    $parentDir = Split-Path -Path $path -Parent
    if (-not (Test-Path -Path $parentDir)) {
        New-Item -ItemType Directory -Path $parentDir -Force | Out-Null
    }
    [System.IO.File]::WriteAllText($path, $content, [System.Text.UTF8Encoding]::new($false))
    Write-Output "Capsule: wrote $path"
}

$hookScript = @'
<PASTE YOUR CapsuleCodexHook.ps1 CONTENT HERE>
'@
Write-CapsuleFile 'C:\ProgramData\Capsule\CodexHooks\CapsuleCodexHook.ps1' $hookScript

$requirementsToml = @'
<PASTE YOUR REQUIREMENTS.TOML CONTENT HERE>
'@
Write-CapsuleFile 'C:\ProgramData\OpenAI\Codex\requirements.toml' $requirementsToml

exit 0

macOS - Name Capsule Security - Codex Managed Configuration (macOS), platform macOS:

{"name":"Capsule Security - Codex Managed Configuration (macOS)","description":"Deploys the Capsule Security managed configuration for Codex on macOS","outcome":"High Risk","platform":"macOS","timeout":600,"entry_point":"","entry_point_definition":{}}

Wrap this bash in the macOS wrapper, replacing the placeholder with the contents of requirements.toml and keeping the CAPSULE_EOF marker lines unchanged and unindented:

#!/bin/bash
set -e
CODEX_DIR="/etc/codex"
mkdir -p "$CODEX_DIR"
cat > "$CODEX_DIR/requirements.toml" << 'CAPSULE_EOF'
<PASTE YOUR REQUIREMENTS.TOML CONTENT HERE>
CAPSULE_EOF
chmod 644 "$CODEX_DIR/requirements.toml"
echo "Capsule: wrote Codex managed configuration to $CODEX_DIR/requirements.toml"
exit 0

Verification

After deployment, close any running Codex CLI sessions on target devices, then:

  1. Start a new Codex CLI session so the managed configuration is loaded
  2. Run /hooks and confirm the Capsule hooks are listed as managed
  3. In the Capsule portal, navigate to Inventory > Agents and confirm the Codex agent appears

Uninstall

Cortex cannot take a .ps1 or a .sh file, so wrap the removal script below in the same Python wrapper and upload it as a second library script, for example Capsule Security - Codex hooks removal.

Upload and run a second script that deletes the files this deployment wrote. Each delete is conditional on the file still carrying Capsule's /codex/hooks/events marker. CapsuleCodexHook.ps1 and the directories holding it are Capsule's own, and those directories are removed once empty, innermost first. C:\ProgramData\OpenAI\Codex is Codex's own directory and stays.

$config = 'C:\ProgramData\OpenAI\Codex\requirements.toml'
$hookScript = 'C:\ProgramData\Capsule\CodexHooks\CapsuleCodexHook.ps1'

foreach ($path in @($config, $hookScript)) {
    if ((Test-Path -LiteralPath $path) -and ((Get-Content -LiteralPath $path -Raw) -like '*/codex/hooks/events*')) {
        Remove-Item -LiteralPath $path -Force
        Write-Output "Capsule: removed $path"
    }
}

foreach ($dir in @('C:\ProgramData\Capsule\CodexHooks', 'C:\ProgramData\Capsule')) {
    if ((Test-Path -LiteralPath $dir) -and -not (Get-ChildItem -LiteralPath $dir -Force)) {
        Remove-Item -LiteralPath $dir -Force
    }
}
exit 0
#!/bin/bash
FILE="/etc/codex/requirements.toml"
if grep -qF '/codex/hooks/events' "$FILE" 2>/dev/null; then
    rm -f "$FILE"
    echo "Capsule: removed $FILE"
fi
exit 0

Key Considerations

  • There is no continuous enforcement. Run Endpoint Script is a one-shot action, not a policy. An endpoint that enrolls, is rebuilt, or has the file removed after the action ran is not covered until you run the action again. Cortex offers Rerun and Run on additional agents for that, but nothing schedules them. If you need a configuration that reapplies itself, deploy these hooks through an MDM and use Cortex only where an MDM does not reach
  • Actions expire after four days. An endpoint that is offline longer than that moves from Pending to Expired and never receives the action. Plan a second wave for stragglers
  • Scripts run as SYSTEM on Windows and root on macOS, so environment variables and per-user paths are not the signed-in user's
  • Cursor, Devin (Windsurf), Claude Code, and Codex write to system-level paths, which suits the SYSTEM and root context directly
  • GitHub Copilot uses per-user paths, so its scripts iterate over the user profiles on the device. Users created after the action runs are not covered until you run it again
  • WSL is out of scope for this channel. Capsule's Windows WSL companion has to run in each user's context to enumerate that user's WSL distributions, and Cortex only runs scripts as SYSTEM. Deploy the WSL companion through an MDM that supports user-context scripts, such as Microsoft Intune
  • Scope a rollout by filtering the endpoint table on Group Names, Operating System, OS Version, Agent Version, Tags, or Active Directory groups, then selecting the resulting rows. Endpoint groups are a filter here, not a policy target
  • Automating with POST /public_api/v1/scripts/run_script caps each call at 1000 endpoints and accepts only an endpoint_id_list filter, so large fleets need batching and the endpoint IDs resolved first
  • Codex uses a managed configuration enforced by policy - it applies to all users on the device (including users created later) and cannot be overridden by user-level configuration
  • Devin (Windsurf) merges system, user, and workspace hook configurations, so the system-level deployment coexists with any per-user hooks and cannot be disabled by end users
  • Restart each application on target devices after deployment for hooks to take effect
  • All five integrations can be uploaded as separate scripts and run independently

Monitoring Deployments

Results are pull-based. Cortex sends no notification when an action finishes, so watch the Action Center.

  1. Navigate to Investigation & Response > Response > Action Center
  2. Right-click the action and select Additional data to open Detailed Results. The upper bar shows the script name, status, and target scope; the per-endpoint table shows Status, Execution timestamp, and Standard output - which is where the Capsule: wrote ... lines from the scripts above appear
  3. Read the per-endpoint status:
    • Completed Successfully: the script ran and exited cleanly
    • Pending: the endpoint has not checked in yet. It has four days
    • In Progress: the agent is running the script now
    • Failed: the agent could not execute the script, or the script raised an exception
    • Timeout: the script exceeded the timeout you configured and the agent stopped it
    • Expired: the four-day window elapsed before the endpoint checked in
    • Canceled / Aborted: stopped before the agent pulled the request, or after it started

Right-click an endpoint row to download its stdout or view exceptions, and use Export to file for the whole table. To watch results arrive live, right-click a running action and select Open in interactive mode.

To stop a rollout, right-click the action and choose Cancel for pending endpoints, or Abort and cancel execution for endpoints already running it.


References


Support

For help with deployment:

  • Email: support@capsule.security
  • Include: Your organization ID, Cortex XDR or XSIAM tenant details, the action ID from the Action Center, and any error messages