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.
- 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)
subprocesspresent 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 confirmsubprocessis 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=1orRESTRICT_RESPONSE_ACTIONS=1, or was later disabled through Endpoint Administration. Re-enabling requires reinstalling the agent - A Capsule Security account with admin access
Only the first one reaches an endpoint.
| Feature | Where it runs | Use it here? |
|---|---|---|
| Agent Script Library (Investigation & Response > Response > Action Center) | On the endpoint, via the agent. Python 3.7 only | Yes - 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 macOS | For testing one machine, see below |
| Automation > Scripts (Investigation & Response > Automation) | On the Cortex server, in a Docker container. JavaScript, Python, or PowerShell | No - 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.
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
osandsubprocess. 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 confirmsubprocessis listed before you upload.
- Navigate to Investigation & Response > Response > Action Center > Agent Script Library (the grid is called Scripts Library in Cortex XSIAM)
- Click New Script and drag in the
.pywrapper file. Cortex parses it for unsupported modules and indentation problems, and offers an in-product editor if it finds either - 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
- On the Input step choose Just run, which executes the script line by line. No entry point or parameters are needed
- 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.
- Navigate to Investigation & Response > Response > Action Center and click New Action
- 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
- Select the target endpoints. Cortex displays only the endpoints eligible for the action, based on the script's
platformvalue, 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 - Review the summary and click Done
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.
- Navigate to Inventory > Endpoints > All Endpoints, right-click the endpoint, and select Security Operations > Initiate Live Terminal
- Select Command Line. This runs PowerShell 5.0 or later on Windows and the shell on macOS
- 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.
Deploy the Cursor hooks configuration with a Run Endpoint Script action.
- Log in to the Capsule Security portal
- Navigate to Settings > Integrations
- Locate the Cursor integration
- Download the
hooks.jsonfile
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 0macOS - 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 0After deployment, restart Cursor on target devices, then:
- Open Cursor Settings
- Navigate to the Hooks tab
- Confirm the hooks are listed and enabled
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 0Deploy 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.
- Log in to the Capsule Security portal
- Navigate to Settings > Integrations
- Locate the Devin (Windsurf) integration
- For Windows endpoints, select Windows and extract the downloaded
capsule-windsurf-managed.zip(containshooks.jsonand theCapsuleWindsurfHook.ps1companion script) - For macOS endpoints, select Mac and download the
hooks.jsonfile
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 0macOS - 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 0Hooks are loaded at application start, so after deployment, close Devin Desktop completely on target devices and reopen it, then:
- Start a Cascade session and run a simple task to generate activity
- In the Capsule portal, navigate to Inventory > Agents and confirm the agent appears
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 0Deploy 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.
- Log in to the Capsule Security portal
- Navigate to Settings > Integrations
- Locate the GitHub Copilot integration
- Download the
capsule-hooks.jsonfile
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 0macOS - 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 0After deployment, restart VS Code on target devices, then:
- Right-click in the Chat view and select Diagnostics
- Confirm the hooks are loaded and enabled
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 0Deploy 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.
- Log in to the Capsule Security portal
- Navigate to Settings > Integrations
- Locate the Claude Code integration
- Click Install and select your target platform
- For Windows endpoints, download the
capsule-claude-code-windows.ziparchive Capsule generates: it containsmanaged-settings.jsonand thehooks\capsule-hook.ps1dispatcher it invokes - For macOS endpoints, download the
managed-settings.jsonfile
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.ps1into 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:
- Extract the
capsule-claude-code-windows.ziparchive from Step 1. It holdsmanaged-settings.jsonandhooks\capsule-hook.ps1 - In the installer below, replace
<PASTE YOUR managed-settings.json CONTENT HERE>with the full contents ofmanaged-settings.json, and<PASTE YOUR hooks\capsule-hook.ps1 CONTENT HERE>with the full contents ofhooks\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 - Copy the whole installer, start to finish, and paste it into the Windows wrapper's
<PASTE THE PRODUCT'S WINDOWS POWERSHELL SCRIPT HERE>placeholder - 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 0On 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.ps1A 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:
- Run
/hooksin Claude Code to confirm all hooks are listed - Start a session and verify events appear in the Capsule Security portal
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 0If 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.
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.
- Log in to the Capsule Security portal
- Navigate to Settings > Integrations
- Locate the Codex integration
- For Windows endpoints, select Windows and extract the downloaded
capsule-codex-managed.zip(containsrequirements.tomland theCapsuleCodexHook.ps1companion script) - For macOS endpoints, select Mac and download the
requirements.tomlfile
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 0macOS - 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 0After deployment, close any running Codex CLI sessions on target devices, then:
- Start a new Codex CLI session so the managed configuration is loaded
- Run
/hooksand confirm the Capsule hooks are listed as managed - In the Capsule portal, navigate to Inventory > Agents and confirm the Codex agent appears
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- 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_scriptcaps each call at 1000 endpoints and accepts only anendpoint_id_listfilter, 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
Results are pull-based. Cortex sends no notification when an action finishes, so watch the Action Center.
- Navigate to Investigation & Response > Response > Action Center
- 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 - 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.
- Run agent scripts on an endpoint (Cortex XSIAM)
- Run scripts on an endpoint (Cortex XDR 3.x)
- Response actions, platform and agent version matrix
- Agent Scripts Library permissions (Cortex XDR 5.x)
- Initiate and monitor endpoint actions
- Initiate a Live Terminal session
- Manage endpoints
- Install the Cortex XDR agent for Windows, including the restriction flags
- Script Execution API
- Codex Hooks
- Codex Managed Configuration
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