diff --git a/.gitignore b/.gitignore index 3916cd8..f2a456e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ package-firewall/out/ .env **/out/ -.DS_Store \ No newline at end of file +.DS_Store + +# agent-governance: stray local generations from render.sh run without -o +# (may carry real credentials — never commit them) +*.tmp +agent-governance/claude-settings.json +agent-governance/cursor-hooks.json +agent-governance/com.anthropic.claudecode.mobileconfig \ No newline at end of file diff --git a/README.md b/README.md index 508b3df..4fb070c 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,8 @@ Scripts and generators for deploying Endor Labs configuration via MDM. ### [`package-firewall/`](package-firewall/README.md) -Generates self-contained MDM scripts that configure developer machines to route package installations through the [Endor Package Firewall](https://docs.endorlabs.com/integrations/package-firewall). \ No newline at end of file +Generates self-contained MDM scripts that configure developer machines to route package installations through the [Endor Package Firewall](https://docs.endorlabs.com/integrations/package-firewall). + +### [`agent-governance/`](agent-governance/README.md) + +Generates MDM-deployable Endor Labs audit hooks for every AI coding agent on your fleet. \ No newline at end of file diff --git a/agent-governance/README.md b/agent-governance/README.md new file mode 100644 index 0000000..77248c7 --- /dev/null +++ b/agent-governance/README.md @@ -0,0 +1,155 @@ +# agent-governance + +Deploy Endor Labs audit hooks to every AI coding agent on your fleet — Claude Code and Cursor — through your MDM. + +## What this is + +Endor's AI governance works through **hooks** wired into developer AI tools. Whenever a developer does something — starts a session, runs a tool, edits a file — the hook calls `endorctl ai-audit`, which records the action and, when you turn enforcement on, blocks the ones your policies disallow. + +This directory of the public [`mdm-scripts`](https://github.com/endorlabs/mdm-scripts) repo gets those hooks onto every laptop and keeps them current. You run one generator to produce a tool's config, then deliver it however your fleet is managed — an MDM profile, a recurring script, or your config-management tooling. The repo is **credential-free**: your Endor API credentials are supplied at generation time and never stored here. + +## Quick start — try it on one machine + +Generate a config and drop it in your own user directory. Nothing is enforced this way (a local file isn't tamper-proof), but it's the fastest way to see hooks fire and watch the audit log. + +```sh +# Cursor — writes ~/.cursor/hooks.json +scripts/render.sh --agent cursor \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o ~/.cursor/hooks.json + +# Claude Code — writes ~/.claude/settings.json +scripts/render.sh --agent claude \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o ~/.claude/settings.json +``` + +Start a new session in the tool: the hook installs `endorctl` on first run and begins reporting to your Endor namespace. When you're ready to roll this out to the fleet, pick a deployment path below. + +## Choose how to deploy + +Each tool is delivered the way it best supports it. On macOS, Claude Code takes a tamper-resistant MDM **profile**; Cursor's config is a plain file delivered by a small **script**. A laptop can run any combination. + +| Tool | macOS delivery | Why | Runbook | +| --- | --- | --- | --- | +| **Claude Code** | MDM **Custom Profile** (`.mobileconfig`) | Claude reads a managed-settings profile payload (`com.anthropic.claudecode`), enforced by the OS | [Deploy Claude via profile](docs/deploy-claude-profile.md) | +| **Cursor** | MDM **Custom Script** (the runner) | `hooks.json` is a plain file, not a profile payload | [Deploy Cursor via the runner](docs/deploy-cursor-runner.md) | + +**Linux** delivers the same JSON as a file — Cursor at `/etc/cursor/hooks.json`, Claude at `/etc/claude-code/managed-settings.json` — via the runner or your config management (Ansible, Chef, …). + +**Windows** has no `.mobileconfig`. You pre-generate the config (`--target-os windows`) and push the file with **Intune**; the hook is a self-contained `powershell` command that runs regardless of how the agent launches it. See [Deploy on Windows via Intune](docs/deploy-windows-intune.md). + +Other paths: [JumpCloud](docs/deploy-jumpcloud.md) (any OS), and [manual / enterprise-platform install](docs/deploy-manual-enterprise.md) for trials or orgs governing through Cursor Team hooks / Claude's admin console. The full agent × OS × MDM grid is the [support matrix](docs/support-matrix.md). + +## The generator + +[`scripts/render.sh`](scripts/render.sh) builds a tool's config as JSON. For the Claude macOS profile, pipe that JSON through [`scripts/render-plist.sh`](scripts/render-plist.sh), which wraps it in the profile envelope and converts it to a `.mobileconfig`. + +```sh +# Cursor hooks.json +scripts/render.sh --agent cursor \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o hooks.json + +# Claude Code settings.json +scripts/render.sh --agent claude \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o settings.json + +# Claude Code MDM profile (.mobileconfig) — upload to your MDM +scripts/render.sh --agent claude \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o - \ +| scripts/render-plist.sh \ + --identifier com.acme.ai-governance.claudecode --organization "Acme Corp" \ + --name "Claude Code - Endor AI Governance" \ + -o com.anthropic.claudecode.mobileconfig + +# Windows config (push via Intune) +scripts/render.sh --agent cursor --target-os windows \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o cursor-hooks.json +``` + +**Credentials** resolve flag → environment variable → prompt (the prompt only appears on a TTY, so unattended runs never hang): + +| Flag | Env fallback | Default | +| --- | --- | --- | +| `--api-key` | `ENDOR_API_CREDENTIALS_KEY` | — (required) | +| `--api-secret` | `ENDOR_API_CREDENTIALS_SECRET` | — (required) | +| `--namespace` | `ENDOR_NAMESPACE` | — (required) | +| `--api-url` | `ENDOR_API` | `https://api.endorlabs.com` | + +**`--target-os {macos,linux,windows}`** (default `macos`; macOS and Linux are identical POSIX) chooses the hook form. `windows` inlines the PowerShell bootstrap as a base64 `powershell -NoProfile -EncodedCommand …` so it runs under Git Bash, PowerShell, or cmd alike. + +**Behavior settings** go through `--env KEY=VALUE` (repeatable) and land in the right place per tool. Response caching is on by default; monitor-only mode is just `--env ENDOR_AI_AUDIT_NO_BLOCKING=true`. + +**`--skip-endorctl-update`** makes the session hook use an already-installed `endorctl` instead of checking for a newer one every session — useful once the fleet is provisioned. It passes through the runner too. + +`render.sh` also takes `-o/--output` (`-` for stdout). `render-plist.sh` is agent-agnostic — `--payload-type` (default `com.anthropic.claudecode`) selects the app, and `--identifier`/`--organization` are required, with `--name`, `--profile-identifier`, and the UUID flags optional. Run either script with `--help` for the full list. + +### Enforcing vs. monitor-only + +- **Enforcing** (default): policy-violating actions get a "block" verdict and the agent halts. +- **Monitor-only** (`--env ENDOR_AI_AUDIT_NO_BLOCKING=true`): every action is still evaluated and recorded, but nothing is blocked. + +A good rollout starts in monitor-only, watches the Endor audit log over a representative period to confirm the policies aren't catching false positives, then regenerates without the flag to enforce. + +## How it works + +**`endorctl` installs and updates itself.** It isn't shipped per tool — the generated session hook runs [`download_endorctl.sh`](scripts/download_endorctl.sh) (or [`download_endorctl.ps1`](scripts/download_endorctl.ps1) on Windows), which installs the binary on first run and refreshes it when a new version ships, verifying a SHA-256 each time. So the only things that change after setup are the config (when you regenerate it) and the governance rules (server-side at Endor, fetched at run time). + +**What needs re-delivery when it changes:** + +| What changes | How it updates | Your action | +| --- | --- | --- | +| `endorctl` binary | Self-updates on session start (SHA-256 verified); `--skip-endorctl-update` pins it | None | +| Governance rules | Server-side at Endor, fetched at run time | None | +| Claude profile config (macOS) | Regenerate the `.mobileconfig`, re-upload to the MDM | Re-upload | +| Cursor runner config (macOS/Linux) | Runner re-fetches `REF` and re-renders on each scheduled run | None after setup | +| Windows config | Regenerate (`--target-os windows`), re-push via Intune | Re-push | + +**Security properties:** + +- **Tamper-resistance.** A profile-delivered config (Claude on macOS) is an OS-enforced managed setting — hard for a developer to override. A script-delivered file (Cursor, and the file-based Linux/Windows paths) is not OS-enforced; a determined developer could override it. Cursor has no profile mechanism today, so the profile path is Claude-only. +- **Least-privilege credentials.** A generated profile carries the API key and secret to every laptop — scope it to an **audit-only** credential. +- **Pin the revision.** The runner executes this repo's code as root, so it fetches a specific revision: set `REF` (at the top of `runner.sh`) to a reviewed tag, branch, or commit and each device runs only that, not the moving branch tip. Bump `REF` to roll out a change; the default (`main`) tracks the latest. +- **Credential isolation (Claude).** The `env` block exports into every subprocess Claude spawns, including any `endorctl` the agent itself runs. To keep audit credentials out of the agent's process tree, hook-scoped variables use an `AGENT_HOOK_ENDOR_*` prefix that `endorctl` doesn't read natively, and the hook passes them through as `--api-key …` flags. +- **Robust quoting.** Credentials and `--env` values are escaped for their target — the shell (POSIX, via `@sh`), PowerShell (single-quote doubling), and JSON (`jq`) — and `$VAR` references in the generated commands are quoted, so a value containing a space, quote, `$`, `;`, `*`, or `` ` `` never word-splits or breaks the hook. + +## Prerequisites + +Each script needs only what's standard to where it runs; the laptop paths stay light, and `plutil` is only the concern of the admin who builds Claude profiles. + +| Script | Runs on | Needs | +| --- | --- | --- | +| [`download_endorctl.sh`](scripts/download_endorctl.sh) | developer laptop (inlined into the session hook) | POSIX `sh` + `curl` (plus `awk`/`sed`/`uname`/`mktemp`/`tr` and `sha256sum` or `shasum` — all standard on macOS & Linux) | +| [`download_endorctl.ps1`](scripts/download_endorctl.ps1) | Windows laptop (encoded into the session hook) | Windows PowerShell 5.1 (built in) | +| [`scripts/render.sh`](scripts/render.sh) | admin machine (macOS/Linux, or Windows via Git Bash/WSL), or laptop via the runner | `jq`; for `--target-os windows` also `iconv` + `base64` | +| [`scripts/render-plist.sh`](scripts/render-plist.sh) | admin machine (macOS) | `jq` + `plutil` | +| [`scripts/runner.sh`](scripts/runner.sh) | developer laptop (run by the MDM) | `git` + POSIX `sh` + `jq` | + +## Repository layout + +``` +scripts/ + download_endorctl.sh endorctl bootstrap, POSIX (macOS/Linux session hook) + download_endorctl.ps1 endorctl bootstrap, PowerShell (Windows session hook) + render.sh generate a tool's config as JSON + render-plist.sh wrap a config (stdin) into a .mobileconfig profile + runner.sh MDM runner: clone → render → swap-if-changed +examples/ checked-in samples (demo creds, placeholder UUIDs) +docs/ deployment runbooks + the support matrix +``` + +## Examples + +`examples/` holds one checked-in sample per output shape, generated with demo credentials (`PEPE` / `PAPA` / namespace `spiderman`) and placeholder profile UUIDs: + +| Shape | Agent | File | +| --- | --- | --- | +| JSON (POSIX hooks) | Claude | `examples/claude/settings.json` | +| MDM profile (plist) | Claude | `examples/claude/com.anthropic.claudecode.mobileconfig` | +| JSON (encoded PowerShell hook) | Claude | `examples/claude/settings.windows.json` | +| JSON (POSIX hooks) | Cursor | `examples/cursor/hooks.json` | +| JSON (encoded PowerShell hook) | Cursor | `examples/cursor/hooks.windows.json` | + +There's no separate Linux example: `settings.json` is exactly what Claude reads as the Linux `/etc/claude-code/managed-settings.json` and as the inner payload of the macOS profile, and JumpCloud reuses these same files. Only the Windows samples differ (the encoded `powershell` hook). After changing a script, regenerate the affected examples with the commands above so they stay in sync. + +## Extending + +To add another agent: add a `build_` jq builder and a `case` arm in [`scripts/render.sh`](scripts/render.sh), plus a default-`DEST` `case` arm in [`scripts/runner.sh`](scripts/runner.sh) if it's script-delivered. diff --git a/agent-governance/docs/deploy-claude-profile.md b/agent-governance/docs/deploy-claude-profile.md new file mode 100644 index 0000000..2638e01 --- /dev/null +++ b/agent-governance/docs/deploy-claude-profile.md @@ -0,0 +1,56 @@ +# Deploy Claude Code via an MDM profile (macOS) + +On macOS, Claude Code reads managed settings from a Configuration Profile payload of type `com.anthropic.claudecode`, so a profile is **tamper-resistant** — the OS enforces it, and nothing extra runs on the laptop. The one tradeoff: an update means regenerating the profile and re-uploading it. + +You'll generate a `.mobileconfig` on an admin Mac (you need `jq` and `plutil`), then upload it through Jamf or Kandji. + +## 1. Generate the profile + +`render.sh` builds the settings JSON and `render-plist.sh` wraps it into the `.mobileconfig` — pipe one into the other. Use an **audit-only / least-privilege** API credential, since the key and secret are embedded in the profile and delivered to every laptop. + +```sh +scripts/render.sh --agent claude \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o - \ +| scripts/render-plist.sh \ + --identifier com..ai-governance.claudecode \ + --organization "" \ + --name "Claude Code - Endor AI Governance" \ + -o com.anthropic.claudecode.mobileconfig +``` + +- `--identifier` is a unique reverse-DNS id in your namespace; the inner payload becomes `.settings`. +- `--organization` sets the profile's `PayloadOrganization`, and `--name` the MDM-visible display name. +- UUIDs are generated fresh unless you pass `--profile-uuid` / `--content-uuid`. +- `--payload-type` defaults to `com.anthropic.claudecode`, so it's omitted here. +- For an initial monitor-only rollout, add `--env ENDOR_AI_AUDIT_NO_BLOCKING=true` to the `render.sh` step. + +Confirm it's a valid property list before uploading (there's a ready-made sample at `examples/claude/com.anthropic.claudecode.mobileconfig`): + +```sh +plutil -lint com.anthropic.claudecode.mobileconfig +``` + +## 2. Upload to the MDM + +**Jamf Pro** — Computers → Configuration Profiles → New; add an **Application & Custom Settings → Upload** payload with the `.mobileconfig`; set the **Scope**; Save. Jamf pushes it and the OS installs it as a managed setting. (If you embed it under a Jamf-owned outer profile, set `--profile-identifier` to that profile's id so the outer identity matches.) + +**Kandji** — Library → Add New → Custom Profile; upload the `.mobileconfig`; assign it to the target Blueprint. + +## 3. Verify + +Open Claude Code on a target machine and start a session. The `SessionStart` hook installs/updates `endorctl` and begins reporting to your Endor namespace — confirm the activity in the Endor audit log. + +## Updating + +Profile-delivered config doesn't auto-update. To ship a change, regenerate the `.mobileconfig` (step 1), re-upload it to the MDM with a **bumped profile version**, and the MDM re-pushes it. How fast it lands depends on the MDM's check-in behavior. + +## Linux and Windows: the same settings, as a file + +`.mobileconfig` is macOS-only. On Linux and Windows, Claude reads the *same* managed-settings JSON (the `env` + `hooks` that `render.sh --agent claude` produces) as a plain file at a system path — no `render-plist.sh` step, but still admin-enforced and highest-precedence. + +| OS | Path | Deliver with | +| --- | --- | --- | +| Linux | `/etc/claude-code/managed-settings.json` (+ `managed-settings.d/`) | [`runner.sh (AGENT=claude)`](deploy-cursor-runner.md), config management, or a [JumpCloud](deploy-jumpcloud.md) Command | +| Windows | `C:\Program Files\ClaudeCode\managed-settings.json` | pre-generate `--target-os windows`, push via [Intune](deploy-windows-intune.md) | + +Generate the Linux file with `render.sh --agent claude` (POSIX hooks) and the Windows file with `--target-os windows` (encoded PowerShell hooks). diff --git a/agent-governance/docs/deploy-cursor-runner.md b/agent-governance/docs/deploy-cursor-runner.md new file mode 100644 index 0000000..fdbaf61 --- /dev/null +++ b/agent-governance/docs/deploy-cursor-runner.md @@ -0,0 +1,50 @@ +# Deploy Cursor via the runner (macOS & Linux) + +Cursor's `hooks.json` is a plain file, not a profile payload, so it's delivered by a script your MDM runs on a schedule. That script is [`scripts/runner.sh`](../scripts/runner.sh): you **paste it into your MDM** as the script body. On each run it fetches this repo at a pinned revision, renders the config, and atomically swaps the file in **only if it changed** — so repo updates and any credential or flag change both take effect. Set it up once; after that, updates flow on their own. + +A few things to know: + +- **The endpoint needs `git` and `jq`** (the runner clones the repo once and renders on the machine). Windows endpoints have neither — pre-generate with `--target-os windows` and push via [Intune](deploy-windows-intune.md) instead. +- **It isn't tamper-resistant.** A plain file isn't OS-enforced; a developer could override it (e.g. via `~/.cursor/`). Only Claude has an OS-enforced path, via its [managed-settings profile](deploy-claude-profile.md). +- **Settings live at the top of `runner.sh`** — set `AGENT` (`cursor` or `claude`), `REF` (the revision to run), and optionally `EXTRA` (e.g. `--env ENDOR_AI_AUDIT_NO_BLOCKING=true` for monitor-only) or `DEST` (to override the install path). +- **Pin a version.** `REF` defaults to `main` (tracks latest); set it to a reviewed tag or commit (e.g. `v1.0.0`) so each device runs a known revision, since the runner executes this repo's code as root. Bump it deliberately to roll out a change. +- **Credentials** must be in the environment before the runner renders. You add them at the top of the pasted script, per your MDM (below). Use an **audit-only / least-privilege** credential — never commit them. + +In each MDM, the script body is: your credential lines, then the contents of `scripts/runner.sh` (with `AGENT`/`REF` set). + +## Jamf Pro + +Jamf reserves positional parameters `$1`–`$3` (mount point, computer, user), so yours start at `$4`. + +1. **Settings → Computer Management → Scripts → New.** Paste the credential line, then the body of `scripts/runner.sh`: + ```sh + #!/bin/sh + export ENDOR_API_CREDENTIALS_KEY="$4" ENDOR_API_CREDENTIALS_SECRET="$5" ENDOR_NAMESPACE="$6" + # …contents of scripts/runner.sh below (set AGENT=cursor, REF=)… + ``` + Label *Parameter 4 = API key*, *5 = API secret*, *6 = namespace*. +2. **Computers → Policies → New** — add the **Scripts** payload, select the script, and fill in the credential parameters. +3. Set the **Trigger** to *Recurring Check-in* and **Execution Frequency** to *Ongoing*. +4. Set the **Scope** and **Save**. + +## Kandji + +Kandji Custom Scripts run as **root** and can run on a schedule, but Kandji has **no secret store** — the script body is plaintext and visible to anyone with admin access. So hard-code the values (single-quoted), using an audit-only key, and rotate it. + +1. **Library → Add New → Custom Script.** Paste the credential line, then the body of `scripts/runner.sh`: + ```sh + #!/bin/sh + export ENDOR_API_CREDENTIALS_KEY='…' ENDOR_API_CREDENTIALS_SECRET='…' ENDOR_NAMESPACE='…' + # …contents of scripts/runner.sh below (set AGENT=cursor, REF=)… + ``` + Single-quote the values so a `"`, `$`, or backtick can't break the assignment; if a value contains a single quote, write it as `'\''`. +2. Set **Execution Frequency** to *Run every 15 min* or *Run daily*. +3. Assign it to the target **Blueprint**. + +## Verify + +After a run, confirm `/Library/Application Support/Cursor/hooks.json` (or `/etc/cursor/hooks.json` on Linux) exists, then open Cursor and start a session — the `sessionStart` hook installs/updates `endorctl` and begins reporting. Confirm the activity in the Endor audit log. + +## Updating + +Nothing to do. Each scheduled run re-fetches the repo at `REF` and re-renders, swapping the file in only if it changed — so repo updates **and** credential/flag changes both take effect. To move to a new release, bump `REF`. How quickly an update lands depends on the MDM's schedule (minutes to an hour). diff --git a/agent-governance/docs/deploy-jumpcloud.md b/agent-governance/docs/deploy-jumpcloud.md new file mode 100644 index 0000000..b76884d --- /dev/null +++ b/agent-governance/docs/deploy-jumpcloud.md @@ -0,0 +1,62 @@ +# Deploy via JumpCloud (macOS, Linux, Windows) + +JumpCloud can deliver to all three OSes using two building blocks: + +- **Commands** run a script on devices — as **root** on macOS/Linux, as **`NT AUTHORITY\System`** on Windows. They support recurring execution (**Run as Repeating**) plus login and webhook triggers. This is how you run the runner or write a config file. +- **MDM Custom Configuration Profiles** (macOS only) deliver a `.mobileconfig`. JumpCloud signs the profile and rewrites its outer `PayloadIdentifier`/`PayloadUUID`, so you don't pass `--profile-identifier`/`--profile-uuid` when generating for JumpCloud. + +There's no native "push a file" feature — file delivery is just a Command that writes the file. Use an **audit-only / least-privilege** credential throughout, supplied through JumpCloud and never the repo. Find your agent + OS below: + +| Agent | OS | Mechanism | +| --- | --- | --- | +| Claude Code | macOS | MDM Custom Configuration Profile (`.mobileconfig`) — tamper-resistant | +| Claude Code | Linux | Command → `runner.sh (AGENT=claude)` → `/etc/claude-code/managed-settings.json` | +| Claude Code | Windows | Command (PowerShell) writes the pre-generated config to `C:\Program Files\ClaudeCode\managed-settings.json` | +| Cursor | macOS | Command → `runner.sh (AGENT=cursor)` | +| Cursor | Linux | Command → `runner.sh (AGENT=cursor)` → `/etc/cursor/hooks.json` | +| Cursor | Windows | Command (PowerShell) writes the pre-generated config to `C:\ProgramData\Cursor\hooks.json` | + +## macOS — Claude via profile + +Generate the `.mobileconfig` ([see the profile runbook](deploy-claude-profile.md); omit `--profile-identifier`/`--profile-uuid`, since JumpCloud rewrites them). Then in **Device Management → Policy Management → Mac → MDM Custom Configuration Profile**, upload the file and scope it to a Device Group (devices must be MDM-enrolled). To update, regenerate and re-upload. + +## macOS & Linux — the runner via a Command + +This is JumpCloud's equivalent of a Jamf recurring policy or a Kandji schedule. + +1. **Device Management → Commands → New**, target **Mac** or **Linux**, interpreter Bash/Shell. The body is the credential line, then the contents of [`scripts/runner.sh`](../scripts/runner.sh): + ```sh + #!/bin/sh + export ENDOR_API_CREDENTIALS_KEY='…' ENDOR_API_CREDENTIALS_SECRET='…' ENDOR_NAMESPACE='…' + # …contents of scripts/runner.sh below (set AGENT=cursor or claude, REF=)… + ``` + Single-quote the values so a `"`, `$`, or backtick can't break the assignment (escape any literal single quote as `'\''`). In `runner.sh`, set `AGENT` and `REF` (a reviewed tag/commit to pin). The endpoint needs `git` + `jq`. +2. Set the launch type to **Run as Repeating** (e.g. hourly) so each run re-fetches `REF` and re-renders, swapping only on change — repo and credential/flag changes flow automatically. +3. Scope it to a Device Group. + +If you'd rather not require `git`/`jq` on Linux, use the file-writing Command pattern below (pre-generate, embed the JSON) for Linux too. + +## Windows — a PowerShell Command + +Windows endpoints have no `git`/`jq`, so **pre-generate** the config on a macOS/Linux (or WSL/Git Bash) admin box: + +```sh +scripts/render.sh --agent cursor --target-os windows --api-key … --api-secret … --namespace … -o cursor-hooks.json +``` + +Then a JumpCloud **Windows Command** (PowerShell, runs as SYSTEM) writes it to the system path: + +```powershell +$dest = "C:\ProgramData\Cursor\hooks.json" # or C:\Program Files\ClaudeCode\managed-settings.json +New-Item -ItemType Directory -Force -Path (Split-Path $dest) | Out-Null +Set-Content -Path $dest -Value @' + +'@ -Encoding UTF8 +``` + +Set it to **Run as Repeating** to keep it current; to update, edit the embedded JSON and re-push. For a per-user `%USERPROFILE%\.cursor\hooks.json`, use JumpCloud's **"Run As Signed In User"** template instead of the SYSTEM default. + +## Things to watch + +- **Repeating commands** fire in the device's local time zone and can miss their window if the device is asleep — pick a frequency (e.g. hourly) that tolerates the occasional miss. +- A macOS Command writing into a protected path needs the JumpCloud agent to have **Full Disk Access**, or it fails with "Operation Not Permitted." diff --git a/agent-governance/docs/deploy-manual-enterprise.md b/agent-governance/docs/deploy-manual-enterprise.md new file mode 100644 index 0000000..233ca25 --- /dev/null +++ b/agent-governance/docs/deploy-manual-enterprise.md @@ -0,0 +1,43 @@ +# Manual & enterprise-platform install (no MDM) + +For trials, individual developers, or orgs that govern through an agent's own enterprise platform instead of MDM. You generate the config the same way, then either drop it at a local path (manual) or hand it to the vendor platform (enterprise). Manual local files **aren't tamper-resistant** — a developer can edit them — so use an [MDM path](../README.md#choose-how-to-deploy) where enforcement matters; the enterprise platforms, by contrast, are centrally managed. + +## Quick start (manual) + +Generate a config for the developer's OS and drop it in their user directory: + +```sh +# macOS / Linux +scripts/render.sh --agent cursor --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o ~/.cursor/hooks.json +scripts/render.sh --agent claude --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o ~/.claude/settings.json + +# Windows (encoded PowerShell hooks) +scripts/render.sh --agent cursor --target-os windows … -o "$USERPROFILE\.cursor\hooks.json" +scripts/render.sh --agent claude --target-os windows … -o "$USERPROFILE\.claude\settings.json" +``` + +Restart the agent (or start a new session) and it picks up the config — the hook installs `endorctl` on first run and starts reporting. + +## Where the file goes + +| Agent | Scope | macOS / Linux | Windows | +| --- | --- | --- | --- | +| **Cursor** | per-user | `~/.cursor/hooks.json` | `%USERPROFILE%\.cursor\hooks.json` | +| **Cursor** | per-project | `/.cursor/hooks.json` | same (in the repo) | +| **Claude Code** | per-user | `~/.claude/settings.json` | `%USERPROFILE%\.claude\settings.json` | +| **Claude Code** | per-project | `/.claude/settings.json` | same (in the repo) | + +Use the `--target-os windows` config when the file lands on Windows, and the default POSIX config for macOS/Linux. + +## Enterprise platform (centrally managed, no MDM) + +If you run Cursor's or Claude's enterprise platform, you can govern the fleet without touching MDM — generate the config as above and supply it through the platform: + +- **Cursor — Team hooks** (Enterprise plan). Configure hooks in the Team dashboard; Cursor cloud-delivers them to members on login and supports OS-specific targeting. Paste the body of the generated `hooks.json` (POSIX for macOS/Linux, the `--target-os windows` variant for Windows) into the matching OS slot. +- **Claude Code — server-managed settings.** In the Claude admin console, set server-managed settings to the same `env` + `hooks` that `render.sh --agent claude` produces. These are the **highest-precedence** scope, so this alone can govern the fleet. + +## Which to use + +- **Manual** — fastest to try; not enforced; good for one machine or a pilot. +- **Enterprise platform** — centrally managed by the vendor, no MDM needed; the cleanest path if you already run Cursor Enterprise or Claude's admin console. +- **MDM** — OS-enforced and tamper-resistant; use it when enforcement matters. See the [deployment runbooks](../README.md#choose-how-to-deploy). diff --git a/agent-governance/docs/deploy-windows-intune.md b/agent-governance/docs/deploy-windows-intune.md new file mode 100644 index 0000000..4d4cbe3 --- /dev/null +++ b/agent-governance/docs/deploy-windows-intune.md @@ -0,0 +1,63 @@ +# Deploy on Windows via Intune + +Windows has no Configuration Profiles for these tools — both agents read a plain JSON config from a known path. So the pattern is different from macOS: you **pre-generate the config on a macOS/Linux admin machine** (a Windows laptop has no `jq`/`sh`) and use **Microsoft Intune** to place the file. The endpoint never runs the generator. + +## How the Windows hook runs + +The generated config's hook is one self-contained command: + +``` +powershell -NoProfile -EncodedCommand +``` + +Base64 is plain alphanumeric, so it runs identically whether the agent launches the hook through Git Bash, PowerShell, or cmd — and `powershell.exe` (5.1) ships with Windows, so nothing extra is needed on the endpoint. The readable source is [`download_endorctl.ps1`](../scripts/download_endorctl.ps1); the config just carries its encoding. + +## 1. Generate the config (admin machine) + +Generate on macOS/Linux, or on Windows under Git Bash (ships with Git for Windows) or WSL with `jq` installed — the output is identical. + +```sh +# Cursor +scripts/render.sh --agent cursor --target-os windows \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o cursor-hooks.json + +# Claude Code +scripts/render.sh --agent claude --target-os windows \ + --api-key "$KEY" --api-secret "$SECRET" --namespace "$NS" -o claude-managed-settings.json +``` + +Add `--env ENDOR_AI_AUDIT_NO_BLOCKING=true` for a monitor-only rollout, or `--skip-endorctl-update` to pin to the installed binary. (Generating Windows configs needs `jq` + `iconv` + `base64`, all standard on macOS/Linux.) + +## 2. Where the file goes + +| Agent | Path | Notes | +| --- | --- | --- | +| **Cursor** | `C:\ProgramData\Cursor\hooks.json` (system-wide) or `%USERPROFILE%\.cursor\hooks.json` (per-user) | system-wide outranks per-user | +| **Claude Code** | `C:\Program Files\ClaudeCode\managed-settings.json` (+ `managed-settings.d\`) | managed settings; users can't override | + +## 3. Push with Intune + +Use a **Platform script** (Devices → Scripts → Add → Windows 10 and later) or a Win32 app, run in system context, that writes the generated JSON to the path above: + +```powershell +$dest = "$env:ProgramData\Cursor\hooks.json" # or C:\Program Files\ClaudeCode\managed-settings.json +# Paste the generated config between the single-quoted here-string markers. Single +# quotes (@' '@) keep it literal so PowerShell doesn't interpret $ / quotes in it. +$json = @' + +'@ +New-Item -ItemType Directory -Force -Path (Split-Path $dest) | Out-Null +Set-Content -Path $dest -Value $json -Encoding UTF8 +``` + +Scope it to the target device or user groups. (For the per-user path `%USERPROFILE%\.cursor\hooks.json`, run the script in the user's context instead of system.) + +An Intune **platform script runs once per device** — it re-runs only when you change the script content (and once for each new user who signs in, when run in user context). That's fine for a config that only changes when you regenerate it. If you want Intune to keep re-asserting the file on a schedule — e.g. to repair drift if a user edits or deletes it — use a **Remediation** instead (Devices → Remediations: a detection + remediation script pair, scheduled Once / Hourly / Daily; requires Windows Enterprise E3/E5). + +## Endpoint prerequisites + +Just `powershell.exe` (Windows PowerShell 5.1, built in) — invoked by the hook. There's no on-endpoint generation, so `git`/`jq` aren't needed, and `endorctl.exe` installs itself on the first session via the inlined `download_endorctl.ps1`. + +## Updating + +Regenerate the config (step 1) and re-deploy it. Since a platform script only re-runs when its content changes, "re-push" means updating the script body (the embedded JSON) in the policy — re-saving an unchanged policy won't re-run. There's no on-endpoint auto-pull on Windows, so updates are centrally driven. diff --git a/agent-governance/docs/support-matrix.md b/agent-governance/docs/support-matrix.md new file mode 100644 index 0000000..d1b7d41 --- /dev/null +++ b/agent-governance/docs/support-matrix.md @@ -0,0 +1,29 @@ +# Support matrix + +Every combination of agent × installation mechanism × OS this repo covers, and how it's delivered. + +| # | Agent | Mechanism | OS | Provider(s) | How it's delivered | Runbook | +| --- | --- | --- | --- | --- | --- | --- | +| 1 | Cursor | Manual / Enterprise | — | Cursor Team hooks | `render.sh --agent cursor` → drop the file, or paste into the Team-hooks dashboard | [manual / enterprise](deploy-manual-enterprise.md) | +| 2 | Cursor | MDM | macOS | Kandji, Jamf, JumpCloud | `runner.sh (AGENT=cursor)` → `/Library/Application Support/Cursor/hooks.json` | [runner](deploy-cursor-runner.md), [jumpcloud](deploy-jumpcloud.md) | +| 3 | Cursor | MDM | Linux | JumpCloud · config mgmt | `runner.sh (AGENT=cursor)` → `/etc/cursor/hooks.json` | [runner](deploy-cursor-runner.md), [jumpcloud](deploy-jumpcloud.md) | +| 4 | Cursor | MDM | Windows | Intune, JumpCloud | `--target-os windows` → push to `C:\ProgramData\Cursor\hooks.json` (system-wide) or `%USERPROFILE%\.cursor\hooks.json` (per-user) | [intune](deploy-windows-intune.md), [jumpcloud](deploy-jumpcloud.md) | +| 5 | Claude Code | Manual / Enterprise | — | Claude admin console | `render.sh --agent claude` → drop the file, or set as server-managed settings | [manual / enterprise](deploy-manual-enterprise.md) | +| 6 | Claude Code | MDM | macOS | Kandji, Jamf, JumpCloud | `render-plist.sh` → `.mobileconfig` profile (tamper-resistant) | [profile](deploy-claude-profile.md), [jumpcloud](deploy-jumpcloud.md) | +| 7 | Claude Code | MDM | Linux | JumpCloud · config mgmt | `runner.sh (AGENT=claude)` → `/etc/claude-code/managed-settings.json` | [profile](deploy-claude-profile.md#linux-and-windows-the-same-settings-as-a-file), [jumpcloud](deploy-jumpcloud.md) | +| 8 | Claude Code | MDM | Windows | Intune, JumpCloud | `--target-os windows` → push to `C:\Program Files\ClaudeCode\managed-settings.json` | [intune](deploy-windows-intune.md), [jumpcloud](deploy-jumpcloud.md) | + +## How delivery differs by OS + +- **macOS Claude** is the only **profile** path (`.mobileconfig`, OS-enforced, tamper-resistant). Every other cell is a **plain JSON file** at a system path. +- **macOS and Linux** can keep that file current automatically with `runner.sh` (re-render and swap-on-change, on the MDM's schedule). +- **Windows** has no `git`/`jq` on the endpoint, so you **pre-generate** (`--target-os windows`) and push the file centrally. +- **Config management** (Ansible, Chef, Puppet, Salt, …) works for any file-based cell on any OS — the artifact is just JSON at a known path. The one exception is the macOS Claude profile (row 6), which needs an MDM to install the `.mobileconfig`. + +## Worth knowing + +- **The runner needs `git` + `jq` on the endpoint** (rows 2, 3, 7). The profile path (row 6) and the Windows pre-generate path (rows 4, 8) need nothing beyond what ships with the OS. If you'd rather not add `git`/`jq` to Linux endpoints, deliver the pre-generated file via config management instead. +- **Smoke-test Windows before a fleet rollout.** The encoded PowerShell hook is verified by decode/round-trip; confirm `endorctl.exe` installs and audits on a real Windows endpoint for both agents before going wide. +- **Generation is POSIX-only** — `render.sh` needs `sh` + `jq` (and `iconv` + `base64` for Windows targets). A Windows-only admin generates under WSL or Git Bash. +- **Cursor Team hooks** take a manual paste of the generated `hooks.json` — supported, not automated. +- **JumpCloud** delivers files via a Command (no native file-push) and recurs via *Run as Repeating* (which can miss a window during device sleep); macOS profiles go through its MDM Custom Configuration Profile policy, which rewrites the outer profile UUID/identifier. diff --git a/agent-governance/examples/claude/com.anthropic.claudecode.mobileconfig b/agent-governance/examples/claude/com.anthropic.claudecode.mobileconfig new file mode 100644 index 0000000..2557afc --- /dev/null +++ b/agent-governance/examples/claude/com.anthropic.claudecode.mobileconfig @@ -0,0 +1,178 @@ + + + + + PayloadContent + + + PayloadDescription + Managed settings (env + hooks) for Endor Labs AI governance auditing. + PayloadDisplayName + Claude Code - Endor AI Governance + PayloadEnabled + + PayloadIdentifier + com.endorlabs.ai-governance.claudecode.settings + PayloadType + com.anthropic.claudecode + PayloadUUID + 00000000-0000-0000-0000-BBBBBBBBBBBB + PayloadVersion + 1 + env + + AGENT_HOOK_ENDOR_API + https://api.endorlabs.com + AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY + PEPE + AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET + PAPA + AGENT_HOOK_ENDOR_NAMESPACE + spiderman + ENDOR_AI_AUDIT_CACHE_ENABLED + true + + hooks + + PostToolUse + + + hooks + + + command + "$HOME/.endorctl/endorctl" --api "$AGENT_HOOK_ENDOR_API" --namespace "$AGENT_HOOK_ENDOR_NAMESPACE" --api-key "$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY" --api-secret "$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET" ai-audit claudecode + type + command + + + matcher + .* + + + PostToolUseFailure + + + hooks + + + command + "$HOME/.endorctl/endorctl" --api "$AGENT_HOOK_ENDOR_API" --namespace "$AGENT_HOOK_ENDOR_NAMESPACE" --api-key "$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY" --api-secret "$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET" ai-audit claudecode + type + command + + + matcher + .* + + + PreToolUse + + + hooks + + + command + "$HOME/.endorctl/endorctl" --api "$AGENT_HOOK_ENDOR_API" --namespace "$AGENT_HOOK_ENDOR_NAMESPACE" --api-key "$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY" --api-secret "$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET" ai-audit claudecode + type + command + + + matcher + .* + + + SessionStart + + + hooks + + + command + BIN="${HOME}/.endorctl/endorctl" +skip= +[ -n "${ENDORCTL_SKIP_UPDATE:-}" ] && [ -x "$BIN" ] && skip=1 +if [ -z "$skip" ]; then + case "$(uname -s)" in Darwin) os=macos ;; Linux) os=linux ;; *) exit 1 ;; esac + case "$(uname -m)" in arm64|aarch64) arch=arm64 ;; x86_64|amd64) arch=amd64 ;; *) exit 1 ;; esac + URL="https://api.endorlabs.com/download/latest/endorctl_${os}_${arch}" + ARCH_KEY="ARCH_TYPE_$(echo "${os}_${arch}" | tr '[:lower:]' '[:upper:]')" + current=$([ -x "$BIN" ] && "$BIN" --version 2>/dev/null | awk '/version/ {print $NF; exit}') + meta=$(curl -fsSL --retry 5 --retry-connrefused --retry-all-errors https://api.endorlabs.com/meta/version) + latest=$(echo "$meta" | sed -n 's/.*"ClientVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') + expected_sha=$(echo "$meta" | sed -n "s/.*\"${ARCH_KEY}\"[[:space:]]*:[[:space:]]*\"\([a-f0-9]*\)\".*/\1/p") + uptodate= + [ -n "$current" ] && { [ -z "$latest" ] || [ "$current" = "$latest" ]; } && uptodate=1 + if [ -z "$uptodate" ]; then + DIR=$(dirname "$BIN") + mkdir -p "$DIR" + # Sweep leftovers from interrupted past runs. Age-gated so a concurrent + # session's in-flight download is never deleted; the name cannot match the + # installed binary ("endorctl"). + find "$DIR" -name 'endorctl-download-*' -mmin +60 -delete 2>/dev/null + TMP=$(mktemp "$DIR/endorctl-download-XXXXXX") || exit 1 + curl -fsSL --retry 5 --retry-connrefused --retry-all-errors -o "$TMP" "$URL" || { rm -f "$TMP"; exit 1; } + [ ${#expected_sha} -eq 64 ] || { rm -f "$TMP"; exit 1; } + case "$expected_sha" in *[!0-9a-f]*) rm -f "$TMP"; exit 1 ;; esac + if command -v sha256sum >/dev/null 2>&1; then sum=$(sha256sum "$TMP" | awk '{print $1}'); else sum=$(shasum -a 256 "$TMP" | awk '{print $1}'); fi + [ "$sum" = "$expected_sha" ] || { rm -f "$TMP"; exit 1; } + chmod +x "$TMP" || { rm -f "$TMP"; exit 1; } + mv "$TMP" "$BIN" + fi +fi +"$HOME/.endorctl/endorctl" --api "$AGENT_HOOK_ENDOR_API" --namespace "$AGENT_HOOK_ENDOR_NAMESPACE" --api-key "$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY" --api-secret "$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET" ai-audit claudecode + type + command + + + + + Stop + + + hooks + + + command + "$HOME/.endorctl/endorctl" --api "$AGENT_HOOK_ENDOR_API" --namespace "$AGENT_HOOK_ENDOR_NAMESPACE" --api-key "$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY" --api-secret "$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET" ai-audit claudecode + type + command + + + + + UserPromptSubmit + + + hooks + + + command + "$HOME/.endorctl/endorctl" --api "$AGENT_HOOK_ENDOR_API" --namespace "$AGENT_HOOK_ENDOR_NAMESPACE" --api-key "$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY" --api-secret "$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET" ai-audit claudecode + type + command + + + + + + + + PayloadDescription + Deploys managed AI-tool settings for Endor Labs AI auditing. + PayloadDisplayName + Claude Code - Endor AI Governance + PayloadIdentifier + com.endorlabs.ai-governance.claudecode + PayloadOrganization + Endor Labs + PayloadScope + System + PayloadType + Configuration + PayloadUUID + 00000000-0000-0000-0000-AAAAAAAAAAAA + PayloadVersion + 1 + + diff --git a/agent-governance/examples/claude/settings.json b/agent-governance/examples/claude/settings.json new file mode 100644 index 0000000..90f85ff --- /dev/null +++ b/agent-governance/examples/claude/settings.json @@ -0,0 +1,74 @@ +{ + "env": { + "AGENT_HOOK_ENDOR_API": "https://api.endorlabs.com", + "AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY": "PEPE", + "AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET": "PAPA", + "AGENT_HOOK_ENDOR_NAMESPACE": "spiderman", + "ENDOR_AI_AUDIT_CACHE_ENABLED": "true" + }, + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "BIN=\"${HOME}/.endorctl/endorctl\"\nskip=\n[ -n \"${ENDORCTL_SKIP_UPDATE:-}\" ] && [ -x \"$BIN\" ] && skip=1\nif [ -z \"$skip\" ]; then\n case \"$(uname -s)\" in Darwin) os=macos ;; Linux) os=linux ;; *) exit 1 ;; esac\n case \"$(uname -m)\" in arm64|aarch64) arch=arm64 ;; x86_64|amd64) arch=amd64 ;; *) exit 1 ;; esac\n URL=\"https://api.endorlabs.com/download/latest/endorctl_${os}_${arch}\"\n ARCH_KEY=\"ARCH_TYPE_$(echo \"${os}_${arch}\" | tr '[:lower:]' '[:upper:]')\"\n current=$([ -x \"$BIN\" ] && \"$BIN\" --version 2>/dev/null | awk '/version/ {print $NF; exit}')\n meta=$(curl -fsSL --retry 5 --retry-connrefused --retry-all-errors https://api.endorlabs.com/meta/version)\n latest=$(echo \"$meta\" | sed -n 's/.*\"ClientVersion\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p')\n expected_sha=$(echo \"$meta\" | sed -n \"s/.*\\\"${ARCH_KEY}\\\"[[:space:]]*:[[:space:]]*\\\"\\([a-f0-9]*\\)\\\".*/\\1/p\")\n uptodate=\n [ -n \"$current\" ] && { [ -z \"$latest\" ] || [ \"$current\" = \"$latest\" ]; } && uptodate=1\n if [ -z \"$uptodate\" ]; then\n DIR=$(dirname \"$BIN\")\n mkdir -p \"$DIR\"\n # Sweep leftovers from interrupted past runs. Age-gated so a concurrent\n # session's in-flight download is never deleted; the name cannot match the\n # installed binary (\"endorctl\").\n find \"$DIR\" -name 'endorctl-download-*' -mmin +60 -delete 2>/dev/null\n TMP=$(mktemp \"$DIR/endorctl-download-XXXXXX\") || exit 1\n curl -fsSL --retry 5 --retry-connrefused --retry-all-errors -o \"$TMP\" \"$URL\" || { rm -f \"$TMP\"; exit 1; }\n [ ${#expected_sha} -eq 64 ] || { rm -f \"$TMP\"; exit 1; }\n case \"$expected_sha\" in *[!0-9a-f]*) rm -f \"$TMP\"; exit 1 ;; esac\n if command -v sha256sum >/dev/null 2>&1; then sum=$(sha256sum \"$TMP\" | awk '{print $1}'); else sum=$(shasum -a 256 \"$TMP\" | awk '{print $1}'); fi\n [ \"$sum\" = \"$expected_sha\" ] || { rm -f \"$TMP\"; exit 1; }\n chmod +x \"$TMP\" || { rm -f \"$TMP\"; exit 1; }\n mv \"$TMP\" \"$BIN\"\n fi\nfi\n\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit claudecode" + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit claudecode" + } + ] + } + ], + "PreToolUse": [ + { + "hooks": [ + { + "type": "command", + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit claudecode" + } + ], + "matcher": ".*" + } + ], + "PostToolUse": [ + { + "hooks": [ + { + "type": "command", + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit claudecode" + } + ], + "matcher": ".*" + } + ], + "PostToolUseFailure": [ + { + "hooks": [ + { + "type": "command", + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit claudecode" + } + ], + "matcher": ".*" + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit claudecode" + } + ] + } + ] + } +} diff --git a/agent-governance/examples/claude/settings.windows.json b/agent-governance/examples/claude/settings.windows.json new file mode 100644 index 0000000..575b148 --- /dev/null +++ b/agent-governance/examples/claude/settings.windows.json @@ -0,0 +1,74 @@ +{ + "env": { + "AGENT_HOOK_ENDOR_API": "https://api.endorlabs.com", + "AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY": "PEPE", + "AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET": "PAPA", + "AGENT_HOOK_ENDOR_NAMESPACE": "spiderman", + "ENDOR_AI_AUDIT_CACHE_ENABLED": "true" + }, + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "powershell -NoProfile -EncodedCommand JABCAGkAbgAgAD0AIABKAG8AaQBuAC0AUABhAHQAaAAgACQAZQBuAHYAOgBVAFMARQBSAFAAUgBPAEYASQBMAEUAIAAnAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAJwAKACQAcwBrAGkAcAAgAD0AIAAkAGYAYQBsAHMAZQAKAGkAZgAgACgAJABlAG4AdgA6AEUATgBEAE8AUgBDAFQATABfAFMASwBJAFAAXwBVAFAARABBAFQARQAgAC0AYQBuAGQAIAAoAFQAZQBzAHQALQBQAGEAdABoACAAJABCAGkAbgApACkAIAB7ACAAJABzAGsAaQBwACAAPQAgACQAdAByAHUAZQAgAH0ACgBpAGYAIAAoAC0AbgBvAHQAIAAkAHMAawBpAHAAKQAgAHsACgAgACAAcwB3AGkAdABjAGgAIAAoACQAZQBuAHYAOgBQAFIATwBDAEUAUwBTAE8AUgBfAEEAUgBDAEgASQBUAEUAQwBUAFUAUgBFACkAIAB7AAoAIAAgACAAIAAnAEEATQBEADYANAAnACAAewAgACQAYQByAGMAaAAgAD0AIAAnAGEAbQBkADYANAAnACAAfQAKACAAIAAgACAAJwBBAFIATQA2ADQAJwAgAHsAIAAkAGEAcgBjAGgAIAA9ACAAJwBhAHIAbQA2ADQAJwAgAH0ACgAgACAAIAAgAGQAZQBmAGEAdQBsAHQAIAB7ACAAZQB4AGkAdAAgADEAIAB9AAoAIAAgAH0ACgAgACAAJAB1AHIAbAAgAD0AIAAiAGgAdAB0AHAAcwA6AC8ALwBhAHAAaQAuAGUAbgBkAG8AcgBsAGEAYgBzAC4AYwBvAG0ALwBkAG8AdwBuAGwAbwBhAGQALwBsAGEAdABlAHMAdAAvAGUAbgBkAG8AcgBjAHQAbABfAHcAaQBuAGQAbwB3AHMAXwAkAGEAcgBjAGgALgBlAHgAZQAiAAoAIAAgACQAYQByAGMAaABLAGUAeQAgAD0AIAAnAEEAUgBDAEgAXwBUAFkAUABFAF8AVwBJAE4ARABPAFcAUwBfACcAIAArACAAJABhAHIAYwBoAC4AVABvAFUAcABwAGUAcgAoACkACgAgACAAJABjAHUAcgByAGUAbgB0ACAAPQAgACcAJwAKACAAIABpAGYAIAAoAFQAZQBzAHQALQBQAGEAdABoACAAJABCAGkAbgApACAAewAKACAAIAAgACAAdAByAHkAIAB7AAoAIAAgACAAIAAgACAAJABsAGkAbgBlACAAPQAgACgAJgAgACQAQgBpAG4AIAAtAC0AdgBlAHIAcwBpAG8AbgAgADIAPgAkAG4AdQBsAGwAKQAgAHwAIABTAGUAbABlAGMAdAAtAFMAdAByAGkAbgBnACAALQBQAGEAdAB0AGUAcgBuACAAJwB2AGUAcgBzAGkAbwBuACcAIAB8ACAAUwBlAGwAZQBjAHQALQBPAGIAagBlAGMAdAAgAC0ARgBpAHIAcwB0ACAAMQAKACAAIAAgACAAIAAgAGkAZgAgACgAJABsAGkAbgBlACkAIAB7ACAAJABjAHUAcgByAGUAbgB0ACAAPQAgACgAJABsAGkAbgBlAC4AVABvAFMAdAByAGkAbgBnACgAKQAgAC0AcwBwAGwAaQB0ACAAJwBcAHMAKwAnACkAWwAtADEAXQAgAH0ACgAgACAAIAAgAH0AIABjAGEAdABjAGgAIAB7AH0ACgAgACAAfQAKACAAIAAkAGwAYQB0AGUAcwB0ACAAPQAgACcAJwA7ACAAJABlAHgAcABlAGMAdABlAGQAUwBoAGEAIAA9ACAAJwAnAAoAIAAgAHQAcgB5ACAAewAKACAAIAAgACAAJABtAGUAdABhACAAPQAgAEkAbgB2AG8AawBlAC0AUgBlAHMAdABNAGUAdABoAG8AZAAgAC0AVQByAGkAIAAnAGgAdAB0AHAAcwA6AC8ALwBhAHAAaQAuAGUAbgBkAG8AcgBsAGEAYgBzAC4AYwBvAG0ALwBtAGUAdABhAC8AdgBlAHIAcwBpAG8AbgAnACAALQBUAGkAbQBlAG8AdQB0AFMAZQBjACAAMwAwAAoAIAAgACAAIAAkAGwAYQB0AGUAcwB0ACAAPQAgAFsAcwB0AHIAaQBuAGcAXQAkAG0AZQB0AGEALgBDAGwAaQBlAG4AdABWAGUAcgBzAGkAbwBuAAoAIAAgACAAIAAkAGUAeABwAGUAYwB0AGUAZABTAGgAYQAgAD0AIABbAHMAdAByAGkAbgBnAF0AJABtAGUAdABhAC4AJABhAHIAYwBoAEsAZQB5AAoAIAAgAH0AIABjAGEAdABjAGgAIAB7AH0ACgAgACAAJAB1AHAAdABvAGQAYQB0AGUAIAA9ACAAKAAkAGMAdQByAHIAZQBuAHQAIAAtAGEAbgBkACAAKAAoAC0AbgBvAHQAIAAkAGwAYQB0AGUAcwB0ACkAIAAtAG8AcgAgACgAJABjAHUAcgByAGUAbgB0ACAALQBlAHEAIAAkAGwAYQB0AGUAcwB0ACkAKQApAAoAIAAgAGkAZgAgACgALQBuAG8AdAAgACQAdQBwAHQAbwBkAGEAdABlACkAIAB7AAoAIAAgACAAIABpAGYAIAAoACQAZQB4AHAAZQBjAHQAZQBkAFMAaABhACAALQBuAG8AdABtAGEAdABjAGgAIAAnAF4AWwAwAC0AOQBhAC0AZgBBAC0ARgBdAHsANgA0AH0AJAAnACkAIAB7ACAAZQB4AGkAdAAgADEAIAB9AAoAIAAgACAAIAAkAGQAaQByACAAPQAgAFMAcABsAGkAdAAtAFAAYQB0AGgAIAAkAEIAaQBuAAoAIAAgACAAIABOAGUAdwAtAEkAdABlAG0AIAAtAEkAdABlAG0AVAB5AHAAZQAgAEQAaQByAGUAYwB0AG8AcgB5ACAALQBGAG8AcgBjAGUAIAAtAFAAYQB0AGgAIAAkAGQAaQByACAAfAAgAE8AdQB0AC0ATgB1AGwAbAAKACAAIAAgACAAIwAgAFMAdwBlAGUAcAAgAGwAZQBmAHQAbwB2AGUAcgBzACAAZgByAG8AbQAgAGkAbgB0AGUAcgByAHUAcAB0AGUAZAAgAHAAYQBzAHQAIAByAHUAbgBzAC4AIABBAGcAZQAtAGcAYQB0AGUAZAAgAHMAbwAgAGEAIABjAG8AbgBjAHUAcgByAGUAbgB0AAoAIAAgACAAIAAjACAAcwBlAHMAcwBpAG8AbgAnAHMAIABpAG4ALQBmAGwAaQBnAGgAdAAgAGQAbwB3AG4AbABvAGEAZAAgAGkAcwAgAG4AZQB2AGUAcgAgAGQAZQBsAGUAdABlAGQAOwAgAHQAaABlACAAbgBhAG0AZQAgAGMAYQBuAG4AbwB0ACAAbQBhAHQAYwBoACAAdABoAGUACgAgACAAIAAgACMAIABpAG4AcwB0AGEAbABsAGUAZAAgAGIAaQBuAGEAcgB5ACAAKAAiAGUAbgBkAG8AcgBjAHQAbAAuAGUAeABlACIAKQAuAAoAIAAgACAAIABHAGUAdAAtAEMAaABpAGwAZABJAHQAZQBtACAALQBQAGEAdABoACAAJABkAGkAcgAgAC0ARgBpAGwAdABlAHIAIAAnAGUAbgBkAG8AcgBjAHQAbAAtAGQAbwB3AG4AbABvAGEAZAAtACoAJwAgAC0ARgBvAHIAYwBlACAALQBFAHIAcgBvAHIAQQBjAHQAaQBvAG4AIABTAGkAbABlAG4AdABsAHkAQwBvAG4AdABpAG4AdQBlACAAfAAKACAAIAAgACAAIAAgAFcAaABlAHIAZQAtAE8AYgBqAGUAYwB0ACAAewAgACQAXwAuAEwAYQBzAHQAVwByAGkAdABlAFQAaQBtAGUAIAAtAGwAdAAgACgARwBlAHQALQBEAGEAdABlACkALgBBAGQAZABNAGkAbgB1AHQAZQBzACgALQA2ADAAKQAgAH0AIAB8AAoAIAAgACAAIAAgACAAUgBlAG0AbwB2AGUALQBJAHQAZQBtACAALQBGAG8AcgBjAGUAIAAtAEUAcgByAG8AcgBBAGMAdABpAG8AbgAgAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUACgAgACAAIAAgACQAdABtAHAAIAA9ACAASgBvAGkAbgAtAFAAYQB0AGgAIAAkAGQAaQByACAAKAAnAGUAbgBkAG8AcgBjAHQAbAAtAGQAbwB3AG4AbABvAGEAZAAtACcAIAArACAAWwBJAE8ALgBQAGEAdABoAF0AOgA6AEcAZQB0AFIAYQBuAGQAbwBtAEYAaQBsAGUATgBhAG0AZQAoACkAKQAKACAAIAAgACAAdAByAHkAIAB7ACAASQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACAALQBVAHIAaQAgACQAdQByAGwAIAAtAE8AdQB0AEYAaQBsAGUAIAAkAHQAbQBwACAALQBUAGkAbQBlAG8AdQB0AFMAZQBjACAAMQAyADAAIAB9ACAAYwBhAHQAYwBoACAAewAgAGkAZgAgACgAVABlAHMAdAAtAFAAYQB0AGgAIAAkAHQAbQBwACkAIAB7ACAAUgBlAG0AbwB2AGUALQBJAHQAZQBtACAALQBGAG8AcgBjAGUAIAAkAHQAbQBwACAALQBFAHIAcgBvAHIAQQBjAHQAaQBvAG4AIABTAGkAbABlAG4AdABsAHkAQwBvAG4AdABpAG4AdQBlACAAfQA7ACAAZQB4AGkAdAAgADEAIAB9AAoAIAAgACAAIABpAGYAIAAoACgARwBlAHQALQBGAGkAbABlAEgAYQBzAGgAIAAtAEEAbABnAG8AcgBpAHQAaABtACAAUwBIAEEAMgA1ADYAIAAtAFAAYQB0AGgAIAAkAHQAbQBwACkALgBIAGEAcwBoACAALQBuAGUAIAAkAGUAeABwAGUAYwB0AGUAZABTAGgAYQApACAAewAgAFIAZQBtAG8AdgBlAC0ASQB0AGUAbQAgAC0ARgBvAHIAYwBlACAAJAB0AG0AcAAgAC0ARQByAHIAbwByAEEAYwB0AGkAbwBuACAAUwBpAGwAZQBuAHQAbAB5AEMAbwBuAHQAaQBuAHUAZQA7ACAAZQB4AGkAdAAgADEAIAB9AAoAIAAgACAAIABNAG8AdgBlAC0ASQB0AGUAbQAgAC0ARgBvAHIAYwBlACAAJAB0AG0AcAAgACQAQgBpAG4ACgAgACAAfQAKAH0ACgAmACAAIgAkAGUAbgB2ADoAVQBTAEUAUgBQAFIATwBGAEkATABFAFwALgBlAG4AZABvAHIAYwB0AGwAXABlAG4AZABvAHIAYwB0AGwALgBlAHgAZQAiACAALQAtAGEAcABpACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAEEAUABJACIAIAAtAC0AbgBhAG0AZQBzAHAAYQBjAGUAIAAiACQAZQBuAHYAOgBBAEcARQBOAFQAXwBIAE8ATwBLAF8ARQBOAEQATwBSAF8ATgBBAE0ARQBTAFAAQQBDAEUAIgAgAC0ALQBhAHAAaQAtAGsAZQB5ACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAEEAUABJAF8AQwBSAEUARABFAE4AVABJAEEATABTAF8ASwBFAFkAIgAgAC0ALQBhAHAAaQAtAHMAZQBjAHIAZQB0ACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAEEAUABJAF8AQwBSAEUARABFAE4AVABJAEEATABTAF8AUwBFAEMAUgBFAFQAIgAgAGEAaQAtAGEAdQBkAGkAdAAgAGMAbABhAHUAZABlAGMAbwBkAGUAOwAgAGUAeABpAHQAIAAkAEwAQQBTAFQARQBYAEkAVABDAE8ARABFAA==" + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "powershell -NoProfile -EncodedCommand JgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAGwAYQB1AGQAZQBjAG8AZABlADsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQA=" + } + ] + } + ], + "PreToolUse": [ + { + "hooks": [ + { + "type": "command", + "command": "powershell -NoProfile -EncodedCommand JgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAGwAYQB1AGQAZQBjAG8AZABlADsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQA=" + } + ], + "matcher": ".*" + } + ], + "PostToolUse": [ + { + "hooks": [ + { + "type": "command", + "command": "powershell -NoProfile -EncodedCommand JgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAGwAYQB1AGQAZQBjAG8AZABlADsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQA=" + } + ], + "matcher": ".*" + } + ], + "PostToolUseFailure": [ + { + "hooks": [ + { + "type": "command", + "command": "powershell -NoProfile -EncodedCommand JgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAGwAYQB1AGQAZQBjAG8AZABlADsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQA=" + } + ], + "matcher": ".*" + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "command", + "command": "powershell -NoProfile -EncodedCommand JgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAGwAYQB1AGQAZQBjAG8AZABlADsAIABlAHgAaQB0ACAAJABMAEEAUwBUAEUAWABJAFQAQwBPAEQARQA=" + } + ] + } + ] + } +} diff --git a/agent-governance/examples/cursor/hooks.json b/agent-governance/examples/cursor/hooks.json new file mode 100644 index 0000000..fc9619f --- /dev/null +++ b/agent-governance/examples/cursor/hooks.json @@ -0,0 +1,65 @@ +{ + "version": 1, + "hooks": { + "sessionStart": [ + { + "command": "umask 077\nT=\"$HOME/.endorctl-cursor-stdin.$$\"\ncat > \"$T\"\nchmod 600 \"$T\"\ntrap 'rm -f \"$T\"' EXIT\nBIN=\"${HOME}/.endorctl/endorctl\"\nskip=\n[ -n \"${ENDORCTL_SKIP_UPDATE:-}\" ] && [ -x \"$BIN\" ] && skip=1\nif [ -z \"$skip\" ]; then\n case \"$(uname -s)\" in Darwin) os=macos ;; Linux) os=linux ;; *) exit 1 ;; esac\n case \"$(uname -m)\" in arm64|aarch64) arch=arm64 ;; x86_64|amd64) arch=amd64 ;; *) exit 1 ;; esac\n URL=\"https://api.endorlabs.com/download/latest/endorctl_${os}_${arch}\"\n ARCH_KEY=\"ARCH_TYPE_$(echo \"${os}_${arch}\" | tr '[:lower:]' '[:upper:]')\"\n current=$([ -x \"$BIN\" ] && \"$BIN\" --version 2>/dev/null | awk '/version/ {print $NF; exit}')\n meta=$(curl -fsSL --retry 5 --retry-connrefused --retry-all-errors https://api.endorlabs.com/meta/version)\n latest=$(echo \"$meta\" | sed -n 's/.*\"ClientVersion\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p')\n expected_sha=$(echo \"$meta\" | sed -n \"s/.*\\\"${ARCH_KEY}\\\"[[:space:]]*:[[:space:]]*\\\"\\([a-f0-9]*\\)\\\".*/\\1/p\")\n uptodate=\n [ -n \"$current\" ] && { [ -z \"$latest\" ] || [ \"$current\" = \"$latest\" ]; } && uptodate=1\n if [ -z \"$uptodate\" ]; then\n DIR=$(dirname \"$BIN\")\n mkdir -p \"$DIR\"\n # Sweep leftovers from interrupted past runs. Age-gated so a concurrent\n # session's in-flight download is never deleted; the name cannot match the\n # installed binary (\"endorctl\").\n find \"$DIR\" -name 'endorctl-download-*' -mmin +60 -delete 2>/dev/null\n TMP=$(mktemp \"$DIR/endorctl-download-XXXXXX\") || exit 1\n curl -fsSL --retry 5 --retry-connrefused --retry-all-errors -o \"$TMP\" \"$URL\" || { rm -f \"$TMP\"; exit 1; }\n [ ${#expected_sha} -eq 64 ] || { rm -f \"$TMP\"; exit 1; }\n case \"$expected_sha\" in *[!0-9a-f]*) rm -f \"$TMP\"; exit 1 ;; esac\n if command -v sha256sum >/dev/null 2>&1; then sum=$(sha256sum \"$TMP\" | awk '{print $1}'); else sum=$(shasum -a 256 \"$TMP\" | awk '{print $1}'); fi\n [ \"$sum\" = \"$expected_sha\" ] || { rm -f \"$TMP\"; exit 1; }\n chmod +x \"$TMP\" || { rm -f \"$TMP\"; exit 1; }\n mv \"$TMP\" \"$BIN\"\n fi\nfi\nENDOR_AI_AUDIT_CACHE_ENABLED='true' \"$HOME/.endorctl/endorctl\" --api 'https://api.endorlabs.com' --namespace 'spiderman' --api-key 'PEPE' --api-secret 'PAPA' ai-audit cursor < \"$T\"" + } + ], + "sessionEnd": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "beforeSubmitPrompt": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "preToolUse": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "postToolUse": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "postToolUseFailure": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "beforeShellExecution": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "afterShellExecution": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "beforeMCPExecution": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "beforeReadFile": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "afterFileEdit": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ], + "stop": [ + { + "command": "\"$HOME/.endorctl/endorctl\" --api \"$AGENT_HOOK_ENDOR_API\" --namespace \"$AGENT_HOOK_ENDOR_NAMESPACE\" --api-key \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY\" --api-secret \"$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET\" ai-audit cursor" + } + ] + } +} diff --git a/agent-governance/examples/cursor/hooks.windows.json b/agent-governance/examples/cursor/hooks.windows.json new file mode 100644 index 0000000..38ac0a8 --- /dev/null +++ b/agent-governance/examples/cursor/hooks.windows.json @@ -0,0 +1,65 @@ +{ + "version": 1, + "hooks": { + "sessionStart": [ + { + "command": "powershell -NoProfile -EncodedCommand JABPAHUAdABwAHUAdABFAG4AYwBvAGQAaQBuAGcAIAA9ACAAWwBTAHkAcwB0AGUAbQAuAFQAZQB4AHQALgBVAFQARgA4AEUAbgBjAG8AZABpAG4AZwBdADoAOgBuAGUAdwAoACQAZgBhAGwAcwBlACkACgAkAGkAbgAgAD0AIABbAFMAeQBzAHQAZQBtAC4ASQBPAC4AUwB0AHIAZQBhAG0AUgBlAGEAZABlAHIAXQA6ADoAbgBlAHcAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoATwBwAGUAbgBTAHQAYQBuAGQAYQByAGQASQBuAHAAdQB0ACgAKQAsACAAJABPAHUAdABwAHUAdABFAG4AYwBvAGQAaQBuAGcAKQAuAFIAZQBhAGQAVABvAEUAbgBkACgAKQAKACMAIABTAGkAbABlAG4AYwBlACAAdABoAGUAIABwAHIAbwBnAHIAZQBzAHMAIABzAHQAcgBlAGEAbQA7ACAAZQBsAHMAZQAgAFAAbwB3AGUAcgBTAGgAZQBsAGwAIABzAGUAcgBpAGEAbABpAHoAZQBzACAAaQB0ACAAdABvACAAcwB0AGQAZQByAHIAIABhAHMAIAAiACMAPAAgAEMATABJAFgATQBMACIACgAjACAAbgBvAGkAcwBlACAAdABoAGUAIABNAEQATQAgAGgAbwBvAGsAIABtAGkAcwByAGUAYQBkAHMAIABhAHMAIABlAHIAcgBvAHIAcwAgACgAMgA+ACQAbgB1AGwAbAAgAC8AIAAtAEUAcgByAG8AcgBBAGMAdABpAG8AbgAgAGQAbwBuACcAdAAgAGMAbwB2AGUAcgAgAGkAdAApAC4ACgAkAFAAcgBvAGcAcgBlAHMAcwBQAHIAZQBmAGUAcgBlAG4AYwBlACAAPQAgACcAUwBpAGwAZQBuAHQAbAB5AEMAbwBuAHQAaQBuAHUAZQAnAAoAJABCAGkAbgAgAD0AIABKAG8AaQBuAC0AUABhAHQAaAAgACQAZQBuAHYAOgBVAFMARQBSAFAAUgBPAEYASQBMAEUAIAAnAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAJwAKACQAcwBrAGkAcAAgAD0AIAAkAGYAYQBsAHMAZQAKAGkAZgAgACgAJABlAG4AdgA6AEUATgBEAE8AUgBDAFQATABfAFMASwBJAFAAXwBVAFAARABBAFQARQAgAC0AYQBuAGQAIAAoAFQAZQBzAHQALQBQAGEAdABoACAAJABCAGkAbgApACkAIAB7ACAAJABzAGsAaQBwACAAPQAgACQAdAByAHUAZQAgAH0ACgBpAGYAIAAoAC0AbgBvAHQAIAAkAHMAawBpAHAAKQAgAHsACgAgACAAcwB3AGkAdABjAGgAIAAoACQAZQBuAHYAOgBQAFIATwBDAEUAUwBTAE8AUgBfAEEAUgBDAEgASQBUAEUAQwBUAFUAUgBFACkAIAB7AAoAIAAgACAAIAAnAEEATQBEADYANAAnACAAewAgACQAYQByAGMAaAAgAD0AIAAnAGEAbQBkADYANAAnACAAfQAKACAAIAAgACAAJwBBAFIATQA2ADQAJwAgAHsAIAAkAGEAcgBjAGgAIAA9ACAAJwBhAHIAbQA2ADQAJwAgAH0ACgAgACAAIAAgAGQAZQBmAGEAdQBsAHQAIAB7ACAAZQB4AGkAdAAgADEAIAB9AAoAIAAgAH0ACgAgACAAJAB1AHIAbAAgAD0AIAAiAGgAdAB0AHAAcwA6AC8ALwBhAHAAaQAuAGUAbgBkAG8AcgBsAGEAYgBzAC4AYwBvAG0ALwBkAG8AdwBuAGwAbwBhAGQALwBsAGEAdABlAHMAdAAvAGUAbgBkAG8AcgBjAHQAbABfAHcAaQBuAGQAbwB3AHMAXwAkAGEAcgBjAGgALgBlAHgAZQAiAAoAIAAgACQAYQByAGMAaABLAGUAeQAgAD0AIAAnAEEAUgBDAEgAXwBUAFkAUABFAF8AVwBJAE4ARABPAFcAUwBfACcAIAArACAAJABhAHIAYwBoAC4AVABvAFUAcABwAGUAcgAoACkACgAgACAAJABjAHUAcgByAGUAbgB0ACAAPQAgACcAJwAKACAAIABpAGYAIAAoAFQAZQBzAHQALQBQAGEAdABoACAAJABCAGkAbgApACAAewAKACAAIAAgACAAdAByAHkAIAB7AAoAIAAgACAAIAAgACAAJABsAGkAbgBlACAAPQAgACgAJgAgACQAQgBpAG4AIAAtAC0AdgBlAHIAcwBpAG8AbgAgADIAPgAkAG4AdQBsAGwAKQAgAHwAIABTAGUAbABlAGMAdAAtAFMAdAByAGkAbgBnACAALQBQAGEAdAB0AGUAcgBuACAAJwB2AGUAcgBzAGkAbwBuACcAIAB8ACAAUwBlAGwAZQBjAHQALQBPAGIAagBlAGMAdAAgAC0ARgBpAHIAcwB0ACAAMQAKACAAIAAgACAAIAAgAGkAZgAgACgAJABsAGkAbgBlACkAIAB7ACAAJABjAHUAcgByAGUAbgB0ACAAPQAgACgAJABsAGkAbgBlAC4AVABvAFMAdAByAGkAbgBnACgAKQAgAC0AcwBwAGwAaQB0ACAAJwBcAHMAKwAnACkAWwAtADEAXQAgAH0ACgAgACAAIAAgAH0AIABjAGEAdABjAGgAIAB7AH0ACgAgACAAfQAKACAAIAAkAGwAYQB0AGUAcwB0ACAAPQAgACcAJwA7ACAAJABlAHgAcABlAGMAdABlAGQAUwBoAGEAIAA9ACAAJwAnAAoAIAAgAHQAcgB5ACAAewAKACAAIAAgACAAJABtAGUAdABhACAAPQAgAEkAbgB2AG8AawBlAC0AUgBlAHMAdABNAGUAdABoAG8AZAAgAC0AVQByAGkAIAAnAGgAdAB0AHAAcwA6AC8ALwBhAHAAaQAuAGUAbgBkAG8AcgBsAGEAYgBzAC4AYwBvAG0ALwBtAGUAdABhAC8AdgBlAHIAcwBpAG8AbgAnACAALQBUAGkAbQBlAG8AdQB0AFMAZQBjACAAMwAwAAoAIAAgACAAIAAkAGwAYQB0AGUAcwB0ACAAPQAgAFsAcwB0AHIAaQBuAGcAXQAkAG0AZQB0AGEALgBDAGwAaQBlAG4AdABWAGUAcgBzAGkAbwBuAAoAIAAgACAAIAAkAGUAeABwAGUAYwB0AGUAZABTAGgAYQAgAD0AIABbAHMAdAByAGkAbgBnAF0AJABtAGUAdABhAC4AQwBsAGkAZQBuAHQAQwBoAGUAYwBrAHMAdQBtAHMALgAkAGEAcgBjAGgASwBlAHkACgAgACAAfQAgAGMAYQB0AGMAaAAgAHsAfQAKACAAIAAkAHUAcAB0AG8AZABhAHQAZQAgAD0AIAAoACQAYwB1AHIAcgBlAG4AdAAgAC0AYQBuAGQAIAAoACgALQBuAG8AdAAgACQAbABhAHQAZQBzAHQAKQAgAC0AbwByACAAKAAkAGMAdQByAHIAZQBuAHQAIAAtAGUAcQAgACQAbABhAHQAZQBzAHQAKQApACkACgAgACAAaQBmACAAKAAtAG4AbwB0ACAAJAB1AHAAdABvAGQAYQB0AGUAKQAgAHsACgAgACAAIAAgAGkAZgAgACgAJABlAHgAcABlAGMAdABlAGQAUwBoAGEAIAAtAG4AbwB0AG0AYQB0AGMAaAAgACcAXgBbADAALQA5AGEALQBmAEEALQBGAF0AewA2ADQAfQAkACcAKQAgAHsAIABlAHgAaQB0ACAAMQAgAH0ACgAgACAAIAAgACQAZABpAHIAIAA9ACAAUwBwAGwAaQB0AC0AUABhAHQAaAAgACQAQgBpAG4ACgAgACAAIAAgAE4AZQB3AC0ASQB0AGUAbQAgAC0ASQB0AGUAbQBUAHkAcABlACAARABpAHIAZQBjAHQAbwByAHkAIAAtAEYAbwByAGMAZQAgAC0AUABhAHQAaAAgACQAZABpAHIAIAB8ACAATwB1AHQALQBOAHUAbABsAAoAIAAgACAAIAAjACAAUwB3AGUAZQBwACAAbABlAGYAdABvAHYAZQByAHMAIABmAHIAbwBtACAAaQBuAHQAZQByAHIAdQBwAHQAZQBkACAAcABhAHMAdAAgAHIAdQBuAHMALgAgAEEAZwBlAC0AZwBhAHQAZQBkACAAcwBvACAAYQAgAGMAbwBuAGMAdQByAHIAZQBuAHQACgAgACAAIAAgACMAIABzAGUAcwBzAGkAbwBuACcAcwAgAGkAbgAtAGYAbABpAGcAaAB0ACAAZABvAHcAbgBsAG8AYQBkACAAaQBzACAAbgBlAHYAZQByACAAZABlAGwAZQB0AGUAZAA7ACAAdABoAGUAIABuAGEAbQBlACAAYwBhAG4AbgBvAHQAIABtAGEAdABjAGgAIAB0AGgAZQAKACAAIAAgACAAIwAgAGkAbgBzAHQAYQBsAGwAZQBkACAAYgBpAG4AYQByAHkAIAAoACIAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgApAC4ACgAgACAAIAAgAEcAZQB0AC0AQwBoAGkAbABkAEkAdABlAG0AIAAtAFAAYQB0AGgAIAAkAGQAaQByACAALQBGAGkAbAB0AGUAcgAgACcAZQBuAGQAbwByAGMAdABsAC0AZABvAHcAbgBsAG8AYQBkAC0AKgAnACAALQBGAG8AcgBjAGUAIAAtAEUAcgByAG8AcgBBAGMAdABpAG8AbgAgAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIAB8AAoAIAAgACAAIAAgACAAVwBoAGUAcgBlAC0ATwBiAGoAZQBjAHQAIAB7ACAAJABfAC4ATABhAHMAdABXAHIAaQB0AGUAVABpAG0AZQAgAC0AbAB0ACAAKABHAGUAdAAtAEQAYQB0AGUAKQAuAEEAZABkAE0AaQBuAHUAdABlAHMAKAAtADYAMAApACAAfQAgAHwACgAgACAAIAAgACAAIABSAGUAbQBvAHYAZQAtAEkAdABlAG0AIAAtAEYAbwByAGMAZQAgAC0ARQByAHIAbwByAEEAYwB0AGkAbwBuACAAUwBpAGwAZQBuAHQAbAB5AEMAbwBuAHQAaQBuAHUAZQAKACAAIAAgACAAJAB0AG0AcAAgAD0AIABKAG8AaQBuAC0AUABhAHQAaAAgACQAZABpAHIAIAAoACcAZQBuAGQAbwByAGMAdABsAC0AZABvAHcAbgBsAG8AYQBkAC0AJwAgACsAIABbAEkATwAuAFAAYQB0AGgAXQA6ADoARwBlAHQAUgBhAG4AZABvAG0ARgBpAGwAZQBOAGEAbQBlACgAKQApAAoAIAAgACAAIAB0AHIAeQAgAHsAIABJAG4AdgBvAGsAZQAtAFcAZQBiAFIAZQBxAHUAZQBzAHQAIAAtAFUAcgBpACAAJAB1AHIAbAAgAC0ATwB1AHQARgBpAGwAZQAgACQAdABtAHAAIAAtAFQAaQBtAGUAbwB1AHQAUwBlAGMAIAAxADIAMAAgAH0AIABjAGEAdABjAGgAIAB7ACAAaQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgACQAdABtAHAAKQAgAHsAIABSAGUAbQBvAHYAZQAtAEkAdABlAG0AIAAtAEYAbwByAGMAZQAgACQAdABtAHAAIAAtAEUAcgByAG8AcgBBAGMAdABpAG8AbgAgAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIAB9ADsAIABlAHgAaQB0ACAAMQAgAH0ACgAgACAAIAAgAGkAZgAgACgAKABHAGUAdAAtAEYAaQBsAGUASABhAHMAaAAgAC0AQQBsAGcAbwByAGkAdABoAG0AIABTAEgAQQAyADUANgAgAC0AUABhAHQAaAAgACQAdABtAHAAKQAuAEgAYQBzAGgAIAAtAG4AZQAgACQAZQB4AHAAZQBjAHQAZQBkAFMAaABhACkAIAB7ACAAUgBlAG0AbwB2AGUALQBJAHQAZQBtACAALQBGAG8AcgBjAGUAIAAkAHQAbQBwACAALQBFAHIAcgBvAHIAQQBjAHQAaQBvAG4AIABTAGkAbABlAG4AdABsAHkAQwBvAG4AdABpAG4AdQBlADsAIABlAHgAaQB0ACAAMQAgAH0ACgAgACAAIAAgAE0AbwB2AGUALQBJAHQAZQBtACAALQBGAG8AcgBjAGUAIAAkAHQAbQBwACAAJABCAGkAbgAKACAAIAB9AAoAfQAKACQAZQBuAHYAOgBFAE4ARABPAFIAXwBBAEkAXwBBAFUARABJAFQAXwBDAEEAQwBIAEUAXwBFAE4AQQBCAEwARQBEACAAPQAgACcAdAByAHUAZQAnAAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACcAaAB0AHQAcABzADoALwAvAGEAcABpAC4AZQBuAGQAbwByAGwAYQBiAHMALgBjAG8AbQAnACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAJwBzAHAAaQBkAGUAcgBtAGEAbgAnACAALQAtAGEAcABpAC0AawBlAHkAIAAnAFAARQBQAEUAJwAgAC0ALQBhAHAAaQAtAHMAZQBjAHIAZQB0ACAAJwBQAEEAUABBACcAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "sessionEnd": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "beforeSubmitPrompt": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "preToolUse": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "postToolUse": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "postToolUseFailure": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "beforeShellExecution": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "afterShellExecution": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "beforeMCPExecution": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "beforeReadFile": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "afterFileEdit": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ], + "stop": [ + { + "command": "powershell -NoProfile -EncodedCommand JABQAHIAbwBnAHIAZQBzAHMAUAByAGUAZgBlAHIAZQBuAGMAZQAgAD0AIAAiAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAIgAKACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACAAPQAgAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AVQBUAEYAOABFAG4AYwBvAGQAaQBuAGcAXQA6ADoAbgBlAHcAKAAkAGYAYQBsAHMAZQApAAoAJABpAG4AIAA9ACAAaQBmACAAKABbAEMAbwBuAHMAbwBsAGUAXQA6ADoASQBzAEkAbgBwAHUAdABSAGUAZABpAHIAZQBjAHQAZQBkACkAIAB7ACAAWwBTAHkAcwB0AGUAbQAuAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByAF0AOgA6AG4AZQB3ACgAWwBDAG8AbgBzAG8AbABlAF0AOgA6AE8AcABlAG4AUwB0AGEAbgBkAGEAcgBkAEkAbgBwAHUAdAAoACkALAAgACQATwB1AHQAcAB1AHQARQBuAGMAbwBkAGkAbgBnACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkAIAB9ACAAZQBsAHMAZQAgAHsAIAAiACIAIAB9AAoAJABpAG4AIAB8ACAAJgAgACIAJABlAG4AdgA6AFUAUwBFAFIAUABSAE8ARgBJAEwARQBcAC4AZQBuAGQAbwByAGMAdABsAFwAZQBuAGQAbwByAGMAdABsAC4AZQB4AGUAIgAgAC0ALQBhAHAAaQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQAiACAALQAtAG4AYQBtAGUAcwBwAGEAYwBlACAAIgAkAGUAbgB2ADoAQQBHAEUATgBUAF8ASABPAE8ASwBfAEUATgBEAE8AUgBfAE4AQQBNAEUAUwBQAEEAQwBFACIAIAAtAC0AYQBwAGkALQBrAGUAeQAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAEsARQBZACIAIAAtAC0AYQBwAGkALQBzAGUAYwByAGUAdAAgACIAJABlAG4AdgA6AEEARwBFAE4AVABfAEgATwBPAEsAXwBFAE4ARABPAFIAXwBBAFAASQBfAEMAUgBFAEQARQBOAFQASQBBAEwAUwBfAFMARQBDAFIARQBUACIAIABhAGkALQBhAHUAZABpAHQAIABjAHUAcgBzAG8AcgA7ACAAZQB4AGkAdAAgACQATABBAFMAVABFAFgASQBUAEMATwBEAEUA" + } + ] + } +} diff --git a/agent-governance/scripts/download_endorctl.ps1 b/agent-governance/scripts/download_endorctl.ps1 new file mode 100644 index 0000000..3632d9a --- /dev/null +++ b/agent-governance/scripts/download_endorctl.ps1 @@ -0,0 +1,44 @@ +# Silence the progress stream; else PowerShell serializes it to stderr as "#< CLIXML" +# noise the MDM hook misreads as errors (2>$null / -ErrorAction don't cover it). +$ProgressPreference = 'SilentlyContinue' +$Bin = Join-Path $env:USERPROFILE '.endorctl\endorctl.exe' +$skip = $false +if ($env:ENDORCTL_SKIP_UPDATE -and (Test-Path $Bin)) { $skip = $true } +if (-not $skip) { + switch ($env:PROCESSOR_ARCHITECTURE) { + 'AMD64' { $arch = 'amd64' } + 'ARM64' { $arch = 'arm64' } + default { exit 1 } + } + $url = "https://api.endorlabs.com/download/latest/endorctl_windows_$arch.exe" + $archKey = 'ARCH_TYPE_WINDOWS_' + $arch.ToUpper() + $current = '' + if (Test-Path $Bin) { + try { + $line = (& $Bin --version 2>$null) | Select-String -Pattern 'version' | Select-Object -First 1 + if ($line) { $current = ($line.ToString() -split '\s+')[-1] } + } catch {} + } + $latest = ''; $expectedSha = '' + try { + $meta = Invoke-RestMethod -Uri 'https://api.endorlabs.com/meta/version' -TimeoutSec 30 + $latest = [string]$meta.ClientVersion + $expectedSha = [string]$meta.ClientChecksums.$archKey + } catch {} + $uptodate = ($current -and ((-not $latest) -or ($current -eq $latest))) + if (-not $uptodate) { + if ($expectedSha -notmatch '^[0-9a-fA-F]{64}$') { exit 1 } + $dir = Split-Path $Bin + New-Item -ItemType Directory -Force -Path $dir | Out-Null + # Sweep leftovers from interrupted past runs. Age-gated so a concurrent + # session's in-flight download is never deleted; the name cannot match the + # installed binary ("endorctl.exe"). + Get-ChildItem -Path $dir -Filter 'endorctl-download-*' -Force -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTime -lt (Get-Date).AddMinutes(-60) } | + Remove-Item -Force -ErrorAction SilentlyContinue + $tmp = Join-Path $dir ('endorctl-download-' + [IO.Path]::GetRandomFileName()) + try { Invoke-WebRequest -Uri $url -OutFile $tmp -TimeoutSec 120 } catch { if (Test-Path $tmp) { Remove-Item -Force $tmp -ErrorAction SilentlyContinue }; exit 1 } + if ((Get-FileHash -Algorithm SHA256 -Path $tmp).Hash -ne $expectedSha) { Remove-Item -Force $tmp -ErrorAction SilentlyContinue; exit 1 } + Move-Item -Force $tmp $Bin + } +} diff --git a/agent-governance/scripts/download_endorctl.sh b/agent-governance/scripts/download_endorctl.sh new file mode 100755 index 0000000..3568263 --- /dev/null +++ b/agent-governance/scripts/download_endorctl.sh @@ -0,0 +1,31 @@ +BIN="${HOME}/.endorctl/endorctl" +skip= +[ -n "${ENDORCTL_SKIP_UPDATE:-}" ] && [ -x "$BIN" ] && skip=1 +if [ -z "$skip" ]; then + case "$(uname -s)" in Darwin) os=macos ;; Linux) os=linux ;; *) exit 1 ;; esac + case "$(uname -m)" in arm64|aarch64) arch=arm64 ;; x86_64|amd64) arch=amd64 ;; *) exit 1 ;; esac + URL="https://api.endorlabs.com/download/latest/endorctl_${os}_${arch}" + ARCH_KEY="ARCH_TYPE_$(echo "${os}_${arch}" | tr '[:lower:]' '[:upper:]')" + current=$([ -x "$BIN" ] && "$BIN" --version 2>/dev/null | awk '/version/ {print $NF; exit}') + meta=$(curl -fsSL --retry 5 --retry-connrefused --retry-all-errors https://api.endorlabs.com/meta/version) + latest=$(echo "$meta" | sed -n 's/.*"ClientVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p') + expected_sha=$(echo "$meta" | sed -n "s/.*\"${ARCH_KEY}\"[[:space:]]*:[[:space:]]*\"\([a-f0-9]*\)\".*/\1/p") + uptodate= + [ -n "$current" ] && { [ -z "$latest" ] || [ "$current" = "$latest" ]; } && uptodate=1 + if [ -z "$uptodate" ]; then + DIR=$(dirname "$BIN") + mkdir -p "$DIR" + # Sweep leftovers from interrupted past runs. Age-gated so a concurrent + # session's in-flight download is never deleted; the name cannot match the + # installed binary ("endorctl"). + find "$DIR" -name 'endorctl-download-*' -mmin +60 -delete 2>/dev/null + TMP=$(mktemp "$DIR/endorctl-download-XXXXXX") || exit 1 + curl -fsSL --retry 5 --retry-connrefused --retry-all-errors -o "$TMP" "$URL" || { rm -f "$TMP"; exit 1; } + [ ${#expected_sha} -eq 64 ] || { rm -f "$TMP"; exit 1; } + case "$expected_sha" in *[!0-9a-f]*) rm -f "$TMP"; exit 1 ;; esac + if command -v sha256sum >/dev/null 2>&1; then sum=$(sha256sum "$TMP" | awk '{print $1}'); else sum=$(shasum -a 256 "$TMP" | awk '{print $1}'); fi + [ "$sum" = "$expected_sha" ] || { rm -f "$TMP"; exit 1; } + chmod +x "$TMP" || { rm -f "$TMP"; exit 1; } + mv "$TMP" "$BIN" + fi +fi diff --git a/agent-governance/scripts/render-plist.sh b/agent-governance/scripts/render-plist.sh new file mode 100755 index 0000000..b0f7dec --- /dev/null +++ b/agent-governance/scripts/render-plist.sh @@ -0,0 +1,88 @@ +#!/bin/sh +# render-plist.sh - wrap a managed-settings JSON into an MDM Configuration +# Profile (.mobileconfig). It reads the settings object on stdin and embeds it +# under a payload of the given type, so the agent-specific config is produced by +# render.sh and reused verbatim - this script only adds the profile envelope and +# converts to a plist. The envelope is agent-agnostic; --payload-type selects the +# app (default: Claude Code). +# +# render.sh --agent claude --api-key K --api-secret S --namespace NS -o - \ +# | render-plist.sh --identifier com.acme.ai-governance.claudecode \ +# --organization "Acme Corp" -o profile.mobileconfig +# +# Prerequisites: jq and plutil (plutil is native on macOS, where profiles are +# made). UUIDs default to freshly generated. +# +# --identifier required; reverse-DNS base. Inner payload is .settings +# --organization required; profile PayloadOrganization +# --payload-type app's managed-settings payload domain +# (default: com.anthropic.claudecode) +# --name profile + payload display name (default: Endor AI Governance) +# --profile-identifier outer PayloadIdentifier (default: --identifier) +# --profile-uuid outer PayloadUUID (default: uuidgen) +# --content-uuid inner PayloadUUID (default: uuidgen) +# -o / --output output path (default: stdout) +set -eu + +die() { echo "render-plist.sh: error: $*" >&2; exit 1; } +command -v jq >/dev/null || die "jq is required (install it, e.g. 'brew install jq')" +command -v plutil >/dev/null || die "plutil is required (macOS)" + +identifier=""; profile_identifier=""; organization="" +payload_type="com.anthropic.claudecode"; name="Endor AI Governance" +profile_uuid=""; content_uuid=""; output="-" +while [ $# -gt 0 ]; do + case "$1" in # every flag here takes a value; require one (avoids a raw set -u error) + -h|--help) ;; + -*) [ $# -ge 2 ] || die "$1 requires a value" ;; + esac + case "$1" in + --identifier) identifier="$2"; shift 2 ;; + --profile-identifier) profile_identifier="$2"; shift 2 ;; + --organization) organization="$2"; shift 2 ;; + --payload-type) payload_type="$2"; shift 2 ;; + --name) name="$2"; shift 2 ;; + --profile-uuid) profile_uuid="$2"; shift 2 ;; + --content-uuid) content_uuid="$2"; shift 2 ;; + -o|--output) output="$2"; shift 2 ;; + -h|--help) sed -n '2,24p' "$0"; exit 0 ;; + *) die "unknown argument: $1" ;; + esac +done + +[ -n "$identifier" ] || die "--identifier is required" +[ -n "$organization" ] || die "--organization is required" +[ -n "$profile_identifier" ] || profile_identifier="$identifier" +[ -n "$profile_uuid" ] || profile_uuid=$(uuidgen) +[ -n "$content_uuid" ] || content_uuid=$(uuidgen) + +# Read the managed-settings object produced by render.sh and embed it whole. +settings=$(cat) +printf '%s' "$settings" | jq -e 'type == "object"' >/dev/null 2>&1 \ + || die "stdin is not a JSON settings object (pipe: render.sh --agent ... -o -)" + +jq -n --argjson s "$settings" \ + --arg ident "$identifier" --arg pident "$profile_identifier" \ + --arg name "$name" --arg org "$organization" --arg ptype "$payload_type" \ + --arg puuid "$profile_uuid" --arg cuuid "$content_uuid" ' + { + PayloadContent: [({ + PayloadDescription: "Managed settings (env + hooks) for Endor Labs AI governance auditing.", + PayloadDisplayName: $name, + PayloadEnabled: true, + PayloadIdentifier: ($ident + ".settings"), + PayloadType: $ptype, + PayloadUUID: $cuuid, + PayloadVersion: 1 + } + $s)], + PayloadDescription: "Deploys managed AI-tool settings for Endor Labs AI auditing.", + PayloadDisplayName: $name, + PayloadIdentifier: $pident, + PayloadOrganization: $org, + PayloadScope: "System", + PayloadType: "Configuration", + PayloadUUID: $puuid, + PayloadVersion: 1 + }' | plutil -convert xml1 -o "$output" - + +[ "$output" = "-" ] || echo "render-plist.sh: wrote $output" >&2 diff --git a/agent-governance/scripts/render.sh b/agent-governance/scripts/render.sh new file mode 100755 index 0000000..6b9f5ae --- /dev/null +++ b/agent-governance/scripts/render.sh @@ -0,0 +1,249 @@ +#!/bin/sh +# render.sh - generate Endor AI-governance hook config (JSON) for an AI coding agent. +# +# Builds the config with jq, which escapes JSON correctly by construction. The +# Claude MDM profile (.mobileconfig) is produced by piping this script's Claude +# output through render-plist.sh. +# +# Outputs (JSON): --agent {claude,cursor}. The hook command strings are emitted +# for --target-os: macos/linux share a POSIX shell form; windows inlines the +# PowerShell bootstrap + audit, invoked as a self-contained, shell-agnostic +# powershell -NoProfile -EncodedCommand +# so it runs whether the agent launches hooks via Git Bash, PowerShell, or cmd. +# (The readable source is download_endorctl.ps1; the config carries its encoding.) +# +# Prerequisites: jq; for --target-os windows also iconv + base64 (all standard +# on the macOS/Linux machine that generates the config). +# +# Credentials (dedicated flags) resolve flag > env var > prompt. The prompt fires +# only when stdin is a TTY, so unattended callers (the runner) never block. +# --api-key ENDOR_API_CREDENTIALS_KEY +# --api-secret ENDOR_API_CREDENTIALS_SECRET +# --namespace ENDOR_NAMESPACE +# --api-url ENDOR_API (default: https://api.endorlabs.com) +# +# Behavior settings go through --env KEY=VALUE (repeatable), routed to Claude's +# env block / Cursor's sessionStart. Cache is on by default; monitor-only is just +# --env ENDOR_AI_AUDIT_NO_BLOCKING=true. --skip-endorctl-update uses an installed +# endorctl as-is (no per-session version check), installing only when missing. +# +# Example: +# render.sh --agent cursor --api-key K --api-secret S --namespace NS -o hooks.json +# render.sh --agent claude --target-os windows --api-key K --api-secret S --namespace NS +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +DEFAULT_API_URL="https://api.endorlabs.com" + +die() { echo "render.sh: error: $*" >&2; exit 1; } +command -v jq >/dev/null || die "jq is required (install it, e.g. 'brew install jq')" + +# Quote a value for safe interpolation into a command string. Values (creds, +# --env) may contain spaces, quotes, $, ;, globs - so never inline them raw. +sq() { printf '%s' "$1" | jq -Rrs '@sh'; } # POSIX single-quoted +psq() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/''/g")"; } # PowerShell single-quoted + +# Behavior env vars, newline-separated KEY=VALUE. Cache is on by default; +# add_env replaces any existing entry for the same key (later flag wins). +env_lines="ENDOR_AI_AUDIT_CACHE_ENABLED=true" +add_env() { + case "$1" in *=*) ;; *) die "--env must be KEY=VALUE (got: $1)" ;; esac + _key=${1%%=*} + case "$_key" in + ''|[0-9]*|*[!A-Za-z0-9_]*) die "--env key must match [A-Za-z_][A-Za-z0-9_]* (got: $_key)" ;; + esac + env_lines=$(printf '%s\n' "$env_lines" | grep -v "^${_key}=" || true) + env_lines="${env_lines} +${1}" +} + +# --- defaults / arg parsing --------------------------------------------------- +agent=""; output=""; skip_update=""; target_os="macos" +api_url="${ENDOR_API:-$DEFAULT_API_URL}" +api_key="${ENDOR_API_CREDENTIALS_KEY:-}" +api_secret="${ENDOR_API_CREDENTIALS_SECRET:-}" +namespace="${ENDOR_NAMESPACE:-}" + +while [ $# -gt 0 ]; do + case "$1" in # flags that take a value must have one (avoids a raw set -u error) + --agent|--target-os|-o|--output|--env|--api-url|--api-key|--api-secret|--namespace) + [ $# -ge 2 ] || die "$1 requires a value" ;; + esac + case "$1" in + --agent) agent="$2"; shift 2 ;; + --target-os) target_os="$2"; shift 2 ;; + -o|--output) output="$2"; shift 2 ;; + --env) add_env "$2"; shift 2 ;; + --skip-endorctl-update) skip_update=1; shift ;; + --api-url) api_url="$2"; shift 2 ;; + --api-key) api_key="$2"; shift 2 ;; + --api-secret) api_secret="$2"; shift 2 ;; + --namespace) namespace="$2"; shift 2 ;; + -h|--help) sed -n '2,32p' "$0"; exit 0 ;; + *) die "unknown argument: $1" ;; + esac +done + +# --- validation --------------------------------------------------------------- +case "$agent" in + claude|cursor) ;; + "") die "--agent is required (claude|cursor)" ;; + *) die "unknown agent: $agent (claude|cursor)" ;; +esac +case "$target_os" in + macos|linux|windows) ;; + *) die "unknown --target-os: $target_os (macos|linux|windows)" ;; +esac +if [ "$target_os" = windows ]; then + command -v iconv >/dev/null || die "iconv is required for --target-os windows" + command -v base64 >/dev/null || die "base64 is required for --target-os windows" + boot=$(cat "$SCRIPT_DIR/download_endorctl.ps1") +else + boot=$(cat "$SCRIPT_DIR/download_endorctl.sh") +fi + +# Prompt only when interactive; unattended runs must supply creds up front. +prompt_if_tty() { # prompt_if_tty VAR_NAME LABEL hide-input? + eval "_cur=\${$1}" + [ -n "$_cur" ] && return 0 + [ -t 0 ] || die "$2 not provided (pass --$2 or set its env var; no TTY to prompt)" + printf '%s: ' "$2" >&2 + [ "$3" = secret ] && { stty -echo 2>/dev/null || true; } + IFS= read -r _val + [ "$3" = secret ] && { stty echo 2>/dev/null || true; echo >&2; } + eval "$1=\$_val" +} +prompt_if_tty api_key "api-key" secret +prompt_if_tty api_secret "api-secret" secret +prompt_if_tty namespace "namespace" plain + +# --- behavior envs ------------------------------------------------------------ +# env_obj: JSON object for Claude's env block. +# env_prefix: "K=V " inline prefix for the POSIX Cursor sessionStart. +# ps_env_sets: "$env:K = 'V'" lines for the PowerShell Cursor sessionStart. +env_obj=$(printf '%s\n' "$env_lines" | jq -Rn \ + '[inputs | select(length > 0) | {key: .[:index("=")], value: .[index("=")+1:]}] | from_entries') +env_prefix=$(printf '%s' "$env_obj" | jq -r 'to_entries | map(.key + "=" + (.value|@sh) + " ") | add // ""') +ps_env_sets=$(printf '%s\n' "$env_lines" | while IFS= read -r kv; do + [ -n "$kv" ] || continue + printf '$env:%s = %s\n' "${kv%%=*}" "$(psq "${kv#*=}")" +done) + +# Fold the skip toggle into the bootstrap (read at the top of download_endorctl). +if [ -n "$skip_update" ]; then + if [ "$target_os" = windows ]; then + boot=$(printf "\$env:ENDORCTL_SKIP_UPDATE = '1'\n%s" "$boot") + else + boot=$(printf 'ENDORCTL_SKIP_UPDATE=1\n%s' "$boot") + fi +fi + +# Encode PowerShell source into a shell-agnostic invocation (base64 is plain +# alphanumeric, so it survives Git Bash, PowerShell, and cmd identically). +psenc() { + _b64=$(printf '%s' "$1" | iconv -f UTF-8 -t UTF-16LE | base64 | tr -d '\n') + printf 'powershell -NoProfile -EncodedCommand %s' "$_b64" +} + +# The audit invocation each hook runs (literal $VARS expand at hook time; Cursor +# non-session hooks read AGENT_HOOK_ENDOR_* that endorctl sets at sessionStart). +# endorctl's audit subcommand per agent (Claude Code is "claudecode", not "claude"). +case "$agent" in claude) subcmd="claudecode" ;; cursor) subcmd="cursor" ;; esac +posix_audit='"$HOME/.endorctl/endorctl" --api "$AGENT_HOOK_ENDOR_API" --namespace "$AGENT_HOOK_ENDOR_NAMESPACE" --api-key "$AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY" --api-secret "$AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET" ai-audit '"$subcmd" +ps_bin='& "$env:USERPROFILE\.endorctl\endorctl.exe"' +ps_audit="$ps_bin"' --api "$env:AGENT_HOOK_ENDOR_API" --namespace "$env:AGENT_HOOK_ENDOR_NAMESPACE" --api-key "$env:AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY" --api-secret "$env:AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET" ai-audit '"$subcmd"'; exit $LASTEXITCODE' +# A native command under `powershell -EncodedCommand` does not inherit the agent's +# event pipe (POSIX does), so per-event Windows hooks read stdin and pipe it in - else +# endorctl gets an empty event and silently enforces nothing. IsInputRedirected guards +# against a blocking read if a hook is invoked without a pipe. +ps_audit_event=$(printf '$ProgressPreference = "SilentlyContinue"\n$OutputEncoding = [System.Text.UTF8Encoding]::new($false)\n$in = if ([Console]::IsInputRedirected) { [System.IO.StreamReader]::new([Console]::OpenStandardInput(), $OutputEncoding).ReadToEnd() } else { "" }\n$in | %s' "$ps_audit") + +# --- compose per-hook command strings (session = bootstrap + audit) ----------- +case "$agent:$target_os" in + claude:macos|claude:linux) + cmd_audit="$posix_audit" + cmd_session=$(printf '%s\n%s' "$boot" "$posix_audit") ;; + cursor:macos|cursor:linux) + cmd_audit="$posix_audit" + # Capture stdin first (Cursor closes its pipe quickly) into a per-user file, + # clean up via trap, and let the audit be the last command so its exit code + # (e.g. a block) is the hook's. `umask 077` makes the file born 0600 (no + # readable window); `chmod 600` also covers a recycled-PID leftover. The name + # is predictable but lives in $HOME, which other users cannot write, so there's + # no cross-user symlink race. Only builtins precede `cat` - no subshell, which + # Cursor's stdin pipe does not tolerate, so $(mktemp) is intentionally avoided. + cmd_session=$(printf 'umask 077\nT="$HOME/.endorctl-cursor-stdin.$$"\ncat > "$T"\nchmod 600 "$T"\ntrap '\''rm -f "$T"'\'' EXIT\n%s\n%s"$HOME/.endorctl/endorctl" --api %s --namespace %s --api-key %s --api-secret %s ai-audit cursor < "$T"' \ + "$boot" "$env_prefix" "$(sq "$api_url")" "$(sq "$namespace")" "$(sq "$api_key")" "$(sq "$api_secret")") ;; + claude:windows) + cmd_audit=$(psenc "$ps_audit_event") + cmd_session=$(psenc "$(printf '%s\n%s' "$boot" "$ps_audit")") ;; + cursor:windows) + cmd_audit=$(psenc "$ps_audit_event") + # Windows PowerShell 5.1 is not UTF-8 by default; force it and read stdin as raw + # bytes so endorctl's JSON payload isn't mangled (avoid [Console]::InputEncoding, + # whose setter throws on a piped handle). + cmd_session=$(psenc "$(printf '$OutputEncoding = [System.Text.UTF8Encoding]::new($false)\n$in = [System.IO.StreamReader]::new([Console]::OpenStandardInput(), $OutputEncoding).ReadToEnd()\n%s\n%s\n$in | %s --api %s --namespace %s --api-key %s --api-secret %s ai-audit cursor; exit $LASTEXITCODE' \ + "$boot" "$ps_env_sets" "$ps_bin" "$(psq "$api_url")" "$(psq "$namespace")" "$(psq "$api_key")" "$(psq "$api_secret")")") ;; +esac + +# --- build (jq is pure structure; commands are injected) ---------------------- +build_cursor() { + jq -n --arg session "$cmd_session" --arg audit "$cmd_audit" ' + def hook($c): {command: $c}; + { + version: 1, + hooks: { + sessionStart: [hook($session)], + sessionEnd: [hook($audit)], + beforeSubmitPrompt: [hook($audit)], + preToolUse: [hook($audit)], + postToolUse: [hook($audit)], + postToolUseFailure: [hook($audit)], + beforeShellExecution: [hook($audit)], + afterShellExecution: [hook($audit)], + beforeMCPExecution: [hook($audit)], + beforeReadFile: [hook($audit)], + afterFileEdit: [hook($audit)], + stop: [hook($audit)] + } + }' +} + +build_claude() { + jq -n --arg session "$cmd_session" --arg audit "$cmd_audit" --argjson envobj "$env_obj" \ + --arg url "$api_url" --arg key "$api_key" --arg secret "$api_secret" --arg ns "$namespace" ' + def hook($c): {hooks: [{type: "command", command: $c}]}; + def mhook($c): hook($c) + {matcher: ".*"}; + # Hook-scoped AGENT_HOOK_ENDOR_* names keep audit creds out of any endorctl + # the agent itself spawns; behavior envs use their canonical names. + { + env: ({ + AGENT_HOOK_ENDOR_API: $url, + AGENT_HOOK_ENDOR_API_CREDENTIALS_KEY: $key, + AGENT_HOOK_ENDOR_API_CREDENTIALS_SECRET: $secret, + AGENT_HOOK_ENDOR_NAMESPACE: $ns + } + $envobj), + hooks: { + SessionStart: [hook($session)], + UserPromptSubmit: [hook($audit)], + PreToolUse: [mhook($audit)], + PostToolUse: [mhook($audit)], + PostToolUseFailure: [mhook($audit)], + Stop: [hook($audit)] + } + }' +} + +# --- emit --------------------------------------------------------------------- +emit() { case "$agent" in claude) build_claude ;; cursor) build_cursor ;; esac; } +case "$agent" in claude) def_out="claude-settings.json" ;; cursor) def_out="cursor-hooks.json" ;; esac +out_path="${output:-$def_out}" + +if [ "$out_path" = "-" ]; then + emit +else + out_dir=$(dirname -- "$out_path") + [ -d "$out_dir" ] || die "output directory does not exist: $out_dir (run: mkdir -p \"$out_dir\")" + emit > "$out_path" + echo "render.sh: wrote $out_path (agent=$agent, target-os=$target_os)" >&2 +fi diff --git a/agent-governance/scripts/runner.sh b/agent-governance/scripts/runner.sh new file mode 100755 index 0000000..d699a13 --- /dev/null +++ b/agent-governance/scripts/runner.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# runner.sh - paste this into your MDM as the script body (Jamf script, Kandji +# Custom Script, JumpCloud Command) to keep an agent's hook config current on +# macOS/Linux. On each run it fetches this repo at a pinned revision, renders the +# config, and places it - atomically, and only when the result changed. Run it on +# a recurring schedule and repo, credential, and flag changes all take effect. +# +# The MDM must put credentials in the environment before this runs: +# ENDOR_API_CREDENTIALS_KEY ENDOR_API_CREDENTIALS_SECRET ENDOR_NAMESPACE +# Jamf: prepend export ENDOR_API_CREDENTIALS_KEY="$4" ENDOR_API_CREDENTIALS_SECRET="$5" ENDOR_NAMESPACE="$6" +# Kandji / JumpCloud: prepend export ENDOR_API_CREDENTIALS_KEY='...' ... (single-quoted) +# Use an audit-only / least-privilege credential. +# +# Endpoint needs git + jq. Windows doesn't use this - pre-generate and push via +# Intune (see docs/deploy-windows-intune.md). +set -eu + +# --- settings: edit these --------------------------------------------------- +AGENT=cursor # cursor | claude +REF=main # pin to a reviewed tag/commit, e.g. v1.0.0 +EXTRA= # extra render flags, e.g. --env ENDOR_AI_AUDIT_NO_BLOCKING=true +DEST= # override install path; empty = OS default +REPO_URL=https://github.com/endorlabs/mdm-scripts +# ----------------------------------------------------------------------------- + +os=$(uname -s) +case "$os" in + Darwin) REPO="/Library/Application Support/EndorAIGovernance/repo" ;; + *) REPO="/var/lib/endor-ai-governance/repo" ;; +esac + +# Default install path per (agent, OS). macOS Claude is profile-delivered (see +# docs/deploy-claude-profile.md), so set DEST yourself if you really want a file. +if [ -z "$DEST" ]; then + case "$AGENT:$os" in + cursor:Darwin) DEST="/Library/Application Support/Cursor/hooks.json" ;; + cursor:Linux) DEST="/etc/cursor/hooks.json" ;; + claude:Linux) DEST="/etc/claude-code/managed-settings.json" ;; + *) echo "runner.sh: set DEST for agent '$AGENT' on $os" >&2; exit 2 ;; + esac +fi + +mkdir -p "$(dirname "$REPO")" "$(dirname "$DEST")" + +# Fetch this repo at the pinned ref - one clone, into a root-owned path (never +# /tmp). Checking out an explicit ref means the device runs a reviewed revision. +if [ ! -d "$REPO/.git" ]; then + git init -q "$REPO" + git -C "$REPO" remote add origin "$REPO_URL" +fi +git -C "$REPO" fetch --depth 1 origin "$REF" +git -C "$REPO" -c advice.detachedHead=false checkout -f FETCH_HEAD + +# Render the config and swap it in only if it changed. Comparing the output (not +# the repo revision) means credential and flag changes take effect too. +sh "$REPO/agent-governance/scripts/render.sh" --agent "$AGENT" $EXTRA -o "$DEST.tmp" +if [ -f "$DEST" ] && cmp -s "$DEST.tmp" "$DEST"; then + rm -f "$DEST.tmp" +else + mv "$DEST.tmp" "$DEST" +fi