Skip to content

Feature: a registerable, authoritative approval-resolver seam (capability-level, cross-harness, exclusive) for external approval providers #97152

Description

@beenhero

Problem statement

OpenClaw already has rich approval machinery — before_tool_call hooks, requireApproval / plugin.approval.*, approvals.exec & approvals.plugin, and the Codex native-permission relay. These work well for operator-in-the-loop tap / /approve approvals.

What they don't yet support is a third-party approval provider — a wallet-signature gate, a hardware-key approver, a policy/audit engine — being able to guarantee the three properties such a provider exists to give:

  • Completenessevery consequential action passes the gate.
  • Authorityonly the designated provider's decision can authorize; nothing else.
  • Integrity — the decision is bound to the exact action it authorized.

Today, each of those has a structural gap (described at the capability level; concrete reproductions are being shared via responsible disclosure):

Completeness

  • The Codex native-permission relay routes commandExecution and mcp approval requests, but native side-effecting tools are not routed through the same approval seam. Live-confirmed instance: network egress that is gated via exec (curl) runs ungated via the native web_fetch tool. The same structural gap applies to any native tool whose effect has a gated exec equivalent (file_write, browser, …); gating by tool name is therefore unsound — the gate must be at the effect/capability level. (Only the web_fetch/egress case was demonstrated live; the others follow from the same per-tool routing and are raised as design scope, not as separately confirmed bypasses.)
  • ACP-harness agents (e.g. a claude/gemini CLI spawned as the agent) don't traverse before_tool_call; their permission path (onPermissionRequest) isn't reachable by a plugin resolver.
  • Sub-agent spawns and agentFilter scoping don't clearly re-apply the gate to delegated work.

Authority

  • There is no exclusive / designated-resolver routing. approvals.exec and approvals.plugin are parallel surfaces; when more than one is active, first-to-resolve wins. A provider that requires (say) a wallet signature can be bypassed by a plain /approve tap on another active surface.
  • Resolution isn't bound to a designated approver identity — any client that can see the prompt on a configured channel can /approve it.

Integrity

  • The native approval payload is a human title/description string, not a stable, hashable commitment to the exact (tool, params). A provider whose authorization is a verifiable signature over the action has to render and verify out-of-band, and the gateway doesn't enforce that the resolved decision is bound to that request.

Proposed solution

A small core seam: a registerable, authoritative approval resolver, with four guarantees.

api.registerApprovalResolver(resolver, {
  scope: "tools.exec" | "tools.*" | { capabilities: ["fs.write", "net.egress", "process.exec", ...] },
  exclusive: true,   // suppress all other approval surfaces for in-scope decisions
});
  1. Capability-level, cross-harness coverage. Route all side-effecting tool calls — exec, file_write, web_fetch, browser, mcp, message, sub-agent spawn — across all harnesses (codex, ACP, native) through the resolver seam, classified by effect/capability, not tool name. (Closes the native-tool and ACP bypasses; makes the gate sound regardless of which tool produces the effect.)
  2. Exclusive routing. When a resolver registers exclusive for a scope, the gateway delivers in-scope decisions only to it and suppresses other surfaces (the suppressDelivery plumbing in handlePendingApprovalRequest already exists). No races; the designated provider is the sole authority.
  3. Decision↔request binding + resolver evidence. Hand the resolver a stable descriptor { requestId, tool, capability, paramsDigest }, and let it return — alongside allow/deny — an opaque proof (e.g. a signature) that the gateway records and binds to that requestId. A decision can't be replayed onto another request, and authorization becomes verifiable + non-repudiable, not a boolean tap.
  4. Fail-closed on every path (no resolver, timeout, disconnect, error) — already the norm for plugin.approval; make it the contract for the seam.

Alternatives considered

  • Plugin-only (today's before_tool_call). A plugin can gate the tools that reach the hook, but it cannot (a) intercept native tools that never route to it, (b) cover ACP harnesses, or (c) suppress competing approval surfaces to enforce exclusivity. Those require the core dispatch — which is why only the seam is a core ask. Per CONTRIBUTING's "prefer third-party plugins," the consumers (wallet gate, policy engine, …) stay third-party plugins; the seam is the minimal core enabler.
  • Per-channel approver rules. Improve Authority partially, but give no completeness, no cryptographic binding, and no exclusivity.
  • Disabling native tools. Cripples the agent and is brittle — each new tool reopens the gap.

Impact / prior art

Unlocks a class of authoritative external approvers OpenClaw can't host today:

  • Wallet-signature gates — an owner's mobile/hardware wallet signature is the authorization, cryptographically bound to what executes (the motivating case).
  • Passkey / security-key approvals; enterprise policy engines; tamper-evident audit (the recorded proof is the non-repudiable record).

In-tree prior art this composes (rather than duplicates): before_tool_call + requireApproval, plugin.approval.*, the Codex native-permission relay, and the existing suppressDelivery path.

Acceptance sketch (optional, for discussion)

  • A side-effecting native tool is gated identically to its exec equivalent once an exclusive capability resolver covers it — e.g. web_fetch under net.egress gates like curl (the case confirmed live); the same holds for other capabilities (fs.write, …) by construction.
  • With an exclusive resolver registered, a concurrent /approve on another surface does not resolve an in-scope request.
  • The resolver's returned proof is retrievable and bound to the requestId in the approval record.
  • Every failure path (no resolver / timeout / disconnect) blocks.

Filed from a live investigation building a wallet-signature approval provider (Sigil) on OpenClaw. Specific bypass reproductions are being shared via responsible disclosure, not in this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:needs-infoClawSweeper needs more reporter information before it can verify this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🦐 gold shrimpDecent issue quality, but reproduction details are still incomplete.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions