55# Shrike Security — Agent Action Governance
66
77Govern what an OpenClaw agent is allowed to ** do** . This recipe installs a
8- Shrike PreToolUse hook into a NemoClaw/OpenShell sandbox so every matched tool
9- call is evaluated by Shrike's enforce plane before it runs, and denied when the
10- verdict is ` block ` or ` require_approval ` .
8+ Shrike ** ` before_tool_call ` plugin ** into a NemoClaw/OpenShell sandbox so every
9+ tool call is evaluated by Shrike's enforce plane before it runs, and blocked
10+ when the verdict is ` block ` or ` require_approval ` .
1111
1212OpenShell contains * where* an agent can act (network, filesystem, syscalls).
1313Shrike governs * what* a specific action is — a benign shell command is allowed;
1414a destructive command, a SQL injection, an injected instruction, or a secret
15- exfiltration attempt is denied — with the decision made server-side against
15+ exfiltration attempt is blocked — with the decision made server-side against
1616organizational policy and returned as ` allow ` / ` warn ` / ` require_approval ` /
1717` block ` . The two compose: the sandbox is the cage, Shrike is the judgment.
1818
19+ This is ** in-sandbox defense-in-depth** , not an independent security boundary.
20+ The plugin runs inside the sandbox alongside the agent; a fully compromised
21+ sandbox could tamper with it. The independent controls remain the OpenShell
22+ egress policy + credential provider (` providers/shrike.yaml ` ), which hold even
23+ if the in-sandbox plugin is subverted.
24+
1925## Intended users and support boundary
2026
2127For operators running OpenClaw agents inside NemoClaw who want an action-level
@@ -29,12 +35,12 @@ agreement.
2935
3036## Data-sharing boundary (read before enabling)
3137
32- When the hook evaluates an action, the ** action content** — the shell command,
33- SQL text, file-write body, web-search target, or message content of the matched
34- tool call — is transmitted over TLS to ` api.shrikesecurity.com ` for evaluation.
35- A decision and a short reason are returned; no other sandbox data is sent. If no
36- Shrike credential is configured, the hook cannot obtain a verdict and (by
37- default) denies the action fail-closed — nothing is transmitted without a key.
38+ When the plugin evaluates an action, the ** action content** — the shell command,
39+ SQL text, file-write body, web-search target, or message content of the tool
40+ call — is transmitted over TLS to ` api.shrikesecurity.com ` for evaluation. A
41+ decision and a short reason are returned; no other sandbox data is sent. If no
42+ Shrike credential is configured, the plugin cannot obtain a verdict and (by
43+ default) blocks the action fail-closed — nothing is transmitted without a key.
3844Retention, GDPR/CCPA, and data-deletion posture: < https://shrikesecurity.com/privacy > .
3945
4046Do not enable this recipe on a workload whose tool-call content must never leave
@@ -43,89 +49,151 @@ the sandbox.
4349## Provenance
4450
4551Contributed by ** Shrike Security, Inc.** Attribution is preserved in the SPDX
46- headers of every file. Shrike Security authored the hook , policy, and lifecycle
47- scripts; the recipe targets the public NemoClaw/OpenShell CLIs.
52+ headers of every file. Shrike Security authored the plugin , policy, and
53+ lifecycle scripts; the recipe targets the public NemoClaw/OpenShell CLIs.
4854
4955## Prerequisites and supported environments
5056
5157- NemoClaw installed with a working OpenShell gateway (` nemoclaw ` , ` openshell `
5258 on ` PATH ` ). Install: ` curl -fsSL https://www.nvidia.com/nemoclaw.sh | NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 bash `
5359- An inference provider (NVIDIA-hosted ` build ` endpoints, or any OpenAI-compatible endpoint).
5460- A Shrike API key (free tier available): < https://shrikesecurity.com/signup >
55- - ` bash ` , ` node ` , and ` curl ` available in the sandbox (present in the OpenClaw runtime).
56- - Verified on macOS (arm64) and Linux hosts with a CPU-only OpenShell gateway.
61+ - On the host: ` node ` + ` npm ` (to build the plugin) and ` curl ` .
62+ - Verified on macOS (arm64) and Linux hosts with a CPU-only OpenShell gateway,
63+ NemoClaw v0.0.97 / OpenClaw 2026.7.1.
5764
5865## Architecture and major components
5966
6067```
61- OpenClaw agent --(tool call)--> PreToolUse hook --(action content)--> Shrike enforce
62- | | |
63- | | allow/warn -> allow | server-side
64- | <---------- decision ------------+ block/require_approval -> deny | 9-layer policy
68+ OpenClaw agent --(tool call)--> before_tool_call plugin --(action content)--> Shrike enforce
69+ | | |
70+ | | allow/warn -> allow | server-side
71+ | <----------- decision ------------- + block/require_approval -> block | 9-layer policy
6572```
6673
67- - ` hooks/shrike-preaction-hook.mjs ` — classifies the action (sql / command /
68- file_write / web_search / general), calls the enforce plane, maps the verdict
69- to an OpenClaw permission decision. Fail-closed by default.
74+ - ` plugin/ ` — the OpenClaw ` before_tool_call ` plugin (TypeScript → ` dist/ ` ). It
75+ classifies each action (sql / command / file_write / web_search / general),
76+ calls the enforce plane, and maps the verdict to an OpenClaw decision
77+ (` allow ` /` warn ` → allow; ` block ` /` require_approval ` → block). Fail-closed by
78+ default. This is the supported OpenClaw interception path — the same contract
79+ NemoClaw's own secret-scanner uses — not a Claude-style PreToolUse hook.
7080- ` providers/shrike.yaml ` — OpenShell v2 provider profile: declares the
71- ` SHRIKE_API_KEY ` bearer credential and scopes egress to
72- ` api.shrikesecurity.com ` with ` enforcement: enforce ` . Imported at onboard time .
73- - ` agents.yaml ` — keeps the full toolset; governance is at the hook , not by
81+ ` SHRIKE_API_KEY ` bearer credential and scopes egress to the two enforce POST
82+ paths on ` api.shrikesecurity.com ` with ` enforcement: enforce ` .
83+ - ` agents.yaml ` — keeps the full toolset; governance is at the plugin , not by
7484 removing tools.
75- - ` scripts/ ` — ` onboard.sh ` , ` install.sh ` , ` verify.sh ` , ` status.sh ` , ` teardown.sh ` .
85+ - ` scripts/ ` — ` onboard.sh ` , ` install.sh ` , ` build-image.sh ` , ` verify.sh ` ,
86+ ` status.sh ` , ` teardown.sh ` .
87+
88+ ## Two install paths
89+
90+ The plugin can be delivered two ways. ** Image is the supported, reliable path;
91+ runtime is a best-effort local convenience.**
92+
93+ | | ** ` image ` (recommended)** | ** ` runtime ` (dev quick-try)** |
94+ | --- | --- | --- |
95+ | How | Baked into a version-matched custom sandbox image at onboard time (` nemoclaw onboard --from ` ) | Installed into the live sandbox (` openclaw plugins install ` ) |
96+ | Durable across ` rebuild ` | Yes | No |
97+ | Touches the managed config guard | No — config is sealed correctly at build time | Yes — see below |
98+ | Provenance / trusted-source check | Yes — NemoClaw records + re-validates plugin provenance on every rebuild (v0.0.76+) | No — see below |
99+ | Prerequisite | A matched NemoClaw source checkout (or GitHub access to clone the release tag) | None |
100+
101+ ### Why image is the reliable path (the config-integrity shield)
102+
103+ The managed runtime protects ` openclaw.json ` with an integrity shield
104+ (` .config-hash ` ). Enabling a plugin ** at runtime** rewrites ` openclaw.json `
105+ out-of-band, and a background normalizer keeps rewriting it, so the gateway
106+ refuses to restart (` GATEWAY_UNSAFE_CONFIG_PATH ` ) until the change is re-blessed
107+ — and re-blessing races the normalizer nondeterministically. There is no
108+ supported runtime plugin-install into a managed sandbox yet (tracked upstream in
109+ [ NemoClaw #5998 ] ( https://github.com/NVIDIA/NemoClaw/issues/5998 ) ).
110+
111+ The ** image path avoids this entirely** : the plugin is installed and the config
112+ is sealed * at trusted build time* , before the shield locks it, under NemoClaw's
113+ provenance guard. That guard is the real "this change is authentic" check — a
114+ property the runtime path cannot provide (a runtime re-bless proves * intent* ,
115+ not * authenticity* : ` .config-hash ` is a sha256, carrying no signature). As a
116+ security recipe, this example uses NemoClaw's supported path and does ** not**
117+ silently circumvent the integrity shield.
118+
119+ ## Setup and configuration
120+
121+ ``` bash
122+ cp .env.example .env # set SHRIKE_API_KEY + your inference provider vars
123+ ```
124+
125+ ### Recommended — image install (durable, provenance-guarded)
126+
127+ ``` bash
128+ # Point at a NemoClaw source checkout matching your installed CLI (or let
129+ # build-image.sh clone the matching release tag from GitHub).
130+ export INSTALL_MODE=image
131+ export NEMOCLAW_SOURCE_DIR=/path/to/NemoClaw # matched to `nemoclaw --version`
132+
133+ bash scripts/onboard.sh # import provider + create provider + BAKE plugin image + attach provider
134+ bash scripts/install.sh # verifies the baked plugin loaded (no-op install)
135+ bash scripts/verify.sh # allowed/denied live validation
136+ ```
137+
138+ ### Dev quick-try — runtime install (not durable; best-effort)
139+
140+ ``` bash
141+ bash scripts/onboard.sh # import + create provider + onboard + attach provider
142+ SHRIKE_RUNTIME_REBLESS=1 bash scripts/install.sh # install plugin into the live sandbox
143+ bash scripts/verify.sh
144+ ```
145+
146+ ` install.sh ` will not silently touch the managed config guard: without
147+ ` SHRIKE_RUNTIME_REBLESS=1 ` it fails loud and points you here. With the opt-in it
148+ re-blesses the integrity hash (unsigned, operator-asserted) and retries; because
149+ that races the managed normalizer it may not settle — if it doesn't, use the
150+ image path.
151+
152+ Overridable knobs (in ` .env ` ): ` INSTALL_MODE ` , ` NEMOCLAW_SANDBOX_NAME ` ,
153+ ` SHRIKE_PROFILE_ID ` , ` SHRIKE_PROVIDER_NAME ` , ` SHRIKE_FAIL_MODE ` (` closed `
154+ default = block on enforce error; ` open ` = allow), ` SHRIKE_VERIFY_TOOL ` ,
155+ ` NEMOCLAW_SOURCE_DIR ` .
76156
77157## Credential and secret handling
78158
79159` onboard.sh ` imports the v2 provider profile (` providers/shrike.yaml ` ) and
80- creates a provider instance with ` openshell provider create ` , supplying
81- ` SHRIKE_API_KEY ` to the ** gateway** once through a clean sub-environment. The
82- sandboxed agent and hook reference only the placeholder
160+ creates a provider with ` openshell provider create ` , supplying ` SHRIKE_API_KEY `
161+ to the ** gateway** once through a clean sub-environment, then ** attaches ** that
162+ provider to the sandbox. The plugin references only the placeholder
83163` openshell:resolve:env:SHRIKE_API_KEY ` ; the OpenShell L7 proxy substitutes the
84164real value on egress to ` api.shrikesecurity.com ` . The raw key is never written
85165into the sandbox environment, filesystem, or agent context. ` .env ` holds the key
86166on the host only (for the one ` provider create ` call) and is git-ignored.
87167
88- ## Setup and configuration
89-
90- ``` bash
91- cp .env.example .env # set SHRIKE_API_KEY + your inference provider vars
92- bash scripts/onboard.sh # import provider profile + create provider (key held gateway-side) + onboard sandbox
93- bash scripts/install.sh # install the PreToolUse hook into the sandbox
94- ```
95-
96- Overridable knobs (in ` .env ` ): ` NEMOCLAW_SANDBOX_NAME ` , ` SHRIKE_PROFILE_ID ` ,
97- ` SHRIKE_PROVIDER_NAME ` , ` SHRIKE_FAIL_MODE ` (` closed ` default = deny on enforce
98- error; ` open ` = allow).
99-
100168## Sandbox, network, and policy permissions
101169
102- The provider profile ( ` providers/shrike.yaml ` ) scopes egress to
103- ` api.shrikesecurity.com:443 ` with ` enforcement: enforce ` and permits ` curl ` +
104- ` node ` for the hook. The Shrike enforce API itself accepts only the
105- ` /agent/api/scan/enforce[/specialized] ` , ` /agent/api/session/status ` , and
106- ` /health ` paths . No other network destination is reachable from the sandbox.
170+ ` providers/shrike.yaml ` scopes egress to ` api.shrikesecurity.com:443 ` with
171+ ` enforcement: enforce ` , and restricts it to exactly two POST paths
172+ ( ` /agent/api/scan/enforce ` and ` /agent/api/scan/ enforce/specialized ` ) — no other
173+ method or path on the host is reachable — and permits only ` curl ` for the egress
174+ call . No other network destination is reachable from the sandbox.
107175
108176## Startup behavior
109177
110- Governance is active as soon as ` install.sh ` registers the PreToolUse hook —
111- there is no long-running service to start. Every matched tool call is evaluated
112- inline before it executes.
178+ Governance is active once the plugin is loaded (after the image onboard, or
179+ after a successful runtime install + gateway restart). There is no long-running
180+ service to start; every tool call is evaluated inline before it executes.
113181
114182## Verification steps and expected results
115183
116184``` bash
117185bash scripts/verify.sh
118186```
119187
120- Drives the installed hook with representative payloads and asserts:
188+ ` verify.sh ` drives ** real tool calls through the sandbox gateway**
189+ (` /tools/invoke ` ); the ` before_tool_call ` plugin fires on each before the tool
190+ runs. A benign call must pass the plugin (not blocked); a malicious call must be
191+ blocked (` tool_call_blocked ` ).
121192
122- | Action | Expected |
193+ | Tool call ( ` SHRIKE_VERIFY_TOOL ` , default ` web_search ` ) | Expected |
123194| --- | --- |
124- | Benign shell command (` ls -la ` ) | ** allow** |
125- | Destructive command (` rm -rf / ` ) | ** deny** |
126- | SQL injection (` ... OR 1=1; DROP TABLE ` ) | ** deny** |
127- | Prompt injection (ignore-instructions + exfil) | ** deny** |
128- | Secret exfiltration (key in URL) | ** deny** |
195+ | Benign technical query | ** allowed** (passes the plugin) |
196+ | Prompt injection (ignore-instructions + exfil) | ** blocked** |
129197
130198` scripts/verify.sh ` exits ` 0 ` only when every case matches. A full transcript of
131199a real run is in [ docs/verify-functionality.md] ( docs/verify-functionality.md ) .
@@ -134,25 +202,34 @@ Inspect wiring any time with `bash scripts/status.sh`.
134202## Teardown and cleanup
135203
136204``` bash
137- bash scripts/teardown.sh # remove hook + Shrike provider/profile + sandbox
205+ bash scripts/teardown.sh # disable/uninstall plugin + detach + remove provider/profile + sandbox
138206KEEP_SANDBOX=1 bash scripts/teardown.sh # only un-wire Shrike; keep the sandbox
139207```
140208
141- Teardown-safe: leaves no service or credential active.
209+ Teardown-safe: leaves no service or credential active. For an ` image ` install
210+ the plugin is baked into the image, so removing the sandbox (the default) is the
211+ clean teardown.
142212
143213## Known limitations
144214
145- - The hook governs ** matched tool calls** ; content the agent never routes
146- through a tool is out of scope (that is OpenShell's containment layer's job).
215+ - ** In-sandbox defense-in-depth, not an independent boundary** — a fully
216+ compromised sandbox could tamper with the plugin; the OpenShell egress policy
217+ + credential provider are the controls that hold regardless.
218+ - ** Runtime install is best-effort** — it races the managed config-integrity
219+ shield and is not durable across ` rebuild ` ; use ` image ` for anything real.
147220- Enforce adds a network round-trip per governed action (typically well under a
148221 second warm). ` SHRIKE_FAIL_MODE=open ` trades containment for availability if
149222 the enforce plane is unreachable.
150223- Action classification is heuristic by tool/field shape; unusual tool schemas
151224 fall back to the general enforce path.
225+ - ` verify.sh ` drives one tool (` SHRIKE_VERIFY_TOOL ` , default ` web_search ` ); set
226+ it to a tool your agent exposes.
152227
153228## Third-party dependencies and license obligations
154229
155- No new third-party libraries are added by this recipe. It uses the NemoClaw and
156- OpenShell CLIs, ` node ` , and ` curl ` , all already present in the runtime. The
157- recipe is licensed under Apache-2.0. The Shrike service it calls is operated by
158- Shrike Security, Inc. under its own terms (< https://shrikesecurity.com > ).
230+ The plugin builds with TypeScript (dev-only) and imports nothing from the
231+ ` openclaw ` runtime (structural types), so it ships no runtime third-party
232+ libraries. It uses the NemoClaw and OpenShell CLIs and ` curl ` , already present
233+ in the runtime. The recipe is licensed under Apache-2.0. The Shrike service it
234+ calls is operated by Shrike Security, Inc. under its own terms
235+ (< https://shrikesecurity.com > ).
0 commit comments