OpenClaw’s `message_sending` Hook Bypass Is the Kind of Security Bug That Makes Extensions Look Safer Than They Are

OpenClaw’s `message_sending` Hook Bypass Is the Kind of Security Bug That Makes Extensions Look Safer Than They Are

A security hook that silently does not run is worse than no hook. No hook forces operators to be honest about the risk. A documented hook that appears installed, loaded, and available — while major delivery paths skip it — creates the kind of false confidence that gets secrets posted to channels with a clean bill of health.

That is why OpenClaw issue #92374 matters. The report says plugins registering api.on("message_sending", ...) can be bypassed on important agent-reply delivery paths when a channel supplies its own dispatcherOptions.beforeDeliver. Telegram is the named example: conversational direct replies are delivered, internal send events fire, but the documented outbound message_sending hook never runs. If you expected that hook to rewrite, redact, cancel, or log outbound content, the system just turned your policy layer into a decoration.

The documentation describes message_sending as a hook that can “rewrite outbound content or cancel delivery.” That is exactly the seam a sensible operator would choose for DLP, compliance language, customer-data filtering, policy banners, channel-specific redaction, or kill-switch behavior before a message leaves the agent. The issue says the defect exists in OpenClaw 2026.5.26 and remains present in 2026.6.1, with Telegram as the concrete reproduction path.

The branch that bypasses policy

The mechanism is depressingly plausible. In 2026.5.26, the relevant delivery logic reportedly uses dispatcherOptions.beforeDeliver ?? buildMessageSendingBeforeDeliver(finalized). If the channel supplies a beforeDeliver, the global hook builder does not run. In 2026.6.1, combineBeforeDeliverHooks(...) still composes only replyPayloadBeforeDeliver with the channel hook when beforeDeliver exists; buildMessageSendingBeforeDeliver remains on the other branch. That is not a race. It is deterministic exclusion.

The report also identifies a second class of gap: stream-finalized draft or preview paths emit internal message:sent events but do not traverse deliverReplies, so restoring the normal delivery seam would still miss visible streamed finals unless the repair covers those paths too. A third problem is diagnostic silence: remaining call sites use optional chaining around getGlobalHookRunner()?.hasHooks("message_sending"), so a null runner or empty registry can skip without a meaningful warning. That is polite for a convenience callback. It is not acceptable for a governance seam.

The reproduction is simple enough to be uncomfortable: install a minimal extension that logs MS-HOOK, confirm registration at gateway start, send a DM to a Telegram-connected agent, observe the reply delivered, and see no MS-HOOK log even though internal message:sent fired. ClawSweeper reportedly reviewed current main and confirmed from source inspection that channel-provided beforeDeliver deterministically excludes message_sending. The recommendation is the obvious one: a single canonical pre-send pipeline with exactly-once hook execution.

Outbound delivery is a security boundary now

Agent platforms need to stop treating outbound messages as mere presentation. A reply can contain customer records retrieved from a tool, a stack trace with environment details, a credential accidentally pasted into context, a generated command, an internal plan, or an instruction that triggers another automation. Once agents are connected to Slack, Telegram, WhatsApp, email, ticketing systems, and webhooks, outbound content is part of the data-loss boundary.

That means the pre-send path deserves the same engineering discipline as auth middleware. There should be one canonical delivery graph. Channel-specific formatting and payload mutation should compose with global policy, not replace it. Hook results should be observable. If a message leaves the platform, an operator should be able to answer: which policies ran, in what order, what did they decide, and did any configured policy fail or skip? “The plugin loaded at startup” is not enough. Loaded is not enforced.

This is also a warning about extension ecosystems. Plugin hooks make platforms look governable because they expose seams. But a seam is useful only if every relevant code path passes through it. Branch-specific delivery logic, streaming previews, native replies, queued sends, command responses, and channel-specific dispatchers are exactly where policy contracts rot. The more channels a platform supports, the more tempting it becomes to patch each channel locally. That is how a governance hook becomes a best-effort hook.

For practitioners using OpenClaw plugins for DLP or compliance, the immediate action is to test enforcement per channel and per message class. Test Telegram DMs, group replies, native command replies, streaming finals, queued outbound sends, and any preview/finalization path. Add canary hooks that leave auditable markers in logs. Verify not just that a plugin registered, but that it executed before visible delivery. If a policy can cancel delivery, test cancellation with harmless text and confirm the message never leaves.

For OpenClaw, the fix should be architectural, not Telegram-specific. Build one pre-send pipeline. Run message_sending exactly once for every visible outbound message unless the message class is explicitly documented as exempt. Compose channel beforeDeliver callbacks inside that pipeline. Treat a missing hook runner as a diagnostic event when hooks are configured. Add tests that assert policy execution for each channel delivery path and streamed final path. This is not glamorous work, but neither is a seatbelt latch. It still has to work every time.

The broader take: agent platforms are becoming governance layers. Governance layers cannot have optional ghosts in the send path. If the extension contract says “rewrite or cancel before delivery,” then delivery must mean all delivery. Anything else is theater with a nicer API.

Sources: OpenClaw issue #92374, OpenClaw plugin hooks docs, OpenClaw plugin tool docs, OpenClaw agent loop docs