FastGRC × OpenClaw
Protect your OpenClaw agents with FastGRC 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.fastgrc.ai/fastgrc-hook.sh -o /usr/local/bin/fastgrc-hook && chmod +x /usr/local/bin/fastgrc-hookThis places the hook at /usr/local/bin/fastgrc-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 fastgrc-hook, then chmod +x fastgrc-hook.
#!/usr/bin/env bash
# fastgrc-hook — FastGRC PreToolUse compliance hook (no Node.js required)
set -uo pipefail
API_KEY="${FASTGRC_API_KEY:-}"
BASE_URL="${FASTGRC_BASE_URL:-https://app.fastgrc.ai}"
POLICY_ID="${FASTGRC_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)"
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)"
else
exit 0
fi
if [[ "${DECISION:-allow}" == "block" || "${DECISION:-allow}" == "require_approval" ]]; then
[[ -n "${MATCHED:-}" ]] && printf 'FastGRC blocked: [%s] %s\n' "$MATCHED" "${REASONING:-action blocked}" >&2 || printf 'FastGRC blocked: %s\n' "${REASONING:-action blocked}" >&2
exit 2
fi
exit 02. Set your API key
export FASTGRC_API_KEY=fgrc_k1_your_key_here
# Add to ~/.bashrc or your Docker env to persist across sessionsGet your API key free at fastgrc.ai/connect — no credit card required.
3. Configure the hook in .claude/settings.json
OpenClaw looks for this file in ~/.claude/settings.json or the project's .claude/settings.json. Use the full path to the script:
{
"hooks": {
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "command",
"command": "/usr/local/bin/fastgrc-hook"
}
]
}
]
}
}Optional env vars: FASTGRC_POLICY_ID (target a specific policy), FASTGRC_BASE_URL (override API host).
Restart OpenClaw after saving. The hook is fail-open — if the API is unreachable or times out, the tool call proceeds normally.
4. Test it
Run this in the same terminal where OpenClaw runs to confirm the hook fires and reaches the API:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp"},"session_id":"test-123"}' \
| /usr/local/bin/fastgrc-hook
echo "Exit: $?"
# Exit 0 = allowed | Exit 2 = blockedIf exit is always 0 and no logs appear in your dashboard, check that FASTGRC_API_KEY is exported in the environment where OpenClaw runs (echo $FASTGRC_API_KEY). The hook exits 0 silently when the key is missing.
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.
Node.js project setup
If your project already uses Node.js and you prefer a typed plugin API:
npm install fastgrc-openclawSetup (2 lines)
Add to your openclaw.config.ts file (not the terminal):
// File: openclaw.config.ts
import { FastGRCPlugin } from 'fastgrc-openclaw';
export default {
plugins: [
FastGRCPlugin({
apiKey: process.env.FASTGRC_API_KEY,
}),
],
};# .env
FASTGRC_API_KEY=fgrc_k1_your_key_hereGet your API key free at fastgrc.ai/connect — no credit card, unlimited agents.
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.
Full plugin options
FastGRCPlugin({
apiKey: string; // Required
policyId?: string; // Target a specific policy (omit for org-wide default)
onBlock?: 'throw' // Default: throw FastGRCBlockedError
| 'warn' // console.warn and allow through
| 'silent'; // Allow through silently
timeoutMs?: number; // Default: 3000ms. Fail-open on timeout.
baseUrl?: string; // Default: https://app.fastgrc.ai
})Error handling
import { FastGRCBlockedError, FastGRCApprovalRequiredError } from 'fastgrc-openclaw';
// In your OpenClaw error handler or agent wrapper:
try {
await agent.run(task);
} catch (err) {
if (err instanceof FastGRCBlockedError) {
console.log('Blocked by policy:', err.matchedRule);
console.log('Reason:', err.reasoning);
// Surface err.message to the user — it's human-readable
}
if (err instanceof FastGRCApprovalRequiredError) {
console.log('Approval needed:', err.dashboardUrl);
}
}What gets evaluated
Every before_tool_call invocation sends:
{
"subjectContent": "tool_name: delete_record\nargs: {\"table\":\"users\",\"id\":\"123\"}",
"subjectType": "tool_argument",
"direction": "ingress",
"agentId": "<agent context id>"
}The FastGRC policy router checks this against your configured rules (blocked actions, sensitive patterns, approval patterns) and returns a decision in ~20–50ms.
Safety guarantees
- ✓Fail-open: If FastGRC is unreachable or times out, the tool call is allowed through with a
console.warn. FastGRC 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.