openclaw - ✅(Solved) Fix Feature Request: Pre-tool execution hooks / middleware API [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#49342Fetched 2026-04-08 00:56:17
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2

Root Cause

Prompt-level safety rules ("never use rm") are best-effort. Infrastructure-level enforcement (a hook that actually prevents the call) is deterministic. As agent organizations scale, the gap between "the agent was told not to" and "the agent cannot" becomes critical.

Fix Action

Fixed

PR fix notes

PR #55053: feat(guardrails): pluggable GuardrailProvider interface for tool authorization

Description (problem / solution / changelog)

Summary

  • Problem: OpenClaw has exec approvals for shell commands, but no general-purpose authorization for other tools -- file writes, browser actions, messaging, MCP tools, git operations. An agent can write to ~/.ssh/authorized_keys or send messages to arbitrary recipients with no policy check.
  • Why it matters: this has been requested across 10+ issues spanning the past few months (listed below). Security-conscious deployments have no standard way to enforce tool-level authorization. Every solution is ad-hoc and incompatible.
  • What changed: added a pluggable GuardrailProvider type as a core service (not a plugin). Ships with a built-in AllowlistProvider. Runs before plugin hooks -- cannot be bypassed. Zero impact when not configured.
  • What did NOT change (scope boundary): no changes to the steerable agent loop, exec approvals, plugin system, or any existing tool. No bundled external providers.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #46441
  • Related #22068 (tool:before/tool:after internal hooks -- this PR builds on that infrastructure)
  • This PR fixes a bug or regression

Community issues this unblocks:

  • #513 -- Path-based access control rules
  • #1546 -- Per-group tool policies
  • #6823 -- Execution guardrails (agent deleted OAuth credentials)
  • #8081 -- Multi-user RBAC
  • #12202 -- Per-agent file path access control
  • #12617 -- Model output context to before_tool_call
  • #23900 -- Self-preservation guardrails for agents
  • #30504 -- Middleware hooks for agent protocol enforcement
  • #48304 -- Tool-level authorization policy
  • #48503 -- Enrich before_tool_call event
  • #49183 -- Per-session tool policies
  • #49342 -- Pre-tool execution hooks / middleware API
  • #52845 -- Per-agent exec security mode

Root Cause / Regression History (if applicable)

N/A

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: test/guardrails/builtin.test.ts, test/guardrails/index.test.ts
  • Scenario the test should lock in: AllowlistProvider allow/deny logic, evaluateGuardrail fail-closed/fail-open behavior, loadGuardrailProvider builtin and error paths.
  • Why this is the smallest reliable guardrail: the interface is a new subsystem with no existing callers, so unit tests at the provider and evaluation boundary cover the contract without broader integration setup.
  • Existing test that already covers this (if any): none -- this is a new subsystem.
  • If no new test is added, why not: N/A -- new tests are included.

User-visible / Behavior Changes

When guardrails.enabled is true in config.yaml, every tool call is evaluated against the configured provider before execution. If the provider denies, the tool is blocked and the agent sees a denial reason. When not configured, behavior is identical to before this PR.

Security Impact (required)

  • New permissions/capabilities? Yes
  • Secrets/tokens handling changed? No
  • New/changed network calls? No (AllowlistProvider is local-only; external providers may make calls but that is provider-specific)
  • Command/tool execution surface changed? Yes
  • Data access scope changed? No
  • If any Yes, explain risk and mitigation: this adds a pre-execution authorization gate for all tool calls. The gate is opt-in (disabled by default). When enabled, it runs before plugin hooks as a core service, so it cannot be bypassed by disabling a plugin. failClosed defaults to true, meaning provider errors block tool calls rather than allowing them. The AllowlistProvider has zero external dependencies.

Repro and Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22 + pnpm workspace

Steps to verify

  1. Add to config.yaml:
guardrails:
  enabled: true
  provider:
    use: "builtin:allowlist"
    config:
      deniedTools:
        - "exec"
  1. Start OpenClaw
  2. Ask agent: "Run echo hello using exec"
  3. Expected: agent sees "Guardrail (allowlist): 'exec' is in the denied list"
  4. Remove "exec" from deniedTools, restart
  5. Same request now executes normally

Tests

pnpm test test/guardrails/

Changed files

  • docs/docs.json (modified, +1/-0)
  • docs/tools/guardrails.md (added, +307/-0)
  • src/agents/pi-tools.before-tool-call.ts (modified, +25/-0)
  • src/config/types.openclaw.ts (modified, +2/-0)
  • src/config/zod-schema.guardrails.ts (added, +21/-0)
  • src/config/zod-schema.ts (modified, +2/-0)
  • src/gateway/server-plugin-bootstrap.ts (modified, +2/-0)
  • src/guardrails/builtin.ts (added, +39/-0)
  • src/guardrails/index.ts (added, +88/-0)
  • src/guardrails/init.ts (added, +53/-0)
  • src/guardrails/types.ts (added, +59/-0)
  • test/guardrails/builtin.test.ts (added, +59/-0)
  • test/guardrails/index.test.ts (added, +105/-0)
RAW_BUFFERClick to expand / collapse

Use Case

Running a multi-agent organization where different agents have different permission levels. Currently, safety guardrails can only be enforced via prompt instructions, which are inherently bypassable.

Proposed Feature

A hook/middleware system that runs before tool execution, allowing:

  1. Pre-exec hooks — validate or block specific tool calls before they run (e.g., block rm commands, require approval for message sends to external channels)
  2. Output filters — sanitize or validate agent output before delivery
  3. Configurable per-agent — different agents get different permission scopes

Why This Matters

Prompt-level safety rules ("never use rm") are best-effort. Infrastructure-level enforcement (a hook that actually prevents the call) is deterministic. As agent organizations scale, the gap between "the agent was told not to" and "the agent cannot" becomes critical.

Minimal Implementation

A tools.hooks.preSend config in openclaw.json that points to a script/function evaluated before each tool call, with the ability to approve, modify, or deny the call.

Would love to see this on the roadmap.

extent analysis

Fix Plan

To implement the proposed hook/middleware system, we will focus on the following steps:

  • Create a tools.hooks.preSend configuration in openclaw.json to point to a script or function.
  • Develop a pre-exec hook function to validate or block specific tool calls.
  • Implement output filters to sanitize or validate agent output.

Example Code

Here's an example of how the tools.hooks.preSend configuration could be implemented in openclaw.json:

{
  "tools": {
    "hooks": {
      "preSend": "./preSendHook.js"
    }
  }
}

And here's an example of the preSendHook.js function:

module.exports = async function preSendHook(toolCall) {
  // Validate or block specific tool calls
  if (toolCall.command === 'rm') {
    return { approved: false, message: 'rm command is not allowed' };
  }

  // Require approval for message sends to external channels
  if (toolCall.command === 'message' && toolCall.channel === 'external') {
    // Send approval request to administrator
    const approval = await sendApprovalRequest(toolCall);
    if (!approval) {
      return { approved: false, message: 'Message send to external channel requires approval' };
    }
  }

  return { approved: true };
};

Verification

To verify that the fix worked, test the following scenarios:

  • Attempt to run a blocked tool call (e.g., rm) and verify that it is blocked.
  • Attempt to send a message to an external channel and verify that approval is required.
  • Verify that the output filters are correctly sanitizing or validating agent output.

Extra Tips

  • Make sure to handle errors and exceptions properly in the pre-exec hook function.
  • Consider implementing a logging mechanism to track tool calls and approval requests.
  • Review and update the tools.hooks.preSend configuration regularly to ensure that it remains effective and up-to-date.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Feature Request: Pre-tool execution hooks / middleware API [1 pull requests, 1 participants]