GuardEntry ร OpenClaw
Protect your OpenClaw agents with GuardEntry compliance policies. Every tool call is evaluated before it executes โ blocking violations, flagging drift, and building an audit trail automatically.
Server / Docker setupNo Node.js required
Running OpenClaw on a Linux server or inside a Docker container? Use the standalone bash hook โ just curl and bash, no npm needed.
1. Download the hook script
curl -fsSL https://app.guardentry.ai/fastgrc-hook.sh -o /usr/local/bin/guardentry-hook && chmod +x /usr/local/bin/guardentry-hookThis places the hook at /usr/local/bin/guardentry-hook โ on the system PATH and ready to reference in the next step.
Can't reach the URL? Create the file manually โบ
Copy the script below, paste it into a new file named guardentry-hook, then chmod +x guardentry-hook.
#!/usr/bin/env bash
# guardentry-hook โ GuardEntry PreToolUse compliance hook (no Node.js required)
set -uo pipefail
API_KEY="${GUARDENTRY_API_KEY:-}"
BASE_URL="${GUARDENTRY_BASE_URL:-https://app.guardentry.ai}"
POLICY_ID="${GUARDENTRY_POLICY_ID:-}"
[[ -z "$API_KEY" ]] && exit 0
INPUT="$(cat 2>/dev/null || true)"
[[ -z "$INPUT" ]] && exit 0
_json_str() {
local expr="$1" input="$2"
if command -v jq &>/dev/null; then
printf '%s' "$input" | jq -r "$expr // empty" 2>/dev/null || true
elif command -v python3 &>/dev/null; then
printf '%s' "$input" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
print($expr, end='')
except Exception:
pass
" 2>/dev/null || true
fi
}
TOOL_NAME="$(_json_str '.tool_name' "$INPUT")"
AGENT_ID="$(_json_str '.session_id // .agent_id // ""' "$INPUT")"
if command -v jq &>/dev/null; then
TOOL_INPUT="$(printf '%s' "$INPUT" | jq -c '.tool_input // {}' 2>/dev/null || echo '{}')"
elif command -v python3 &>/dev/null; then
TOOL_INPUT="$(printf '%s' "$INPUT" | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
print(json.dumps(d.get('tool_input', {})), end='')
except Exception:
print('{}', end='')
" 2>/dev/null || echo '{}')"
else
exit 0
fi
TOOL_NAME="${TOOL_NAME:-unknown}"
TOOL_INPUT="${TOOL_INPUT:-{}}"
CONTENT_ESC="$(printf '%s' "tool_name: ${TOOL_NAME}\nargs: ${TOOL_INPUT}" | sed 's/\\/\\\\/g; s/"/\\"/g')"
BODY="{\"subjectContent\":\"$CONTENT_ESC\",\"subjectType\":\"tool_argument\",\"direction\":\"ingress\",\"agentId\":\"$AGENT_ID\""
[[ -n "$POLICY_ID" ]] && BODY+=",\"policyId\":\"$POLICY_ID\""
BODY+="}"
RESPONSE="$(curl -sf --max-time 5 \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "$BODY" \
"${BASE_URL}/api/v1/policy-router/evaluate" 2>/dev/null)" || true
[[ -z "$RESPONSE" ]] && exit 0
if command -v jq &>/dev/null; then
DECISION="$(printf '%s' "$RESPONSE" | jq -r '.decision // "allow"' 2>/dev/null || echo 'allow')"
REASONING="$(printf '%s' "$RESPONSE" | jq -r '.reasoning // ""' 2>/dev/null || true)"
MATCHED="$(printf '%s' "$RESPONSE" | jq -r '.policyContext.matchedRule // ""' 2>/dev/null || true)"
OVERRIDE_ACTIVE="$(printf '%s' "$RESPONSE" | jq -r '(.reasonTags // []) | contains(["override_block_active"])' 2>/dev/null || echo 'false')"
elif command -v python3 &>/dev/null; then
DECISION="$(printf '%s' "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('decision','allow'),end='')" 2>/dev/null || echo 'allow')"
REASONING="$(printf '%s' "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('reasoning',''),end='')" 2>/dev/null || true)"
MATCHED="$(printf '%s' "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print((d.get('policyContext') or {}).get('matchedRule',''),end='')" 2>/dev/null || true)"
OVERRIDE_ACTIVE="$(printf '%s' "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); tags=d.get('reasonTags') or []; print('true' if 'override_block_active' in tags else 'false',end='')" 2>/dev/null || echo 'false')"
else
exit 0
fi
if [[ "${DECISION:-allow}" == "block" || "${DECISION:-allow}" == "require_approval" ]]; then
[[ -n "${MATCHED:-}" ]] && printf 'GuardEntry blocked: [%s] %s\n' "$MATCHED" "${REASONING:-action blocked}" >&2 || printf 'GuardEntry blocked: %s\n' "${REASONING:-action blocked}" >&2
exit 2
fi
# Observability mode: block was downgraded to allow โ surface the diagnostic
if [[ "${OVERRIDE_ACTIVE:-false}" == "true" ]]; then
[[ -n "${MATCHED:-}" ]] && printf '[GuardEntry] Observability mode โ would have blocked: [%s] %s\n' "$MATCHED" "${REASONING:-}" >&2 || [[ -n "${REASONING:-}" ]] && printf '[GuardEntry] Observability mode โ would have blocked: %s\n' "$REASONING" >&2
fi
exit 01b. Webchat exec approvals โ socket server
The bash hook covers tool calls from Claude Code and OpenClaw's hook runner. To also block webchat exec commands โ shell commands run directly from the OpenClaw web chat โ you need the exec-approvals socket server.
OpenClaw's plugin installer has an ownership conflict in Docker (installs as uid=1000 but requires uid=0 to load). Use guardentry-hook serve-approvals as a standalone process on the host instead.
Step 1 โ Configure exec-approvals inside the container (run once):
docker exec <container> guardentry-hook setup \
--api-key fgrc_k1_your_key_here \
--policy-id fgrc_p1_your_policy_idStep 2 โ Create a systemd service on the host:
# /etc/systemd/system/guardentry-approvals.service
[Unit]
Description=GuardEntry Exec Approvals Server
After=docker.service
Requires=docker.service
[Service]
Type=simple
Restart=always
RestartSec=15
ExecStartPre=/bin/bash -c 'until docker exec <container> echo ok 2>/dev/null; do sleep 2; done'
ExecStart=/usr/bin/docker exec <container> guardentry-hook serve-approvals
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload && systemctl enable --now guardentry-approvalsThe service polls until the container is ready, then starts the socket server. It auto-recovers after docker restart. Verify: docker exec <container> ls /data/.openclaw/exec-approvals.sock โ should show srw------- 1 root root.
2a. Configure via Claude Code (~/.claude/settings.json)
If your agent runs inside Claude Code, embed your API key directly in the command string. This ensures the hook works regardless of how Claude Code was started (terminal, service, GUI):
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "GUARDENTRY_API_KEY=fgrc_k1_your_key_here /usr/local/bin/guardentry-hook"
}
]
}
]
}
}Get your API key free at guardentry.ai/connect. Optional: append GUARDENTRY_POLICY_ID=your_id before the path to target a specific policy.
Restart Claude Code after saving. The hook is fail-open โ if the API is unreachable or times out, the tool call proceeds normally.
2b. Configure via OpenClaw HOOK.md (v2026.3.28+)
If your agent runs under OpenClaw's own hook runner, embed the key in the handler field of your HOOK.md. OpenClaw spawns hook handlers as subprocesses โ they do not inherit your shell's exported variables, so the key must be inlined:
---
name: GuardEntry Policy Check
description: Evaluate every tool call against your GuardEntry compliance policy
hooks:
- matcher: PreToolUse
handler: "GUARDENTRY_API_KEY=fgrc_k1_your_key_here guardentry-hook"
---If your OpenClaw version doesn't support inline env vars in the handler string, use a wrapper script instead:
#!/usr/bin/env bash
# guardentry-wrapper.sh โ place alongside your HOOK.md
GUARDENTRY_API_KEY=fgrc_k1_your_key_here exec guardentry-hook "$@"handler: "./guardentry-wrapper.sh"3. Test it
Run this to confirm the hook fires and reaches the API. Replace the key with yours:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp"},"session_id":"test-123"}' \
| GUARDENTRY_API_KEY=fgrc_k1_your_key_here /usr/local/bin/guardentry-hook
echo "Exit: $?"
# Exit 0 = allowed | Exit 2 = blockedExit 2 = working. Check your dashboard audit tab for the logged decision. If exit is always 0 with no audit entry, the key is wrong or the hook isn't installed at /usr/local/bin/guardentry-hook.
Hook connects but nothing is blocked? Your policy starts in Observability Mode โ violations are logged but not blocked. Enable enforcement in the dashboard when ready.
OpenClaw HOOK.md
The recommended OpenClaw integration uses OpenClaw's native hook runner. Add guardentry-hook as a handler in your HOOK.md โ OpenClaw spawns it as a subprocess before every tool call.
1. Install the binary
npm install -g guardentry-openclawThis puts guardentry-hook on your system PATH.
2. Save your API key and policy (one-time)
guardentry-hook set-key fgrc_k1_your_key_here
guardentry-hook set-policy fgrc_p1_your_policy_idBoth are saved to ~/.guardentry.json โ the binary reads them automatically. You get both values from the /connect success screen.
Verify: guardentry-hook get-key ยท guardentry-hook get-policy ยท To switch policy later: guardentry-hook set-policy <new-id> (find IDs in the dashboard). Policy ID is optional โ omit to use your org-wide default.
3. Auto-configure HOOK.md
From your OpenClaw project directory:
guardentry-hook install-hookCreates or updates HOOK.md with the correct handler entry โ idempotent, safe to re-run. Pass a path if your project is elsewhere: guardentry-hook install-hook /path/to/project
Restart OpenClaw โ the hook fires for every tool call from that point on.
4. Verify
Run a query in OpenClaw chat, then check your audit log โ every tool call should appear within seconds.
Nothing showing? Confirm guardentry-hook get-key returns your key and that guardentry-hook is on PATH (which guardentry-hook).
Policy options at install time
When you sign up at /connect, you choose one of three default policies โ all start in Observability Mode (log only, nothing blocked):
| Policy | Best for |
|---|---|
| General Observer | Any agent โ just start logging to understand behavior |
| Compliance Drift Monitor | SOC 2 / ISO 27001 โ detect policy drift as agents evolve |
| SOC 2 Auditor | Audit-ready agents โ strict read-only with evidence collection |
Switch to enforcement from your dashboard when ready.
What gets evaluated
Every tool call sends:
{
"subjectContent": "tool_name: delete_record\nargs: {\"table\":\"users\",\"id\":\"123\"}",
"subjectType": "tool_argument",
"direction": "ingress",
"agentId": "<session id>"
}The GuardEntry policy router checks this against your configured rules and returns a decision in ~20โ50ms. Exit 0 = allow. Exit 2 = block (reason written to stderr, shown in OpenClaw chat).
Advanced: TypeScript API
For custom agent wrappers or environments where you can't use HOOK.md, guardentry-openclaw also exports a programmatic API:
npm install guardentry-openclawimport { evaluate, GuardEntryBlockedError } from 'guardentry-openclaw';
// Evaluate a tool call manually (e.g. inside your own hook middleware)
const result = await evaluate({
toolName: 'delete_record',
args: { table: 'users', id: '123' },
apiKey: 'fgrc_k1_your_key_here', // or omit to use ~/.guardentry.json
});
if (result?.decision === 'block') {
throw new GuardEntryBlockedError(
result.policyContext?.matchedRule ?? 'policy rule',
result.reasoning
);
}The evaluate() function is the same one used by the hook binary โ fail-open on timeout or network error.
Safety guarantees
- โFail-open: If GuardEntry is unreachable or times out, the tool call is allowed through with a
console.warn. GuardEntry never breaks your agent due to infra issues. - โNo data stored: Tool arguments are evaluated in-memory. Only the decision record (tool name, decision, reasoning) is stored in the audit log โ not the full argument payload.